nss: upgrade to release 3.73
[LibreOffice.git] / sw / source / core / edit / edlingu.cxx
blobb9bf879a5cbec5c42b2afe5bd22d8589ab9b98fc
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/frame/XModel.hpp>
21 #include <com/sun/star/linguistic2/ProofreadingResult.hpp>
22 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
23 #include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
24 #include <com/sun/star/linguistic2/XLinguProperties.hpp>
25 #include <com/sun/star/text/XFlatParagraph.hpp>
26 #include <com/sun/star/i18n/ScriptType.hpp>
27 #include <com/sun/star/beans/XPropertySet.hpp>
28 #include <o3tl/any.hxx>
30 #include <unoflatpara.hxx>
32 #include <strings.hrc>
33 #include <hintids.hxx>
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>
58 using namespace ::svx;
59 using namespace ::com::sun::star;
60 using namespace ::com::sun::star::uno;
61 using namespace ::com::sun::star::beans;
62 using namespace ::com::sun::star::linguistic2;
64 namespace {
66 class SwLinguIter
68 SwEditShell* m_pSh;
69 std::unique_ptr<SwPosition> m_pStart;
70 std::unique_ptr<SwPosition> m_pEnd;
71 std::unique_ptr<SwPosition> m_pCurr;
72 std::unique_ptr<SwPosition> m_pCurrX;
73 sal_uInt16 m_nCursorCount;
75 public:
76 SwLinguIter();
78 SwEditShell* GetSh() { return m_pSh; }
80 const SwPosition *GetEnd() const { return m_pEnd.get(); }
81 void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); }
83 const SwPosition *GetStart() const { return m_pStart.get(); }
84 void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); }
86 const SwPosition *GetCurr() const { return m_pCurr.get(); }
87 void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); }
89 const SwPosition *GetCurrX() const { return m_pCurrX.get(); }
90 void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); }
92 sal_uInt16& GetCursorCnt() { return m_nCursorCount; }
94 // for the UI:
95 void Start_( SwEditShell *pSh, SwDocPositions eStart,
96 SwDocPositions eEnd );
97 void End_(bool bRestoreSelection = true);
100 // #i18881# to be able to identify the positions of the changed words
101 // the content positions of each portion need to be saved
102 struct SpellContentPosition
104 sal_Int32 nLeft;
105 sal_Int32 nRight;
110 typedef std::vector<SpellContentPosition> SpellContentPositions;
112 namespace {
114 class SwSpellIter : public SwLinguIter
116 uno::Reference<XSpellChecker1> m_xSpeller;
117 svx::SpellPortions m_aLastPortions;
119 SpellContentPositions m_aLastPositions;
120 bool m_bBackToStartOfSentence;
122 void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
123 linguistic2::ProofreadingResult* pGrammarResult,
124 bool bIsField, bool bIsHidden);
126 void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
127 linguistic2::ProofreadingResult* pGrammarResult,
128 const SpellContentPositions& rDeletedRedlines);
129 public:
130 SwSpellIter()
131 : m_bBackToStartOfSentence(false)
135 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
137 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
139 bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck);
140 void ToSentenceStart();
141 const svx::SpellPortions& GetLastPortions() const { return m_aLastPortions; }
142 const SpellContentPositions& GetLastPositions() const { return m_aLastPositions; }
145 /// used for text conversion
146 class SwConvIter : public SwLinguIter
148 SwConversionArgs& m_rArgs;
150 public:
151 explicit SwConvIter(SwConversionArgs& rConvArgs)
152 : m_rArgs(rConvArgs)
156 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
158 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
161 class SwHyphIter : public SwLinguIter
163 // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter?
164 const SwTextNode *m_pLastNode;
165 SwTextFrame *m_pLastFrame;
166 friend SwTextFrame * sw::SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& rCreator);
168 bool m_bOldIdle;
169 static void DelSoftHyph( SwPaM &rPam );
171 public:
172 SwHyphIter()
173 : m_pLastNode(nullptr)
174 , m_pLastFrame(nullptr)
175 , m_bOldIdle(false)
179 void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
180 void End();
182 void Ignore();
184 uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
186 static bool IsAuto();
187 void InsertSoftHyph( const sal_Int32 nHyphPos );
188 void ShowSelection();
193 static SwSpellIter* g_pSpellIter = nullptr;
194 static SwConvIter* g_pConvIter = nullptr;
195 static SwHyphIter* g_pHyphIter = nullptr;
197 SwLinguIter::SwLinguIter()
198 : m_pSh(nullptr)
199 , m_nCursorCount(0)
201 // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc.
204 void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart,
205 SwDocPositions eEnd )
207 // TODO missing: ensurance of re-entrance, locking
208 if (m_pSh)
209 return;
211 bool bSetCurr;
213 m_pSh = pShell;
215 CurrShell aCurr(m_pSh);
217 OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?");
219 SwPaM* pCursor = m_pSh->GetCursor();
221 if( pShell->HasSelection() || pCursor != pCursor->GetNext() )
223 bSetCurr = nullptr != GetCurr();
224 m_nCursorCount = m_pSh->GetCursorCnt();
225 if (m_pSh->IsTableMode())
226 m_pSh->TableCursorToCursor();
228 m_pSh->Push();
229 sal_uInt16 n;
230 for (n = 0; n < m_nCursorCount; ++n)
232 m_pSh->Push();
233 m_pSh->DestroyCursor();
235 m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
237 else
239 bSetCurr = false;
240 m_nCursorCount = 1;
241 m_pSh->Push();
242 m_pSh->SetLinguRange(eStart, eEnd);
245 pCursor = m_pSh->GetCursor();
246 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
247 pCursor->Exchange();
249 m_pStart.reset(new SwPosition(*pCursor->GetPoint()));
250 m_pEnd.reset(new SwPosition(*pCursor->GetMark()));
251 if( bSetCurr )
253 SwPosition* pNew = new SwPosition( *GetStart() );
254 SetCurr( pNew );
255 pNew = new SwPosition( *pNew );
256 SetCurrX( pNew );
259 pCursor->SetMark();
262 void SwLinguIter::End_(bool bRestoreSelection)
264 if (!m_pSh)
265 return;
267 OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?");
268 if(bRestoreSelection)
270 while (m_nCursorCount--)
271 m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
273 m_pSh->KillPams();
274 m_pSh->ClearMark();
276 m_pStart.reset();
277 m_pEnd.reset();
278 m_pCurr.reset();
279 m_pCurrX.reset();
281 m_pSh = nullptr;
284 void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart,
285 SwDocPositions eEnd )
287 if( GetSh() )
288 return;
290 m_xSpeller = ::GetSpellChecker();
291 if (m_xSpeller.is())
292 Start_( pShell, eStart, eEnd );
293 m_aLastPortions.clear();
294 m_aLastPositions.clear();
297 // This method is the origin of SwEditShell::SpellContinue()
298 uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
300 //!!
301 //!! Please check SwConvIter also when modifying this
302 //!!
304 uno::Any aSpellRet;
305 SwEditShell *pMySh = GetSh();
306 if( !pMySh )
307 return aSpellRet;
309 OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?");
311 uno::Reference< uno::XInterface > xSpellRet;
312 bool bGoOn = true;
313 do {
314 SwPaM *pCursor = pMySh->GetCursor();
315 if ( !pCursor->HasMark() )
316 pCursor->SetMark();
318 *pMySh->GetCursor()->GetPoint() = *GetCurr();
319 *pMySh->GetCursor()->GetMark() = *GetEnd();
320 pMySh->GetDoc()->Spell(*pMySh->GetCursor(), m_xSpeller, pPageCnt, pPageSt, false,
321 pMySh->GetLayout())
322 >>= xSpellRet;
323 bGoOn = GetCursorCnt() > 1;
324 if( xSpellRet.is() )
326 bGoOn = false;
327 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
328 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
329 SetCurr( pNewPoint );
330 SetCurrX( pNewMark );
332 if( bGoOn )
334 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
335 pCursor = pMySh->GetCursor();
336 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
337 pCursor->Exchange();
338 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
339 SetStart( pNew );
340 pNew = new SwPosition( *pCursor->GetMark() );
341 SetEnd( pNew );
342 pNew = new SwPosition( *GetStart() );
343 SetCurr( pNew );
344 pNew = new SwPosition( *pNew );
345 SetCurrX( pNew );
346 pCursor->SetMark();
347 --GetCursorCnt();
349 }while ( bGoOn );
350 aSpellRet <<= xSpellRet;
351 return aSpellRet;
354 void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart,
355 SwDocPositions eEnd )
357 if( GetSh() )
358 return;
359 Start_( pShell, eStart, eEnd );
362 uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
364 //!!
365 //!! Please check SwSpellIter also when modifying this
366 //!!
368 uno::Any aConvRet( makeAny( OUString() ) );
369 SwEditShell *pMySh = GetSh();
370 if( !pMySh )
371 return aConvRet;
373 OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?");
375 OUString aConvText;
376 bool bGoOn = true;
377 do {
378 SwPaM *pCursor = pMySh->GetCursor();
379 if ( !pCursor->HasMark() )
380 pCursor->SetMark();
382 *pMySh->GetCursor()->GetPoint() = *GetCurr();
383 *pMySh->GetCursor()->GetMark() = *GetEnd();
385 // call function to find next text portion to be converted
386 uno::Reference< linguistic2::XSpellChecker1 > xEmpty;
387 pMySh->GetDoc()->Spell(*pMySh->GetCursor(), xEmpty, pPageCnt, pPageSt, false,
388 pMySh->GetLayout(), &m_rArgs)
389 >>= aConvText;
391 bGoOn = GetCursorCnt() > 1;
392 if( !aConvText.isEmpty() )
394 bGoOn = false;
395 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
396 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
398 SetCurr( pNewPoint );
399 SetCurrX( pNewMark );
401 if( bGoOn )
403 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
404 pCursor = pMySh->GetCursor();
405 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
406 pCursor->Exchange();
407 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
408 SetStart( pNew );
409 pNew = new SwPosition( *pCursor->GetMark() );
410 SetEnd( pNew );
411 pNew = new SwPosition( *GetStart() );
412 SetCurr( pNew );
413 pNew = new SwPosition( *pNew );
414 SetCurrX( pNew );
415 pCursor->SetMark();
416 --GetCursorCnt();
418 }while ( bGoOn );
419 return makeAny( aConvText );
422 bool SwHyphIter::IsAuto()
424 uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() );
425 return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue(
426 UPN_IS_HYPH_AUTO ));
429 void SwHyphIter::ShowSelection()
431 SwEditShell *pMySh = GetSh();
432 if( pMySh )
434 pMySh->StartAction();
435 // Caution! Due to EndAction() formatting is started which can lead to the fact that new
436 // words are added to/set in the Hyphenator. Thus: save!
437 pMySh->EndAction();
441 void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd )
443 // robust
444 if( GetSh() || GetEnd() )
446 OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" );
447 return;
450 // nothing to do (at least not in the way as in the "else" part)
451 m_bOldIdle = pShell->GetViewOptions()->IsIdle();
452 pShell->GetViewOptions()->SetIdle( false );
453 Start_( pShell, eStart, eEnd );
456 // restore selections
457 void SwHyphIter::End()
459 if( !GetSh() )
460 return;
461 GetSh()->GetViewOptions()->SetIdle(m_bOldIdle);
462 End_();
465 uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
467 uno::Any aHyphRet;
468 SwEditShell *pMySh = GetSh();
469 if( !pMySh )
470 return aHyphRet;
472 const bool bAuto = IsAuto();
473 uno::Reference< XHyphenatedWord > xHyphWord;
474 bool bGoOn = false;
475 do {
476 SwPaM *pCursor;
477 do {
478 OSL_ENSURE( GetEnd(), "SwHyphIter::Continue without Start?" );
479 pCursor = pMySh->GetCursor();
480 if ( !pCursor->HasMark() )
481 pCursor->SetMark();
482 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
484 pCursor->Exchange();
485 pCursor->SetMark();
488 if ( *pCursor->End() <= *GetEnd() )
490 *pCursor->GetMark() = *GetEnd();
492 // Do we need to break the word at the current cursor position?
493 const Point aCursorPos( pMySh->GetCharRect().Pos() );
494 xHyphWord = pMySh->GetDoc()->Hyphenate( pCursor, aCursorPos,
495 pPageCnt, pPageSt );
498 if( bAuto && xHyphWord.is() )
500 SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1);
502 } while( bAuto && xHyphWord.is() ); //end of do-while
503 bGoOn = !xHyphWord.is() && GetCursorCnt() > 1;
505 if( bGoOn )
507 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
508 pCursor = pMySh->GetCursor();
509 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
510 pCursor->Exchange();
511 SwPosition* pNew = new SwPosition(*pCursor->End());
512 SetEnd( pNew );
513 pCursor->SetMark();
514 --GetCursorCnt();
516 } while ( bGoOn );
517 aHyphRet <<= xHyphWord;
518 return aHyphRet;
521 /// ignore hyphenation
522 void SwHyphIter::Ignore()
524 SwEditShell *pMySh = GetSh();
525 SwPaM *pCursor = pMySh->GetCursor();
527 // delete old SoftHyphen
528 DelSoftHyph( *pCursor );
530 // and continue
531 pCursor->Start()->nContent = pCursor->End()->nContent;
532 pCursor->SetMark();
535 void SwHyphIter::DelSoftHyph( SwPaM &rPam )
537 const SwPosition* pStt = rPam.Start();
538 const sal_Int32 nStart = pStt->nContent.GetIndex();
539 const sal_Int32 nEnd = rPam.End()->nContent.GetIndex();
540 SwTextNode *pNode = pStt->nNode.GetNode().GetTextNode();
541 pNode->DelSoftHyph( nStart, nEnd );
544 void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos )
546 SwEditShell *pMySh = GetSh();
547 OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()");
548 if( !pMySh )
549 return;
551 SwPaM *pCursor = pMySh->GetCursor();
552 SwPosition* pSttPos = pCursor->Start();
553 SwPosition* pEndPos = pCursor->End();
555 const sal_Int32 nLastHyphLen = GetEnd()->nContent.GetIndex() -
556 pSttPos->nContent.GetIndex();
558 if( pSttPos->nNode != pEndPos->nNode || !nLastHyphLen )
560 OSL_ENSURE( pSttPos->nNode == pEndPos->nNode,
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->nContent += 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 = GetSwCursor();
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 Push();
842 LeftMargin();
843 const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex();
844 RightMargin();
845 const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex();
846 Pop(PopMode::DeleteCurrent);
848 // make sure the selection build later from the data below does
849 // not "in word" character to the left and right in order to
850 // preserve those. Therefore count those "in words" in order to
851 // modify the selection accordingly.
852 const sal_Unicode* pChar = aText.getStr();
853 sal_Int32 nLeft = 0;
854 while (*pChar++ == CH_TXTATR_INWORD)
855 ++nLeft;
856 pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
857 sal_Int32 nRight = 0;
858 while (pChar && *pChar-- == CH_TXTATR_INWORD)
859 ++nRight;
861 aPos.nContent = nBegin + nLeft;
862 SwPaM* pCursor = GetCursor();
863 *pCursor->GetPoint() = aPos;
864 pCursor->SetMark();
865 ExtendSelection( true, nLen - nLeft - nRight );
866 // don't determine the rectangle in the current line
867 const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
868 // take one less than the line end - otherwise the next line would be calculated
869 const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd
870 ? nLineEnd : (nBegin + nLen - nLeft - nRight);
871 Push();
872 pCursor->DeleteMark();
873 SwIndex& rContent = GetCursor()->GetPoint()->nContent;
874 rContent = nWordStart;
875 SwRect aStartRect;
876 SwCursorMoveState aState;
877 aState.m_bRealWidth = true;
878 SwContentNode* pContentNode = pCursor->GetContentNode();
879 std::pair<Point, bool> tmp;
880 if (pPt)
882 tmp.first = *pPt;
883 tmp.second = false;
885 SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr);
887 pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
888 rContent = nWordEnd - 1;
889 SwRect aEndRect;
890 pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
891 rSelectRect = aStartRect.Union( aEndRect );
892 Pop(PopMode::DeleteCurrent);
895 /** Get a list of potential corrections for misspelled word.
897 * If empty, word is unknown but there are no corrections available.
898 * If NULL then the word is not misspelled but correct.
900 * @brief SwEditShell::GetCorrection
901 * @return list or NULL pointer
903 uno::Reference< XSpellAlternatives >
904 SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
906 uno::Reference< XSpellAlternatives > xSpellAlt;
908 if( IsTableMode() )
909 return nullptr;
910 SwPaM* pCursor = GetCursor();
911 SwPosition aPos( *pCursor->GetPoint() );
912 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
913 SwTextNode *pNode = nullptr;
914 SwWrongList *pWrong = nullptr;
915 if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
916 pNode = aPos.nNode.GetNode().GetTextNode();
917 if (nullptr == pNode)
918 pNode = pCursor->GetNode().GetTextNode();
919 if (nullptr != pNode)
920 pWrong = pNode->GetWrong();
921 if (nullptr != pWrong && !pNode->IsInProtectSect())
923 sal_Int32 nBegin = aPos.nContent.GetIndex();
924 sal_Int32 nLen = 1;
925 if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin))
927 const OUString aText(pNode->GetText().copy(nBegin, nLen));
928 OUString aWord = aText.replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), "")
929 .replaceAll(OUStringChar(CH_TXTATR_INWORD), "");
931 uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
932 if( xSpell.is() )
934 LanguageType eActLang = pNode->GetLang( nBegin, nLen );
935 if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ))
937 // restrict the maximal number of suggestions displayed
938 // in the context menu.
939 // Note: That could of course be done by clipping the
940 // resulting sequence but the current third party
941 // implementations result differs greatly if the number of
942 // suggestions to be returned gets changed. Statistically
943 // it gets much better if told to return e.g. only 7 strings
944 // than returning e.g. 16 suggestions and using only the
945 // first 7. Thus we hand down the value to use to that
946 // implementation here by providing an additional parameter.
947 Sequence< PropertyValue > aPropVals(1);
948 PropertyValue &rVal = aPropVals.getArray()[0];
949 rVal.Name = UPN_MAX_NUMBER_OF_SUGGESTIONS;
950 rVal.Value <<= sal_Int16(7);
952 xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals );
956 if ( xSpellAlt.is() ) // error found?
958 HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect );
962 return xSpellAlt;
965 bool SwEditShell::GetGrammarCorrection(
966 linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result
967 sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked...
968 sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors
969 uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found
970 const Point *pPt, SwRect &rSelectRect )
972 bool bRes = false;
974 if( IsTableMode() )
975 return bRes;
977 SwPaM* pCursor = GetCursor();
978 SwPosition aPos( *pCursor->GetPoint() );
979 SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
980 SwTextNode *pNode = nullptr;
981 SwGrammarMarkUp *pWrong = nullptr;
982 if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
983 pNode = aPos.nNode.GetNode().GetTextNode();
984 if (nullptr == pNode)
985 pNode = pCursor->GetNode().GetTextNode();
986 if (nullptr != pNode)
987 pWrong = pNode->GetGrammarCheck();
988 if (nullptr != pWrong && !pNode->IsInProtectSect())
990 sal_Int32 nBegin = aPos.nContent.GetIndex();
991 sal_Int32 nLen = 1;
992 if (pWrong->InWrongWord(nBegin, nLen))
994 const OUString aText(pNode->GetText().copy(nBegin, nLen));
996 uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() );
997 if (xGCIterator.is())
999 uno::Reference< lang::XComponent > xDoc = mxDoc->GetDocShell()->GetBaseModel();
1001 // Expand the string:
1002 const ModelToViewHelper aConversionMap(*pNode, GetLayout());
1003 const OUString& aExpandText = aConversionMap.getViewText();
1004 // get XFlatParagraph to use...
1005 uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap );
1007 // get error position of cursor in XFlatParagraph
1008 rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin );
1010 const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) );
1011 const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) );
1013 rResult = xGCIterator->checkSentenceAtPosition(
1014 xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence,
1015 nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence,
1016 rErrorPosInText );
1017 bRes = true;
1019 // get suggestions to use for the specific error position
1020 rSuggestions.realloc( 0 );
1021 // return suggestions for first error that includes the given error position
1022 auto pError = std::find_if(rResult.aErrors.begin(), rResult.aErrors.end(),
1023 [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) {
1024 return rError.nErrorStart <= rErrorPosInText
1025 && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength; });
1026 if (pError != rResult.aErrors.end())
1028 rSuggestions = pError->aSuggestions;
1029 rErrorIndexInResult = static_cast<sal_Int32>(std::distance(rResult.aErrors.begin(), pError));
1033 if (rResult.aErrors.hasElements()) // error found?
1035 HandleCorrectionError( aText, aPos, nBegin, nLen, pPt, rSelectRect );
1040 return bRes;
1043 bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1045 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1046 if (!g_pSpellIter)
1047 return false;
1048 bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck);
1050 // make Selection visible - this should simply move the
1051 // cursor to the end of the sentence
1052 StartAction();
1053 EndAction();
1054 return bRet;
1057 ///make SpellIter start with the current sentence when called next time
1058 void SwEditShell::PutSpellingToSentenceStart()
1060 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1061 if (!g_pSpellIter)
1062 return;
1063 g_pSpellIter->ToSentenceStart();
1066 static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions)
1068 return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(),
1069 [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; }));
1072 void SwEditShell::MoveContinuationPosToEndOfCheckedSentence()
1074 // give hint that continuation position for spell/grammar checking is
1075 // at the end of this sentence
1076 if (g_pSpellIter)
1078 g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) );
1082 void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck)
1084 // Note: rNewPortions.size() == 0 is valid and happens when the whole
1085 // sentence got removed in the dialog
1087 OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
1088 if (!g_pSpellIter ||
1089 g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed
1090 return;
1092 const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions();
1093 const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions();
1094 OSL_ENSURE(!rLastPortions.empty() &&
1095 rLastPortions.size() == rLastPositions.size(),
1096 "last vectors of spelling results are not set or not equal");
1098 // iterate over the new portions, beginning at the end to take advantage of the previously
1099 // saved content positions
1101 mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1102 StartAction();
1104 SwPaM *pCursor = GetCursor();
1105 // save cursor position (which should be at the end of the current sentence)
1106 // for later restoration
1107 Push();
1109 sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions);
1110 if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size())
1112 OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" );
1113 OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" );
1114 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1116 // the simple case: the same number of elements on both sides
1117 // each changed element has to be applied to the corresponding source element
1118 svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
1119 SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end();
1120 SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end();
1123 --aCurrentNewPortion;
1124 --aCurrentOldPortion;
1125 --aCurrentOldPosition;
1126 //jump over redline portions
1127 while(aCurrentOldPortion->bIsHidden)
1129 if (aCurrentOldPortion != rLastPortions.begin() &&
1130 aCurrentOldPosition != rLastPositions.begin())
1132 --aCurrentOldPortion;
1133 --aCurrentOldPosition;
1135 else
1137 OSL_FAIL("ApplyChangedSentence: iterator positions broken" );
1138 break;
1141 if ( !pCursor->HasMark() )
1142 pCursor->SetMark();
1143 pCursor->GetPoint()->nContent = aCurrentOldPosition->nLeft;
1144 pCursor->GetMark()->nContent = aCurrentOldPosition->nRight;
1145 sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
1146 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1147 switch(nScriptType)
1149 case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1150 case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1152 if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
1154 // change text ...
1155 // ... and apply language if necessary
1156 if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1157 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1158 mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, aCurrentNewPortion->sText, false);
1160 else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
1162 // apply language
1163 SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
1165 else if( aCurrentNewPortion->bIgnoreThisError )
1167 // add the 'ignore' markup to the TextNode's grammar ignore markup list
1168 IgnoreGrammarErrorAt( *pCursor );
1169 OSL_FAIL("TODO: add ignore mark to text node");
1172 while(aCurrentNewPortion != rNewPortions.begin());
1174 else
1176 OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
1178 // select the complete sentence
1179 SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end();
1180 --aCurrentEndPosition;
1181 SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin();
1182 pCursor->GetPoint()->nContent = aCurrentStartPosition->nLeft;
1183 pCursor->GetMark()->nContent = aCurrentEndPosition->nRight;
1185 // delete the sentence completely
1186 mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor);
1187 for(const auto& rCurrentNewPortion : rNewPortions)
1189 // set the language attribute
1190 SvtScriptType nScriptType = GetScriptType();
1191 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1192 switch(nScriptType)
1194 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1195 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1196 default: break;
1198 SfxItemSet aSet(GetAttrPool(), {{nLangWhichId, nLangWhichId}});
1199 GetCurAttr( aSet );
1200 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1201 if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage)
1202 SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) );
1203 // insert the new string
1204 mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText);
1206 // set the cursor to the end of the inserted string
1207 *pCursor->Start() = *pCursor->End();
1211 // restore cursor to the end of the sentence
1212 // (will work also if the sentence length has changed,
1213 // since cursors get updated automatically!)
1214 Pop(PopMode::DeleteCurrent);
1216 // collapse cursor to the end of the modified sentence
1217 *pCursor->Start() = *pCursor->End();
1218 if (bRecheck)
1220 // in grammar check the current sentence has to be checked again
1221 GoStartSentence();
1223 // set continuation position for spell/grammar checking to the end of this sentence
1224 g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) );
1226 mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
1227 EndAction();
1230 /** Collect all deleted redlines of the current text node
1231 * beginning at the start of the cursor position
1233 static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh)
1235 SpellContentPositions aRedlines;
1236 SwDoc* pDoc = pSh->GetDoc();
1237 const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() );
1238 if ( bShowChg )
1240 SwPaM *pCursor = pSh->GetCursor();
1241 const SwPosition* pStartPos = pCursor->Start();
1242 const SwTextNode* pTextNode = pCursor->GetNode().GetTextNode();
1244 SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any );
1245 const sal_Int32 nStartIndex = pStartPos->nContent.GetIndex();
1246 for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
1248 const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
1250 if ( pRed->Start()->nNode > pTextNode->GetIndex() )
1251 break;
1253 if( RedlineType::Delete == pRed->GetType() )
1255 sal_Int32 nStart_, nEnd_;
1256 pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ );
1257 sal_Int32 nStart = nStart_;
1258 sal_Int32 nEnd = nEnd_;
1259 if(nStart >= nStartIndex || nEnd >= nStartIndex)
1261 SpellContentPosition aAdd;
1262 aAdd.nLeft = nStart;
1263 aAdd.nRight = nEnd;
1264 aRedlines.push_back(aAdd);
1269 return aRedlines;
1272 /// remove the redline positions after the current selection
1273 static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh )
1275 if(!aDeletedRedlines.empty())
1277 SwPaM *pCursor = pSh->GetCursor();
1278 const SwPosition* pEndPos = pCursor->End();
1279 const sal_Int32 nEnd = pEndPos->nContent.GetIndex();
1280 while(!aDeletedRedlines.empty() &&
1281 aDeletedRedlines.back().nLeft > nEnd)
1283 aDeletedRedlines.pop_back();
1288 static SpellContentPosition lcl_FindNextDeletedRedline(
1289 const SpellContentPositions& rDeletedRedlines,
1290 sal_Int32 nSearchFrom )
1292 SpellContentPosition aRet;
1293 aRet.nLeft = aRet.nRight = SAL_MAX_INT32;
1294 if(!rDeletedRedlines.empty())
1296 auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(),
1297 [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; });
1298 if (aIter != rDeletedRedlines.end())
1299 aRet = *aIter;
1301 return aRet;
1304 bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
1306 bool bRet = false;
1307 m_aLastPortions.clear();
1308 m_aLastPositions.clear();
1310 SwEditShell *pMySh = GetSh();
1311 if( !pMySh )
1312 return false;
1314 OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?");
1316 uno::Reference< XSpellAlternatives > xSpellRet;
1317 linguistic2::ProofreadingResult aGrammarResult;
1318 bool bGoOn = true;
1319 bool bGrammarErrorFound = false;
1320 do {
1321 SwPaM *pCursor = pMySh->GetCursor();
1322 if ( !pCursor->HasMark() )
1323 pCursor->SetMark();
1325 *pCursor->GetPoint() = *GetCurr();
1326 *pCursor->GetMark() = *GetEnd();
1328 if (m_bBackToStartOfSentence)
1330 pMySh->GoStartSentence();
1331 m_bBackToStartOfSentence = false;
1333 uno::Any aSpellRet = pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr,
1334 bIsGrammarCheck, pMySh->GetLayout());
1335 aSpellRet >>= xSpellRet;
1336 aSpellRet >>= aGrammarResult;
1337 bGoOn = GetCursorCnt() > 1;
1338 bGrammarErrorFound = aGrammarResult.aErrors.hasElements();
1339 if( xSpellRet.is() || bGrammarErrorFound )
1341 bGoOn = false;
1342 SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
1343 SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
1345 SetCurr( pNewPoint );
1346 SetCurrX( pNewMark );
1348 if( bGoOn )
1350 pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
1351 pCursor = pMySh->GetCursor();
1352 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1353 pCursor->Exchange();
1354 SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
1355 SetStart( pNew );
1356 pNew = new SwPosition( *pCursor->GetMark() );
1357 SetEnd( pNew );
1358 pNew = new SwPosition( *GetStart() );
1359 SetCurr( pNew );
1360 pNew = new SwPosition( *pNew );
1361 SetCurrX( pNew );
1362 pCursor->SetMark();
1363 --GetCursorCnt();
1365 } while ( bGoOn );
1367 if(xSpellRet.is() || bGrammarErrorFound)
1369 // an error has been found
1370 // To fill the spell portions the beginning of the sentence has to be found
1371 SwPaM *pCursor = pMySh->GetCursor();
1372 // set the mark to the right if necessary
1373 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1374 pCursor->Exchange();
1375 // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error
1376 pCursor->DeleteMark();
1377 pCursor->SetMark();
1378 bool bStartSent = pMySh->GoStartSentence();
1379 SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh);
1380 if(bStartSent)
1382 // create a portion from the start part
1383 AddPortion(nullptr, nullptr, aDeletedRedlines);
1385 // Set the cursor to the error already found
1386 *pCursor->GetPoint() = *GetCurrX();
1387 *pCursor->GetMark() = *GetCurr();
1388 AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines);
1390 // save the end position of the error to continue from here
1391 SwPosition aSaveStartPos = *pCursor->End();
1392 // determine the end of the current sentence
1393 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1394 pCursor->Exchange();
1395 // again collapse to start marking after the end of the error
1396 pCursor->DeleteMark();
1397 pCursor->SetMark();
1399 pMySh->GoEndSentence();
1400 if( bGrammarErrorFound )
1402 const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetNode()), pMySh->GetLayout());
1403 const OUString& aExpandText = aConversionMap.getViewText();
1404 sal_Int32 nSentenceEnd =
1405 aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition );
1406 // remove trailing space
1407 if( aExpandText[nSentenceEnd - 1] == ' ' )
1408 --nSentenceEnd;
1409 if( pCursor->End()->nContent.GetIndex() < nSentenceEnd )
1411 pCursor->End()->nContent.Assign(
1412 pCursor->End()->nNode.GetNode().GetContentNode(), nSentenceEnd);
1416 lcl_CutRedlines( aDeletedRedlines, pMySh );
1417 // save the 'global' end of the spellchecking
1418 const SwPosition aSaveEndPos = *GetEnd();
1419 // set the sentence end as 'local' end
1420 SetEnd( new SwPosition( *pCursor->End() ));
1422 *pCursor->GetPoint() = aSaveStartPos;
1423 *pCursor->GetMark() = *GetEnd();
1424 // now the rest of the sentence has to be searched for errors
1425 // for each error the non-error text between the current and the last error has
1426 // to be added to the portions - if necessary broken into same-language-portions
1427 if( !bGrammarErrorFound ) //in grammar check there's only one error returned
1431 xSpellRet = nullptr;
1432 // don't search for grammar errors here anymore!
1433 pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr, false,
1434 pMySh->GetLayout())
1435 >>= xSpellRet;
1436 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1437 pCursor->Exchange();
1438 SetCurr( new SwPosition( *pCursor->GetPoint() ));
1439 SetCurrX( new SwPosition( *pCursor->GetMark() ));
1441 // if an error has been found go back to the text preceding the error
1442 if(xSpellRet.is())
1444 *pCursor->GetPoint() = aSaveStartPos;
1445 *pCursor->GetMark() = *GetCurr();
1447 // add the portion
1448 AddPortion(nullptr, nullptr, aDeletedRedlines);
1450 if(xSpellRet.is())
1452 *pCursor->GetPoint() = *GetCurr();
1453 *pCursor->GetMark() = *GetCurrX();
1454 AddPortion(xSpellRet, nullptr, aDeletedRedlines);
1455 // move the cursor to the end of the error string
1456 *pCursor->GetPoint() = *GetCurrX();
1457 // and save the end of the error as new start position
1458 aSaveStartPos = *GetCurrX();
1459 // and the end of the sentence
1460 *pCursor->GetMark() = *GetEnd();
1462 // if the end of the sentence has already been reached then break here
1463 if(*GetCurrX() >= *GetEnd())
1464 break;
1466 while(xSpellRet.is());
1468 else
1470 // go to the end of sentence as the grammar check returned it
1471 // at this time the Point is behind the grammar error
1472 // and the mark points to the sentence end as
1473 if ( *pCursor->GetPoint() < *pCursor->GetMark() )
1474 pCursor->Exchange();
1477 // the part between the last error and the end of the sentence has to be added
1478 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1479 if(*GetCurrX() < *GetEnd())
1481 AddPortion(nullptr, nullptr, aDeletedRedlines);
1483 // set the shell cursor to the end of the sentence to prevent a visible selection
1484 *pCursor->GetMark() = *GetEnd();
1485 if( !bIsGrammarCheck )
1487 // set the current position to the end of the sentence
1488 SetCurr( new SwPosition(*GetEnd()) );
1490 // restore the 'global' end
1491 SetEnd( new SwPosition(aSaveEndPos) );
1492 rPortions = m_aLastPortions;
1493 bRet = true;
1495 else
1497 // if no error could be found the selection has to be corrected - at least if it's not in the body
1498 *pMySh->GetCursor()->GetPoint() = *GetEnd();
1499 pMySh->GetCursor()->DeleteMark();
1502 return bRet;
1505 void SwSpellIter::ToSentenceStart() { m_bBackToStartOfSentence = true; }
1507 static LanguageType lcl_GetLanguage(SwEditShell& rSh)
1509 SvtScriptType nScriptType = rSh.GetScriptType();
1510 sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
1512 switch(nScriptType)
1514 case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
1515 case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
1516 default: break;
1518 SfxItemSet aSet(rSh.GetAttrPool(), {{nLangWhichId, nLangWhichId}});
1519 rSh.GetCurAttr( aSet );
1520 const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
1521 return rLang.GetLanguage();
1524 /// create a text portion at the given position
1525 void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
1526 linguistic2::ProofreadingResult* pGrammarResult,
1527 bool bIsField, bool bIsHidden)
1529 svx::SpellPortion aPortion;
1530 OUString sText;
1531 GetSh()->GetSelectedText( sText );
1532 if(sText.isEmpty())
1533 return;
1535 // in case of redlined deletions the selection of an error is not the same as the _real_ word
1536 if(xAlt.is())
1537 aPortion.sText = xAlt->getWord();
1538 else if(pGrammarResult)
1540 aPortion.bIsGrammarError = true;
1541 if(pGrammarResult->aErrors.hasElements())
1543 aPortion.aGrammarError = pGrammarResult->aErrors[0];
1544 aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength );
1545 aPortion.xGrammarChecker = pGrammarResult->xProofreader;
1546 auto pProperty = std::find_if(pGrammarResult->aProperties.begin(), pGrammarResult->aProperties.end(),
1547 [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; });
1548 if (pProperty != pGrammarResult->aProperties.end())
1549 pProperty->Value >>= aPortion.sDialogTitle;
1552 else
1553 aPortion.sText = sText;
1554 aPortion.eLanguage = lcl_GetLanguage(*GetSh());
1555 aPortion.bIsField = bIsField;
1556 aPortion.bIsHidden = bIsHidden;
1557 aPortion.xAlternatives = xAlt;
1558 SpellContentPosition aPosition;
1559 SwPaM *pCursor = GetSh()->GetCursor();
1560 aPosition.nLeft = pCursor->Start()->nContent.GetIndex();
1561 aPosition.nRight = pCursor->End()->nContent.GetIndex();
1562 m_aLastPortions.push_back(aPortion);
1563 m_aLastPositions.push_back(aPosition);
1566 void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
1567 linguistic2::ProofreadingResult* pGrammarResult,
1568 const SpellContentPositions& rDeletedRedlines)
1570 SwEditShell *pMySh = GetSh();
1571 OUString sText;
1572 pMySh->GetSelectedText( sText );
1573 if(sText.isEmpty())
1574 return;
1576 if(xAlt.is() || pGrammarResult != nullptr)
1578 CreatePortion(xAlt, pGrammarResult, false, false);
1580 else
1582 SwPaM *pCursor = GetSh()->GetCursor();
1583 if ( *pCursor->GetPoint() > *pCursor->GetMark() )
1584 pCursor->Exchange();
1585 // save the start and end positions
1586 SwPosition aStart(*pCursor->GetPoint());
1587 SwPosition aEnd(*pCursor->GetMark());
1588 // iterate over the text to find changes in language
1589 // set the mark equal to the point
1590 *pCursor->GetMark() = aStart;
1591 SwTextNode* pTextNode = pCursor->GetNode().GetTextNode();
1592 LanguageType eStartLanguage = lcl_GetLanguage(*GetSh());
1593 SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline(
1594 rDeletedRedlines, aStart.nContent.GetIndex() );
1595 if( aNextRedline.nLeft == aStart.nContent.GetIndex() )
1597 // select until the end of the current redline
1598 const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ?
1599 aEnd.nContent.GetIndex() : aNextRedline.nRight;
1600 pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
1601 CreatePortion(xAlt, pGrammarResult, false, true);
1602 aStart = *pCursor->End();
1603 // search for next redline
1604 aNextRedline = lcl_FindNextDeletedRedline(
1605 rDeletedRedlines, aStart.nContent.GetIndex() );
1607 while(*pCursor->GetPoint() < aEnd)
1609 // #125786 in table cell with fixed row height the cursor might not move forward
1610 if(!GetSh()->Right(1, CRSR_SKIP_CELLS))
1611 break;
1613 bool bField = false;
1614 // read the character at the current position to check if it's a field
1615 sal_Unicode const cChar =
1616 pTextNode->GetText()[pCursor->GetMark()->nContent.GetIndex()];
1617 if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
1619 const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt(
1620 pCursor->GetMark()->nContent.GetIndex() );
1621 const sal_uInt16 nWhich = pTextAttr
1622 ? pTextAttr->Which()
1623 : RES_TXTATR_END;
1624 switch (nWhich)
1626 case RES_TXTATR_FIELD:
1627 case RES_TXTATR_ANNOTATION:
1628 case RES_TXTATR_FTN:
1629 case RES_TXTATR_FLYCNT:
1630 bField = true;
1631 break;
1634 else if (cChar == CH_TXT_ATR_FORMELEMENT)
1636 SwPosition aPos(*pCursor->GetMark());
1637 bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos);
1640 LanguageType eCurLanguage = lcl_GetLanguage(*GetSh());
1641 bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->nContent.GetIndex();
1642 // create a portion if the next character
1643 // - is a field,
1644 // - is at the beginning of a deleted redline
1645 // - has a different language
1646 if(bField || bRedline || eCurLanguage != eStartLanguage)
1648 eStartLanguage = eCurLanguage;
1649 // go one step back - the cursor currently selects the first character
1650 // with a different language
1651 // in the case of redlining it's different
1652 if(eCurLanguage != eStartLanguage || bField)
1653 *pCursor->GetPoint() = *pCursor->GetMark();
1654 // set to the last start
1655 *pCursor->GetMark() = aStart;
1656 // create portion should only be called if a selection exists
1657 // there's no selection if there's a field at the beginning
1658 if(*pCursor->Start() != *pCursor->End())
1659 CreatePortion(xAlt, pGrammarResult, false, false);
1660 aStart = *pCursor->End();
1661 // now export the field - if there is any
1662 if(bField)
1664 *pCursor->GetMark() = *pCursor->GetPoint();
1665 GetSh()->Right(1, CRSR_SKIP_CELLS);
1666 CreatePortion(xAlt, pGrammarResult, true, false);
1667 aStart = *pCursor->End();
1670 // if a redline start then create a portion for it
1671 if(bRedline)
1673 *pCursor->GetMark() = *pCursor->GetPoint();
1674 // select until the end of the current redline
1675 const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ?
1676 aEnd.nContent.GetIndex() : aNextRedline.nRight;
1677 pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
1678 CreatePortion(xAlt, pGrammarResult, false, true);
1679 aStart = *pCursor->End();
1680 // search for next redline
1681 aNextRedline = lcl_FindNextDeletedRedline(
1682 rDeletedRedlines, aStart.nContent.GetIndex() );
1684 *pCursor->GetMark() = *pCursor->GetPoint();
1686 pCursor->SetMark();
1687 *pCursor->GetMark() = aStart;
1688 CreatePortion(xAlt, pGrammarResult, false, false);
1692 void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition )
1694 SwTextNode *pNode;
1695 SwWrongList *pWrong;
1696 SwNodeIndex aIdx = rErrorPosition.Start()->nNode;
1697 SwNodeIndex aEndIdx = rErrorPosition.Start()->nNode;
1698 sal_Int32 nStart = rErrorPosition.Start()->nContent.GetIndex();
1699 sal_Int32 nEnd = COMPLETE_STRING;
1700 while( aIdx <= aEndIdx )
1702 pNode = aIdx.GetNode().GetTextNode();
1703 if( pNode ) {
1704 if( aIdx == aEndIdx )
1705 nEnd = rErrorPosition.End()->nContent.GetIndex();
1706 pWrong = pNode->GetGrammarCheck();
1707 if( pWrong )
1708 pWrong->RemoveEntry( nStart, nEnd );
1709 pWrong = pNode->GetWrong();
1710 if( pWrong )
1711 pWrong->RemoveEntry( nStart, nEnd );
1712 SwTextFrame::repaintTextFrames( *pNode );
1714 ++aIdx;
1715 nStart = 0;
1719 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */