sc: factor out some more code
[LibreOffice.git] / sw / source / core / doc / docedt.cxx
blob92acd2a45fb0476fbccb182fff1fdb7adfd9e1c7
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 <fmtanchr.hxx>
21 #include <fmtcntnt.hxx>
22 #include <acorrect.hxx>
23 #include <IDocumentRedlineAccess.hxx>
24 #include <IDocumentLayoutAccess.hxx>
25 #include <IDocumentUndoRedo.hxx>
26 #include <docsh.hxx>
27 #include <docary.hxx>
28 #include <mdiexp.hxx>
29 #include <mvsave.hxx>
30 #include <redline.hxx>
31 #include <rolbck.hxx>
32 #include <rootfrm.hxx>
33 #include <splargs.hxx>
34 #include <swcrsr.hxx>
35 #include <txtfrm.hxx>
36 #include <unoflatpara.hxx>
37 #include <SwGrammarMarkUp.hxx>
38 #include <docedt.hxx>
39 #include <frmfmt.hxx>
40 #include <ndtxt.hxx>
41 #include <undobj.hxx>
42 #include <frameformats.hxx>
43 #include <unotxdoc.hxx>
45 #include <vector>
46 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
47 #include <osl/diagnose.h>
49 using namespace ::com::sun::star;
50 using namespace ::com::sun::star::linguistic2;
53 void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos,
54 const SwNode* pInsertPos, bool const isForceToStartPos)
56 SwPosition aPos(rStartPos);
57 for(const SaveFly & rSave : rArr)
59 // create new anchor
60 SwFrameFormat* pFormat = rSave.pFrameFormat;
61 SwFormatAnchor aAnchor( pFormat->GetAnchor() );
63 if (rSave.isAtInsertNode || isForceToStartPos)
65 if( pInsertPos != nullptr )
67 if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA)
69 assert(pInsertPos->GetContentNode());
70 aPos.Assign( *pInsertPos->GetContentNode(),
71 rSave.nContentIndex);
73 else
75 assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR);
76 aPos = rStartPos;
79 else
81 aPos.Assign(rStartPos.GetNode());
82 assert(aPos.GetNode().GetContentNode());
85 else
87 aPos.Assign(rStartPos.GetNodeIndex() + rSave.nNdDiff);
88 assert(aPos.GetNode().GetContentNode());
89 aPos.SetContent(
90 rSave.nNdDiff == SwNodeOffset(0)
91 ? rStartPos.GetContentIndex() + rSave.nContentIndex
92 : rSave.nContentIndex);
95 aAnchor.SetAnchor( &aPos );
96 pFormat->GetDoc()->GetSpzFrameFormats()->push_back(static_cast<sw::SpzFrameFormat*>(pFormat));
97 // SetFormatAttr should call Modify() and add it to the node
98 pFormat->SetFormatAttr( aAnchor );
99 SwContentNode* pCNd = aPos.GetNode().GetContentNode();
100 if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr))
101 pFormat->MakeFrames();
103 sw::CheckAnchoredFlyConsistency(rStartPos.GetNode().GetDoc());
106 void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr )
108 sw::SpzFrameFormats& rSpzs = *rRg.aStart.GetNode().GetDoc().GetSpzFrameFormats();
109 sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0;
110 while (n < rSpzs.size())
112 auto pSpz = rSpzs[n];
113 SwFormatAnchor const*const pAnchor = &pSpz->GetAnchor();
114 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
115 if (pAnchorNode &&
116 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
117 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
118 rRg.aStart <= *pAnchorNode && *pAnchorNode < rRg.aEnd.GetNode() )
120 SaveFly aSave( pAnchorNode->GetIndex() - rRg.aStart.GetIndex(),
121 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
122 ? pAnchor->GetAnchorContentOffset()
123 : 0,
124 pSpz, false );
125 rArr.push_back( aSave );
126 pSpz->DelFrames();
127 // set a dummy anchor position to maintain anchoring invariants
128 SwFormatAnchor aAnchor( pSpz->GetAnchor() );
129 aAnchor.SetAnchor(nullptr);
130 pSpz->SetFormatAttr(aAnchor);
131 rSpzs.erase( rSpzs.begin() + n );
132 continue;
134 ++n;
136 sw::CheckAnchoredFlyConsistency(rRg.aStart.GetNode().GetDoc());
139 void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos,
140 SaveFlyArr& rArr, bool bMoveAllFlys, SwHistory *const pHistory)
142 sw::SpzFrameFormats& rFormats = *rPam.GetPoint()->GetNode().GetDoc().GetSpzFrameFormats();
143 sw::SpzFrameFormat* pFormat;
144 const SwFormatAnchor* pAnchor;
146 const SwPosition* pPos = rPam.Start();
147 const SwNode& rSttNd = pPos->GetNode();
149 SwPosition atParaEnd(*rPam.End());
150 if (bMoveAllFlys)
152 assert(!rPam.End()->GetNode().IsTextNode() // can be table end-node
153 || rPam.End()->GetContentIndex() == rPam.End()->GetNode().GetTextNode()->Len());
154 atParaEnd.Adjust(SwNodeOffset(1));
157 for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0; n < rFormats.size(); ++n )
159 pFormat = rFormats[n];
160 pAnchor = &pFormat->GetAnchor();
161 const SwPosition* pAPos = pAnchor->GetContentAnchor();
162 const SwNodeIndex* pContentIdx;
163 if (pAPos &&
164 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
165 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
166 // do not move if the InsPos is in the ContentArea of the Fly
167 ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) ||
168 (*pContentIdx >= rInsPos.GetNode() ||
169 rInsPos.GetNode() >= *pContentIdx->GetNode().EndOfSectionNode())))
171 bool bInsPos = false;
173 if ( (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()
174 && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End()))
175 || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
176 && IsSelectFrameAnchoredAtPara(*pAPos, *rPam.Start(), atParaEnd,
177 bMoveAllFlys
178 ? DelContentType::CheckNoCntnt|DelContentType::AllMask
179 : DelContentType::AllMask))
180 || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
181 && (bInsPos = (rInsPos.GetNode() == pAPos->GetNode())))
182 || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()
183 && (bInsPos = (rInsPos == *pAPos))))
185 if (pHistory)
187 pHistory->AddChangeFlyAnchor(*pFormat);
189 SaveFly aSave( pAPos->GetNodeIndex() - rSttNd.GetIndex(),
190 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
191 ? (pAPos->GetNode() == rSttNd)
192 ? pAPos->GetContentIndex() - rPam.Start()->GetContentIndex()
193 : pAPos->GetContentIndex()
194 : 0,
195 pFormat, bInsPos );
196 rArr.push_back( aSave );
197 pFormat->DelFrames();
198 // set a dummy anchor position to maintain anchoring invariants
199 SwFormatAnchor aAnchor( pFormat->GetAnchor() );
200 aAnchor.SetAnchor(nullptr);
201 pFormat->SetFormatAttr(aAnchor);
202 rFormats.erase( rFormats.begin() + n-- );
206 sw::CheckAnchoredFlyConsistency(rPam.GetPoint()->GetNode().GetDoc());
209 /// Delete and move all Flys at the paragraph, that are within the selection.
210 /// If there is a Fly at the SPoint, it is moved onto the Mark.
211 void DelFlyInRange( SwNode& rMkNd,
212 SwNode& rPtNd,
213 std::optional<sal_Int32> oMkContentIdx, std::optional<sal_Int32> oPtContentIdx)
215 assert(oMkContentIdx.has_value() == oPtContentIdx.has_value());
216 SwPosition const point(oPtContentIdx
217 ? SwPosition(rPtNd, rPtNd.GetContentNode(), *oPtContentIdx)
218 : SwPosition(rPtNd));
219 SwPosition const mark(oPtContentIdx
220 ? SwPosition(rMkNd, rMkNd.GetContentNode(), *oMkContentIdx)
221 : SwPosition(rMkNd));
222 SwPosition const& rStart = mark <= point ? mark : point;
223 SwPosition const& rEnd = mark <= point ? point : mark;
225 SwDoc& rDoc = rMkNd.GetDoc();
226 sw::SpzFrameFormats& rTable = *rDoc.GetSpzFrameFormats();
227 for ( auto i = rTable.size(); i; )
229 sw::SpzFrameFormat* pFormat = rTable[--i];
230 const SwFormatAnchor &rAnch = pFormat->GetAnchor();
231 SwPosition const*const pAPos = rAnch.GetContentAnchor();
232 if (pAPos &&
233 (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA)
234 && IsSelectFrameAnchoredAtPara(*pAPos, rStart, rEnd, oPtContentIdx
235 ? DelContentType::AllMask|DelContentType::WriterfilterHack
236 : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt))
237 || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
238 && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, oPtContentIdx
239 ? DelContentType::AllMask|DelContentType::WriterfilterHack
240 : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt))))
242 // If the Fly is deleted, all Flys in its content have to be deleted too.
243 const SwFormatContent &rContent = pFormat->GetContent();
244 // But only fly formats own their content, not draw formats.
245 if (rContent.GetContentIdx() && pFormat->Which() == RES_FLYFRMFMT)
247 DelFlyInRange( rContent.GetContentIdx()->GetNode(),
248 *rContent.GetContentIdx()->
249 GetNode().EndOfSectionNode() );
250 // Position could have been moved!
251 if (i > rTable.size())
252 i = rTable.size();
253 else if (i == rTable.size() || pFormat != rTable[i])
254 i = std::distance(rTable.begin(), rTable.find(pFormat));
257 rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
259 // DelLayoutFormat can also trigger the deletion of objects.
260 if (i > rTable.size())
261 i = rTable.size();
266 // #i59534: Redo of insertion of multiple text nodes runs into trouble
267 // because of unnecessary expanded redlines
268 // From now on this class saves the redline positions of all redlines which ends exact at the
269 // insert position (node _and_ content index)
270 SaveRedlEndPosForRestore::SaveRedlEndPosForRestore( const SwNode& rInsIdx, sal_Int32 nCnt )
271 : mnSaveContent( nCnt )
273 const SwDoc& rDest = rInsIdx.GetDoc();
274 if( rDest.getIDocumentRedlineAccess().GetRedlineTable().empty() )
275 return;
277 SwRedlineTable::size_type nFndPos;
278 const SwPosition* pEnd;
279 SwPosition aSrcPos( rInsIdx, rInsIdx.GetContentNode(), nCnt );
280 rDest.getIDocumentRedlineAccess().GetRedline( aSrcPos, &nFndPos );
281 const SwRangeRedline* pRedl;
282 while( nFndPos--
283 && *( pEnd = ( pRedl = rDest.getIDocumentRedlineAccess().GetRedlineTable()[ nFndPos ] )->End() ) == aSrcPos
284 && *pRedl->Start() < aSrcPos )
286 if( !moSaveIndex )
288 moSaveIndex.emplace( rInsIdx, -1 );
290 mvSavArr.push_back( const_cast<SwPosition*>(pEnd) );
294 SaveRedlEndPosForRestore::~SaveRedlEndPosForRestore()
296 moSaveIndex.reset();
299 void SaveRedlEndPosForRestore::Restore()
301 if (mvSavArr.empty())
302 return;
303 ++(*moSaveIndex);
304 SwContentNode* pNode = moSaveIndex->GetNode().GetContentNode();
305 // If there's no content node at the remembered position, we will not restore the old position
306 // This may happen if a table (or section?) will be inserted.
307 if( pNode )
309 SwPosition aPos( *moSaveIndex, pNode, mnSaveContent );
310 for( auto n = mvSavArr.size(); n; )
311 *mvSavArr[ --n ] = aPos;
315 /// Convert list of ranges of whichIds to a corresponding list of whichIds
316 static std::vector<sal_uInt16> lcl_RangesToVector(const WhichRangesContainer& pRanges)
318 std::vector<sal_uInt16> aResult;
320 for(const WhichPair& rPair : pRanges)
322 for (sal_uInt16 j = rPair.first; j <= rPair.second; j++)
323 aResult.push_back(j);
326 return aResult;
329 void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev )
331 rJoinText = false;
332 rJoinPrev = false;
333 if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
334 return;
336 auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*
337 SwTextNode *pSttNd = pStart->GetNode().GetTextNode();
338 if( !pSttNd )
339 return;
341 SwTextNode *pEndNd = pEnd->GetNode().GetTextNode();
342 rJoinText = nullptr != pEndNd;
343 if( !rJoinText )
344 return;
346 bool bExchange = pStart == rPam.GetPoint();
347 if( !pStart->GetContentIndex() &&
348 pEndNd->GetText().getLength() != pEnd->GetContentIndex())
349 bExchange = !bExchange;
350 if( bExchange )
351 rPam.Exchange();
352 rJoinPrev = rPam.GetPoint() == pStart;
353 OSL_ENSURE( !pStart->GetContentIndex() &&
354 pEndNd->GetText().getLength() != pEnd->GetContentIndex()
355 ? (rPam.GetPoint()->GetNode() < rPam.GetMark()->GetNode())
356 : (rPam.GetPoint()->GetNode() > rPam.GetMark()->GetNode()),
357 "sw_GetJoinFlags");
360 bool sw_JoinText( SwPaM& rPam, bool bJoinPrev )
362 SwNodeIndex aIdx( rPam.GetPoint()->GetNode() );
363 SwTextNode *pTextNd = aIdx.GetNode().GetTextNode();
364 SwNodeIndex aOldIdx( aIdx );
365 SwTextNode *pOldTextNd = pTextNd;
367 if( pTextNd && pTextNd->CanJoinNext( &aIdx ) )
369 SwDoc& rDoc = rPam.GetDoc();
370 if( bJoinPrev )
372 // We do not need to handle xmlids in this case, because
373 // it is only invoked if one paragraph is/becomes completely empty
374 // (see sw_GetJoinFlags)
376 // If PageBreaks are deleted/set, it must not be added to the Undo history!
377 // Also, deleting the Node is not added to the Undo history!
378 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
380 /* PageBreaks, PageDesc, ColumnBreaks */
381 // If we need to change something about the logic to copy the PageBreaks,
382 // PageDesc, etc. we also have to change SwUndoDelete.
383 // There, we copy the AUTO PageBreak from the GetMarkNode!
385 /* The MarkNode */
386 pTextNd = aIdx.GetNode().GetTextNode();
387 if (pTextNd->HasSwAttrSet())
389 if( SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_BREAK, false) )
390 pTextNd->ResetAttr( RES_BREAK );
391 if( pTextNd->HasSwAttrSet() &&
392 SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_PAGEDESC, false ) )
393 pTextNd->ResetAttr( RES_PAGEDESC );
396 /* The PointNode */
397 if( pOldTextNd->HasSwAttrSet() )
399 const SfxPoolItem* pItem;
400 SfxItemSet aSet( rDoc.GetAttrPool(), aBreakSetRange );
401 const SfxItemSet* pSet = pOldTextNd->GetpSwAttrSet();
402 if( SfxItemState::SET == pSet->GetItemState( RES_BREAK,
403 false, &pItem ) )
404 aSet.Put( *pItem );
405 if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
406 false, &pItem ) )
407 aSet.Put( *pItem );
408 if( aSet.Count() )
409 pTextNd->SetAttr( aSet );
411 pOldTextNd->FormatToTextAttr( pTextNd );
413 const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
414 pContentStore->Save(rDoc, aOldIdx.GetIndex(), SAL_MAX_INT32);
416 SwContentIndex aAlphaIdx(pTextNd);
417 pOldTextNd->CutText( pTextNd, aAlphaIdx, SwContentIndex(pOldTextNd),
418 pOldTextNd->Len() );
419 SwPosition aAlphaPos( aIdx, aAlphaIdx );
420 rDoc.CorrRel( rPam.GetPoint()->GetNode(), aAlphaPos, 0, true );
422 // move all Bookmarks/TOXMarks
423 if( !pContentStore->Empty() )
424 pContentStore->Restore( rDoc, aIdx.GetIndex() );
426 // If the passed PaM is not in the Cursor ring,
427 // treat it separately (e.g. when it's being called from AutoFormat)
428 if( pOldTextNd == rPam.GetBound().GetContentNode() )
429 rPam.GetBound() = aAlphaPos;
430 if( pOldTextNd == rPam.GetBound( false ).GetContentNode() )
431 rPam.GetBound( false ) = std::move(aAlphaPos);
433 // delete the Node, at last!
434 SwNode::Merge const eOldMergeFlag(pOldTextNd->GetRedlineMergeFlag());
435 if (eOldMergeFlag == SwNode::Merge::First
436 && !pTextNd->IsCreateFrameWhenHidingRedlines())
438 sw::MoveDeletedPrevFrames(*pOldTextNd, *pTextNd);
440 rDoc.GetNodes().Delete( aOldIdx );
441 sw::CheckResetRedlineMergeFlag(*pTextNd,
442 eOldMergeFlag == SwNode::Merge::NonFirst
443 ? sw::Recreate::Predecessor
444 : sw::Recreate::No);
446 else
448 SwTextNode* pDelNd = aIdx.GetNode().GetTextNode();
449 if( pTextNd->Len() )
450 pDelNd->FormatToTextAttr( pTextNd );
451 else
453 /* This case was missed:
455 <something></something> <-- pTextNd
456 <other>ccc</other> <-- pDelNd
458 <something> and <other> are paragraph
459 attributes. The attribute <something> stayed if not
460 overwritten by an attribute in "ccc". Fixed by
461 first resetting all character attributes in first
462 paragraph (pTextNd).
464 std::vector<sal_uInt16> aShorts =
465 lcl_RangesToVector(aCharFormatSetRange);
466 pTextNd->ResetAttr(aShorts);
468 if( pDelNd->HasSwAttrSet() )
470 // only copy the character attributes
471 SfxItemSet aTmpSet( rDoc.GetAttrPool(), aCharFormatSetRange );
472 aTmpSet.Put( *pDelNd->GetpSwAttrSet() );
473 pTextNd->SetAttr( aTmpSet );
477 rDoc.CorrRel( aIdx.GetNode(), *rPam.GetPoint(), 0, true );
478 // #i100466# adjust given <rPam>, if it does not belong to the cursors
479 if ( pDelNd == rPam.GetBound().GetContentNode() )
481 rPam.GetBound().Assign( *pTextNd );
483 if( pDelNd == rPam.GetBound( false ).GetContentNode() )
485 rPam.GetBound( false ).Assign( *pTextNd );
487 pTextNd->JoinNext();
489 return true;
491 else return false;
494 static void lcl_syncGrammarError( SwTextNode &rTextNode, linguistic2::ProofreadingResult& rResult,
495 const ModelToViewHelper &rConversionMap )
497 if( rTextNode.IsGrammarCheckDirty() )
498 return;
499 SwGrammarMarkUp* pWrong = rTextNode.GetGrammarCheck();
500 linguistic2::SingleProofreadingError* pArray = rResult.aErrors.getArray();
501 sal_uInt16 j = 0;
502 if( pWrong )
504 for( sal_Int32 i = 0; i < rResult.aErrors.getLength(); ++i )
506 const linguistic2::SingleProofreadingError &rError = rResult.aErrors[i];
507 const sal_Int32 nStart = rConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos;
508 const sal_Int32 nEnd = rConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos;
509 if( i != j )
510 pArray[j] = pArray[i];
511 if( pWrong->LookForEntry( nStart, nEnd ) )
512 ++j;
515 if( rResult.aErrors.getLength() > j )
516 rResult.aErrors.realloc( j );
519 uno::Any SwDoc::Spell( SwPaM& rPaM,
520 uno::Reference< XSpellChecker1 > const &xSpeller,
521 sal_uInt16* pPageCnt, sal_uInt16* pPageSt,
522 bool bGrammarCheck,
523 SwRootFrame const*const pLayout,
524 SwConversionArgs *pConvArgs ) const
526 SwPosition* const pSttPos = rPaM.Start();
527 SwPosition* const pEndPos = rPaM.End();
529 std::unique_ptr<SwSpellArgs> pSpellArgs;
530 if (pConvArgs)
532 pConvArgs->SetStart(*pSttPos);
533 pConvArgs->SetEnd(*pEndPos);
535 else
536 pSpellArgs.reset(new SwSpellArgs( xSpeller, *pSttPos, *pEndPos, bGrammarCheck ));
538 SwNodeOffset nCurrNd = pSttPos->GetNodeIndex();
539 SwNodeOffset nEndNd = pEndPos->GetNodeIndex();
541 uno::Any aRet;
542 if( nCurrNd <= nEndNd )
544 SwContentFrame* pContentFrame;
545 bool bGoOn = true;
546 while( bGoOn )
548 SwNode* pNd = GetNodes()[ nCurrNd ];
549 switch( pNd->GetNodeType() )
551 case SwNodeType::Text:
552 if( nullptr != ( pContentFrame = pNd->GetTextNode()->getLayoutFrame( getIDocumentLayoutAccess().GetCurrentLayout() )) )
554 // skip protected and hidden Cells and Flys
555 if( pContentFrame->IsProtected() )
557 nCurrNd = pNd->EndOfSectionIndex();
559 else if( !pContentFrame->IsHiddenNow() )
561 if( pPageCnt && *pPageCnt && pPageSt )
563 sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum();
564 if( !*pPageSt )
566 *pPageSt = nPageNr;
567 if( *pPageCnt < *pPageSt )
568 *pPageCnt = *pPageSt;
570 tools::Long nStat;
571 if( nPageNr >= *pPageSt )
572 nStat = nPageNr - *pPageSt + 1;
573 else
574 nStat = nPageNr + *pPageCnt - *pPageSt + 1;
575 ::SetProgressState( nStat, GetDocShell() );
577 //Spell() changes the pSpellArgs in case an error is found
578 sal_Int32 nBeginGrammarCheck = 0;
579 sal_Int32 nEndGrammarCheck = 0;
580 if( pSpellArgs && pSpellArgs->bIsGrammarCheck)
582 nBeginGrammarCheck = &pSpellArgs->pStartPos->GetNode() == pNd ? pSpellArgs->pStartPos->GetContentIndex() : 0;
583 // if grammar checking starts inside of a sentence the start position has to be adjusted
584 if( nBeginGrammarCheck )
586 SwContentIndex aStartIndex( pNd->GetTextNode(), nBeginGrammarCheck );
587 SwPosition aStart( *pNd, aStartIndex );
588 SwCursor aCursor(aStart, nullptr);
589 SwPosition aOrigPos = *aCursor.GetPoint();
590 aCursor.GoSentence( SwCursor::START_SENT );
591 if( aOrigPos != *aCursor.GetPoint() )
593 nBeginGrammarCheck = aCursor.GetPoint()->GetContentIndex();
596 nEndGrammarCheck = (&pSpellArgs->pEndPos->GetNode() == pNd)
597 ? pSpellArgs->pEndPos->GetContentIndex()
598 : pNd->GetTextNode()
599 ->GetText().getLength();
602 sal_Int32 nSpellErrorPosition = pNd->GetTextNode()->GetText().getLength();
603 if( (!pConvArgs && pNd->GetTextNode()->Spell( pSpellArgs.get() )) ||
604 ( pConvArgs && pNd->GetTextNode()->Convert( *pConvArgs )))
606 // Cancel and remember position
607 if( pSpellArgs )
608 nSpellErrorPosition = pSpellArgs->pStartPos->GetContentIndex() > pSpellArgs->pEndPos->GetContentIndex() ?
609 pSpellArgs->pEndPos->GetContentIndex() :
610 pSpellArgs->pStartPos->GetContentIndex();
611 if( nCurrNd != nEndNd )
613 pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex());
614 pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex());
615 nCurrNd = nEndNd;
619 if( pSpellArgs && pSpellArgs->bIsGrammarCheck )
621 uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() );
622 if (xGCIterator.is())
624 if (const SwDocShell* pShell = GetDocShell())
626 rtl::Reference< SwXTextDocument > xDoc = pShell->GetBaseModel();
627 // Expand the string:
628 const ModelToViewHelper aConversionMap(*pNd->GetTextNode(), pLayout);
629 const OUString& aExpandText = aConversionMap.getViewText();
631 // get XFlatParagraph to use...
632 uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNd->GetTextNode(), aExpandText, aConversionMap );
634 // get error position of cursor in XFlatParagraph
635 linguistic2::ProofreadingResult aResult;
636 bool bGrammarErrors;
639 aConversionMap.ConvertToViewPosition( nBeginGrammarCheck );
640 aResult = xGCIterator->checkSentenceAtPosition(
641 cppu::getXWeak(xDoc.get()), xFlatPara, aExpandText, lang::Locale(), nBeginGrammarCheck, -1, -1 );
643 lcl_syncGrammarError( *pNd->GetTextNode(), aResult, aConversionMap );
645 // get suggestions to use for the specific error position
646 bGrammarErrors = aResult.aErrors.hasElements();
647 // if grammar checking doesn't have any progress then quit
648 if( aResult.nStartOfNextSentencePosition <= nBeginGrammarCheck )
649 break;
650 // prepare next iteration
651 nBeginGrammarCheck = aResult.nStartOfNextSentencePosition;
653 while( nSpellErrorPosition > aResult.nBehindEndOfSentencePosition && !bGrammarErrors && aResult.nBehindEndOfSentencePosition < nEndGrammarCheck );
655 if( bGrammarErrors && nSpellErrorPosition >= aResult.nBehindEndOfSentencePosition )
657 aRet <<= aResult;
658 //put the cursor to the current error
659 const linguistic2::SingleProofreadingError &rError = aResult.aErrors[0];
660 pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex());
661 pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex());
662 pSpellArgs->pStartPos->Assign(*pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos );
663 pSpellArgs->pEndPos->Assign(*pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos );
664 nCurrNd = nEndNd;
671 break;
672 case SwNodeType::Section:
673 if( static_cast<SwSectionNode*>(pNd)->GetSection().IsProtect() ||
674 static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() )
675 nCurrNd = pNd->EndOfSectionIndex();
676 break;
677 case SwNodeType::End:
679 break;
681 default: break;
684 bGoOn = nCurrNd < nEndNd;
685 ++nCurrNd;
689 if( !aRet.hasValue() )
691 if (pConvArgs)
692 aRet <<= pConvArgs->aConvText;
693 else
694 aRet <<= pSpellArgs->xSpellAlt;
697 return aRet;
700 namespace {
702 class SwHyphArgs : public SwInterHyphInfo
704 SwNodeIndex m_aNodeIdx;
705 const SwNode *m_pStart;
706 const SwNode *m_pEnd;
707 sal_uInt16 *m_pPageCnt;
708 sal_uInt16 *m_pPageSt;
710 sal_Int32 m_nPamStart;
711 sal_Int32 m_nPamLen;
713 public:
714 SwHyphArgs( const SwPaM *pPam, const Point &rPoint,
715 sal_uInt16* pPageCount, sal_uInt16* pPageStart );
716 void SetPam( SwPaM *pPam ) const;
717 void SetNode( SwNode& rNew ) { m_aNodeIdx.Assign(rNew); }
718 inline void SetRange( const SwNode *pNew );
719 void NextNode() { ++m_aNodeIdx; }
720 sal_uInt16 *GetPageCnt() { return m_pPageCnt; }
721 sal_uInt16 *GetPageSt() { return m_pPageSt; }
726 SwHyphArgs::SwHyphArgs( const SwPaM *pPam, const Point &rCursorPos,
727 sal_uInt16* pPageCount, sal_uInt16* pPageStart )
728 : SwInterHyphInfo( rCursorPos ), m_aNodeIdx(pPam->GetPoint()->GetNode()),
729 m_pPageCnt( pPageCount ), m_pPageSt( pPageStart )
731 // The following constraints have to be met:
732 // 1) there is at least one Selection
733 // 2) SPoint() == Start()
734 OSL_ENSURE( pPam->HasMark(), "SwDoc::Hyphenate: blowing in the wind");
735 OSL_ENSURE( *pPam->GetPoint() <= *pPam->GetMark(),
736 "SwDoc::Hyphenate: New York, New York");
738 const SwPosition *pPoint = pPam->GetPoint();
740 // Set start
741 m_pStart = pPoint->GetNode().GetTextNode();
742 m_nPamStart = pPoint->GetContentIndex();
744 // Set End and Length
745 const SwPosition *pMark = pPam->GetMark();
746 m_pEnd = pMark->GetNode().GetTextNode();
747 m_nPamLen = pMark->GetContentIndex();
748 if( pPoint->GetNode() == pMark->GetNode() )
749 m_nPamLen = m_nPamLen - pPoint->GetContentIndex();
752 inline void SwHyphArgs::SetRange( const SwNode *pNew )
754 m_nStart = m_pStart == pNew ? m_nPamStart : 0;
755 m_nEnd = m_pEnd == pNew ? m_nPamStart + m_nPamLen : SAL_MAX_INT32;
758 void SwHyphArgs::SetPam( SwPaM *pPam ) const
760 pPam->GetPoint()->Assign( m_aNodeIdx, m_nWordStart );
761 pPam->GetMark()->Assign( m_aNodeIdx, m_nWordStart + m_nWordLen );
764 // Returns true if we can proceed.
765 static bool lcl_HyphenateNode( SwNode* pNd, void* pArgs )
767 // Hyphenate returns true if there is a hyphenation point and sets pPam
768 SwTextNode *pNode = pNd->GetTextNode();
769 SwHyphArgs *pHyphArgs = static_cast<SwHyphArgs*>(pArgs);
770 if( pNode )
772 // sw_redlinehide: this will be called once per node for merged nodes;
773 // the fully deleted ones won't have frames so are skipped.
774 SwContentFrame* pContentFrame = pNode->getLayoutFrame( pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
775 if( pContentFrame && !pContentFrame->IsHiddenNow() )
777 sal_uInt16 *pPageSt = pHyphArgs->GetPageSt();
778 sal_uInt16 *pPageCnt = pHyphArgs->GetPageCnt();
779 if( pPageCnt && *pPageCnt && pPageSt )
781 sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum();
782 if( !*pPageSt )
784 *pPageSt = nPageNr;
785 if( *pPageCnt < *pPageSt )
786 *pPageCnt = *pPageSt;
788 tools::Long nStat = nPageNr >= *pPageSt ? nPageNr - *pPageSt + 1
789 : nPageNr + *pPageCnt - *pPageSt + 1;
790 ::SetProgressState( nStat, pNode->GetDoc().GetDocShell() );
792 pHyphArgs->SetRange( pNd );
793 if( pNode->Hyphenate( *pHyphArgs ) )
795 pHyphArgs->SetNode( *pNd );
796 return false;
800 pHyphArgs->NextNode();
801 return true;
804 uno::Reference< XHyphenatedWord > SwDoc::Hyphenate(
805 SwPaM *pPam, const Point &rCursorPos,
806 sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
808 OSL_ENSURE(this == &pPam->GetDoc(), "SwDoc::Hyphenate: strangers in the night");
810 if( *pPam->GetPoint() > *pPam->GetMark() )
811 pPam->Exchange();
813 SwHyphArgs aHyphArg( pPam, rCursorPos, pPageCnt, pPageSt );
814 SwNodeIndex aTmpIdx( pPam->GetMark()->GetNode(), 1 );
815 GetNodes().ForEach( pPam->GetPoint()->GetNode(), aTmpIdx.GetNode(),
816 lcl_HyphenateNode, &aHyphArg );
817 aHyphArg.SetPam( pPam );
818 return aHyphArg.GetHyphWord(); // will be set by lcl_HyphenateNode
821 // Save the current values to add them as automatic entries to AutoCorrect.
822 void SwDoc::SetAutoCorrExceptWord( std::unique_ptr<SwAutoCorrExceptWord> pNew )
824 mpACEWord = std::move(pNew);
827 void SwDoc::DeleteAutoCorrExceptWord()
829 mpACEWord.reset();
832 void SwDoc::CountWords( const SwPaM& rPaM, SwDocStat& rStat )
834 // This is a modified version of SwDoc::TransliterateText
835 auto [pStart, pEnd] = rPaM.StartEnd(); // SwPosition*
837 const SwNodeOffset nSttNd = pStart->GetNodeIndex();
838 const SwNodeOffset nEndNd = pEnd->GetNodeIndex();
840 const sal_Int32 nSttCnt = pStart->GetContentIndex();
841 const sal_Int32 nEndCnt = pEnd->GetContentIndex();
843 const SwTextNode* pTNd = pStart->GetNode().GetTextNode();
844 if( pStart == pEnd && pTNd ) // no region ?
846 // do nothing
847 return;
850 if( nSttNd != nEndNd )
852 SwNodeIndex aIdx( pStart->GetNode() );
853 if( nSttCnt )
855 ++aIdx;
856 if( pTNd )
857 pTNd->CountWords( rStat, nSttCnt, pTNd->GetText().getLength() );
860 for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
861 if( nullptr != ( pTNd = aIdx.GetNode().GetTextNode() ))
862 pTNd->CountWords( rStat, 0, pTNd->GetText().getLength() );
864 if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
865 pTNd->CountWords( rStat, 0, nEndCnt );
867 else if( pTNd && nSttCnt < nEndCnt )
868 pTNd->CountWords( rStat, nSttCnt, nEndCnt );
872 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */