android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / doc / docedt.cxx
blobc78d8e18b6333f79fff5b05fbd1aa022d759ac38
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>
44 #include <vector>
45 #include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
46 #include <osl/diagnose.h>
48 using namespace ::com::sun::star;
49 using namespace ::com::sun::star::linguistic2;
50 using namespace ::com::sun::star::i18n;
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 for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0; n < rSpzs.size(); ++n )
111 auto pSpz = rSpzs[n];
112 SwFormatAnchor const*const pAnchor = &pSpz->GetAnchor();
113 SwNode const*const pAnchorNode = pAnchor->GetAnchorNode();
114 if (pAnchorNode &&
115 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
116 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
117 rRg.aStart <= *pAnchorNode && *pAnchorNode < rRg.aEnd.GetNode() )
119 SaveFly aSave( pAnchorNode->GetIndex() - rRg.aStart.GetIndex(),
120 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
121 ? pAnchor->GetAnchorContentOffset()
122 : 0,
123 pSpz, false );
124 rArr.push_back( aSave );
125 pSpz->DelFrames();
126 // set a dummy anchor position to maintain anchoring invariants
127 SwFormatAnchor aAnchor( pSpz->GetAnchor() );
128 aAnchor.SetAnchor(nullptr);
129 pSpz->SetFormatAttr(aAnchor);
130 rSpzs.erase( rSpzs.begin() + n-- );
133 sw::CheckAnchoredFlyConsistency(rRg.aStart.GetNode().GetDoc());
136 void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos,
137 SaveFlyArr& rArr, bool bMoveAllFlys, SwHistory *const pHistory)
139 sw::SpzFrameFormats& rFormats = *rPam.GetPoint()->GetNode().GetDoc().GetSpzFrameFormats();
140 sw::SpzFrameFormat* pFormat;
141 const SwFormatAnchor* pAnchor;
143 const SwPosition* pPos = rPam.Start();
144 const SwNode& rSttNd = pPos->GetNode();
146 SwPosition atParaEnd(*rPam.End());
147 if (bMoveAllFlys)
149 assert(!rPam.End()->GetNode().IsTextNode() // can be table end-node
150 || rPam.End()->GetContentIndex() == rPam.End()->GetNode().GetTextNode()->Len());
151 atParaEnd.Adjust(SwNodeOffset(1));
154 for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0; n < rFormats.size(); ++n )
156 pFormat = rFormats[n];
157 pAnchor = &pFormat->GetAnchor();
158 const SwPosition* pAPos = pAnchor->GetContentAnchor();
159 const SwNodeIndex* pContentIdx;
160 if (pAPos &&
161 ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) ||
162 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) &&
163 // do not move if the InsPos is in the ContentArea of the Fly
164 ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) ||
165 (*pContentIdx >= rInsPos.GetNode() ||
166 rInsPos.GetNode() >= *pContentIdx->GetNode().EndOfSectionNode())))
168 bool bInsPos = false;
170 if ( (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()
171 && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End()))
172 || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
173 && IsSelectFrameAnchoredAtPara(*pAPos, *rPam.Start(), atParaEnd,
174 bMoveAllFlys
175 ? DelContentType::CheckNoCntnt|DelContentType::AllMask
176 : DelContentType::AllMask))
177 || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()
178 && (bInsPos = (rInsPos.GetNode() == pAPos->GetNode())))
179 || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()
180 && (bInsPos = (rInsPos == *pAPos))))
182 if (pHistory)
184 pHistory->AddChangeFlyAnchor(*pFormat);
186 SaveFly aSave( pAPos->GetNodeIndex() - rSttNd.GetIndex(),
187 (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())
188 ? (pAPos->GetNode() == rSttNd)
189 ? pAPos->GetContentIndex() - rPam.Start()->GetContentIndex()
190 : pAPos->GetContentIndex()
191 : 0,
192 pFormat, bInsPos );
193 rArr.push_back( aSave );
194 pFormat->DelFrames();
195 // set a dummy anchor position to maintain anchoring invariants
196 SwFormatAnchor aAnchor( pFormat->GetAnchor() );
197 aAnchor.SetAnchor(nullptr);
198 pFormat->SetFormatAttr(aAnchor);
199 rFormats.erase( rFormats.begin() + n-- );
203 sw::CheckAnchoredFlyConsistency(rPam.GetPoint()->GetNode().GetDoc());
206 /// Delete and move all Flys at the paragraph, that are within the selection.
207 /// If there is a Fly at the SPoint, it is moved onto the Mark.
208 void DelFlyInRange( SwNode& rMkNd,
209 SwNode& rPtNd,
210 std::optional<sal_Int32> oMkContentIdx, std::optional<sal_Int32> oPtContentIdx)
212 assert(oMkContentIdx.has_value() == oPtContentIdx.has_value());
213 SwPosition const point(oPtContentIdx
214 ? SwPosition(rPtNd, rPtNd.GetContentNode(), *oPtContentIdx)
215 : SwPosition(rPtNd));
216 SwPosition const mark(oPtContentIdx
217 ? SwPosition(rMkNd, rMkNd.GetContentNode(), *oMkContentIdx)
218 : SwPosition(rMkNd));
219 SwPosition const& rStart = mark <= point ? mark : point;
220 SwPosition const& rEnd = mark <= point ? point : mark;
222 SwDoc& rDoc = rMkNd.GetDoc();
223 sw::SpzFrameFormats& rTable = *rDoc.GetSpzFrameFormats();
224 for ( auto i = rTable.size(); i; )
226 sw::SpzFrameFormat* pFormat = rTable[--i];
227 const SwFormatAnchor &rAnch = pFormat->GetAnchor();
228 SwPosition const*const pAPos = rAnch.GetContentAnchor();
229 if (pAPos &&
230 (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA)
231 && IsSelectFrameAnchoredAtPara(*pAPos, rStart, rEnd, oPtContentIdx
232 ? DelContentType::AllMask|DelContentType::WriterfilterHack
233 : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt))
234 || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
235 && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, oPtContentIdx
236 ? DelContentType::AllMask|DelContentType::WriterfilterHack
237 : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt))))
239 // If the Fly is deleted, all Flys in its content have to be deleted too.
240 const SwFormatContent &rContent = pFormat->GetContent();
241 // But only fly formats own their content, not draw formats.
242 if (rContent.GetContentIdx() && pFormat->Which() == RES_FLYFRMFMT)
244 DelFlyInRange( rContent.GetContentIdx()->GetNode(),
245 *rContent.GetContentIdx()->
246 GetNode().EndOfSectionNode() );
247 // Position could have been moved!
248 if (i > rTable.size())
249 i = rTable.size();
250 else if (i == rTable.size() || pFormat != rTable[i])
251 i = std::distance(rTable.begin(), rTable.find(pFormat));
254 rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
256 // DelLayoutFormat can also trigger the deletion of objects.
257 if (i > rTable.size())
258 i = rTable.size();
263 // #i59534: Redo of insertion of multiple text nodes runs into trouble
264 // because of unnecessary expanded redlines
265 // From now on this class saves the redline positions of all redlines which ends exact at the
266 // insert position (node _and_ content index)
267 SaveRedlEndPosForRestore::SaveRedlEndPosForRestore( const SwNode& rInsIdx, sal_Int32 nCnt )
268 : mnSaveContent( nCnt )
270 const SwDoc& rDest = rInsIdx.GetDoc();
271 if( rDest.getIDocumentRedlineAccess().GetRedlineTable().empty() )
272 return;
274 SwRedlineTable::size_type nFndPos;
275 const SwPosition* pEnd;
276 SwPosition aSrcPos( rInsIdx, rInsIdx.GetContentNode(), nCnt );
277 rDest.getIDocumentRedlineAccess().GetRedline( aSrcPos, &nFndPos );
278 const SwRangeRedline* pRedl;
279 while( nFndPos--
280 && *( pEnd = ( pRedl = rDest.getIDocumentRedlineAccess().GetRedlineTable()[ nFndPos ] )->End() ) == aSrcPos
281 && *pRedl->Start() < aSrcPos )
283 if( !moSaveIndex )
285 moSaveIndex.emplace( rInsIdx, -1 );
287 mvSavArr.push_back( const_cast<SwPosition*>(pEnd) );
291 SaveRedlEndPosForRestore::~SaveRedlEndPosForRestore()
293 moSaveIndex.reset();
296 void SaveRedlEndPosForRestore::Restore()
298 if (mvSavArr.empty())
299 return;
300 ++(*moSaveIndex);
301 SwContentNode* pNode = moSaveIndex->GetNode().GetContentNode();
302 // If there's no content node at the remembered position, we will not restore the old position
303 // This may happen if a table (or section?) will be inserted.
304 if( pNode )
306 SwPosition aPos( *moSaveIndex, pNode, mnSaveContent );
307 for( auto n = mvSavArr.size(); n; )
308 *mvSavArr[ --n ] = aPos;
312 /// Convert list of ranges of whichIds to a corresponding list of whichIds
313 static std::vector<sal_uInt16> lcl_RangesToVector(const WhichRangesContainer& pRanges)
315 std::vector<sal_uInt16> aResult;
317 for(const WhichPair& rPair : pRanges)
319 for (sal_uInt16 j = rPair.first; j <= rPair.second; j++)
320 aResult.push_back(j);
323 return aResult;
326 void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev )
328 rJoinText = false;
329 rJoinPrev = false;
330 if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
331 return;
333 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
334 SwTextNode *pSttNd = pStt->GetNode().GetTextNode();
335 if( !pSttNd )
336 return;
338 SwTextNode *pEndNd = pEnd->GetNode().GetTextNode();
339 rJoinText = nullptr != pEndNd;
340 if( !rJoinText )
341 return;
343 bool bExchange = pStt == rPam.GetPoint();
344 if( !pStt->GetContentIndex() &&
345 pEndNd->GetText().getLength() != pEnd->GetContentIndex())
346 bExchange = !bExchange;
347 if( bExchange )
348 rPam.Exchange();
349 rJoinPrev = rPam.GetPoint() == pStt;
350 OSL_ENSURE( !pStt->GetContentIndex() &&
351 pEndNd->GetText().getLength() != pEnd->GetContentIndex()
352 ? (rPam.GetPoint()->GetNode() < rPam.GetMark()->GetNode())
353 : (rPam.GetPoint()->GetNode() > rPam.GetMark()->GetNode()),
354 "sw_GetJoinFlags");
357 bool sw_JoinText( SwPaM& rPam, bool bJoinPrev )
359 SwNodeIndex aIdx( rPam.GetPoint()->GetNode() );
360 SwTextNode *pTextNd = aIdx.GetNode().GetTextNode();
361 SwNodeIndex aOldIdx( aIdx );
362 SwTextNode *pOldTextNd = pTextNd;
364 if( pTextNd && pTextNd->CanJoinNext( &aIdx ) )
366 SwDoc& rDoc = rPam.GetDoc();
367 if( bJoinPrev )
369 // We do not need to handle xmlids in this case, because
370 // it is only invoked if one paragraph is/becomes completely empty
371 // (see sw_GetJoinFlags)
373 // If PageBreaks are deleted/set, it must not be added to the Undo history!
374 // Also, deleting the Node is not added to the Undo history!
375 ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
377 /* PageBreaks, PageDesc, ColumnBreaks */
378 // If we need to change something about the logic to copy the PageBreaks,
379 // PageDesc, etc. we also have to change SwUndoDelete.
380 // There, we copy the AUTO PageBreak from the GetMarkNode!
382 /* The MarkNode */
383 pTextNd = aIdx.GetNode().GetTextNode();
384 if (pTextNd->HasSwAttrSet())
386 if( SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_BREAK, false) )
387 pTextNd->ResetAttr( RES_BREAK );
388 if( pTextNd->HasSwAttrSet() &&
389 SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_PAGEDESC, false ) )
390 pTextNd->ResetAttr( RES_PAGEDESC );
393 /* The PointNode */
394 if( pOldTextNd->HasSwAttrSet() )
396 const SfxPoolItem* pItem;
397 SfxItemSet aSet( rDoc.GetAttrPool(), aBreakSetRange );
398 const SfxItemSet* pSet = pOldTextNd->GetpSwAttrSet();
399 if( SfxItemState::SET == pSet->GetItemState( RES_BREAK,
400 false, &pItem ) )
401 aSet.Put( *pItem );
402 if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC,
403 false, &pItem ) )
404 aSet.Put( *pItem );
405 if( aSet.Count() )
406 pTextNd->SetAttr( aSet );
408 pOldTextNd->FormatToTextAttr( pTextNd );
410 const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
411 pContentStore->Save(rDoc, aOldIdx.GetIndex(), SAL_MAX_INT32);
413 SwContentIndex aAlphaIdx(pTextNd);
414 pOldTextNd->CutText( pTextNd, aAlphaIdx, SwContentIndex(pOldTextNd),
415 pOldTextNd->Len() );
416 SwPosition aAlphaPos( aIdx, aAlphaIdx );
417 rDoc.CorrRel( rPam.GetPoint()->GetNode(), aAlphaPos, 0, true );
419 // move all Bookmarks/TOXMarks
420 if( !pContentStore->Empty() )
421 pContentStore->Restore( rDoc, aIdx.GetIndex() );
423 // If the passed PaM is not in the Cursor ring,
424 // treat it separately (e.g. when it's being called from AutoFormat)
425 if( pOldTextNd == rPam.GetBound().GetContentNode() )
426 rPam.GetBound() = aAlphaPos;
427 if( pOldTextNd == rPam.GetBound( false ).GetContentNode() )
428 rPam.GetBound( false ) = aAlphaPos;
430 // delete the Node, at last!
431 SwNode::Merge const eOldMergeFlag(pOldTextNd->GetRedlineMergeFlag());
432 if (eOldMergeFlag == SwNode::Merge::First
433 && !pTextNd->IsCreateFrameWhenHidingRedlines())
435 sw::MoveDeletedPrevFrames(*pOldTextNd, *pTextNd);
437 rDoc.GetNodes().Delete( aOldIdx );
438 sw::CheckResetRedlineMergeFlag(*pTextNd,
439 eOldMergeFlag == SwNode::Merge::NonFirst
440 ? sw::Recreate::Predecessor
441 : sw::Recreate::No);
443 else
445 SwTextNode* pDelNd = aIdx.GetNode().GetTextNode();
446 if( pTextNd->Len() )
447 pDelNd->FormatToTextAttr( pTextNd );
448 else
450 /* This case was missed:
452 <something></something> <-- pTextNd
453 <other>ccc</other> <-- pDelNd
455 <something> and <other> are paragraph
456 attributes. The attribute <something> stayed if not
457 overwritten by an attribute in "ccc". Fixed by
458 first resetting all character attributes in first
459 paragraph (pTextNd).
461 std::vector<sal_uInt16> aShorts =
462 lcl_RangesToVector(aCharFormatSetRange);
463 pTextNd->ResetAttr(aShorts);
465 if( pDelNd->HasSwAttrSet() )
467 // only copy the character attributes
468 SfxItemSet aTmpSet( rDoc.GetAttrPool(), aCharFormatSetRange );
469 aTmpSet.Put( *pDelNd->GetpSwAttrSet() );
470 pTextNd->SetAttr( aTmpSet );
474 rDoc.CorrRel( aIdx.GetNode(), *rPam.GetPoint(), 0, true );
475 // #i100466# adjust given <rPam>, if it does not belong to the cursors
476 if ( pDelNd == rPam.GetBound().GetContentNode() )
478 rPam.GetBound().Assign( *pTextNd );
480 if( pDelNd == rPam.GetBound( false ).GetContentNode() )
482 rPam.GetBound( false ).Assign( *pTextNd );
484 pTextNd->JoinNext();
486 return true;
488 else return false;
491 static void lcl_syncGrammarError( SwTextNode &rTextNode, linguistic2::ProofreadingResult& rResult,
492 const ModelToViewHelper &rConversionMap )
494 if( rTextNode.IsGrammarCheckDirty() )
495 return;
496 SwGrammarMarkUp* pWrong = rTextNode.GetGrammarCheck();
497 linguistic2::SingleProofreadingError* pArray = rResult.aErrors.getArray();
498 sal_uInt16 j = 0;
499 if( pWrong )
501 for( sal_Int32 i = 0; i < rResult.aErrors.getLength(); ++i )
503 const linguistic2::SingleProofreadingError &rError = rResult.aErrors[i];
504 const sal_Int32 nStart = rConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos;
505 const sal_Int32 nEnd = rConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos;
506 if( i != j )
507 pArray[j] = pArray[i];
508 if( pWrong->LookForEntry( nStart, nEnd ) )
509 ++j;
512 if( rResult.aErrors.getLength() > j )
513 rResult.aErrors.realloc( j );
516 uno::Any SwDoc::Spell( SwPaM& rPaM,
517 uno::Reference< XSpellChecker1 > const &xSpeller,
518 sal_uInt16* pPageCnt, sal_uInt16* pPageSt,
519 bool bGrammarCheck,
520 SwRootFrame const*const pLayout,
521 SwConversionArgs *pConvArgs ) const
523 SwPosition* const pSttPos = rPaM.Start();
524 SwPosition* const pEndPos = rPaM.End();
526 std::unique_ptr<SwSpellArgs> pSpellArgs;
527 if (pConvArgs)
529 pConvArgs->SetStart(*pSttPos);
530 pConvArgs->SetEnd(*pEndPos);
532 else
533 pSpellArgs.reset(new SwSpellArgs( xSpeller, *pSttPos, *pEndPos, bGrammarCheck ));
535 SwNodeOffset nCurrNd = pSttPos->GetNodeIndex();
536 SwNodeOffset nEndNd = pEndPos->GetNodeIndex();
538 uno::Any aRet;
539 if( nCurrNd <= nEndNd )
541 SwContentFrame* pContentFrame;
542 bool bGoOn = true;
543 while( bGoOn )
545 SwNode* pNd = GetNodes()[ nCurrNd ];
546 switch( pNd->GetNodeType() )
548 case SwNodeType::Text:
549 if( nullptr != ( pContentFrame = pNd->GetTextNode()->getLayoutFrame( getIDocumentLayoutAccess().GetCurrentLayout() )) )
551 // skip protected and hidden Cells and Flys
552 if( pContentFrame->IsProtected() )
554 nCurrNd = pNd->EndOfSectionIndex();
556 else if( !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() )
558 if( pPageCnt && *pPageCnt && pPageSt )
560 sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum();
561 if( !*pPageSt )
563 *pPageSt = nPageNr;
564 if( *pPageCnt < *pPageSt )
565 *pPageCnt = *pPageSt;
567 tools::Long nStat;
568 if( nPageNr >= *pPageSt )
569 nStat = nPageNr - *pPageSt + 1;
570 else
571 nStat = nPageNr + *pPageCnt - *pPageSt + 1;
572 ::SetProgressState( nStat, GetDocShell() );
574 //Spell() changes the pSpellArgs in case an error is found
575 sal_Int32 nBeginGrammarCheck = 0;
576 sal_Int32 nEndGrammarCheck = 0;
577 if( pSpellArgs && pSpellArgs->bIsGrammarCheck)
579 nBeginGrammarCheck = &pSpellArgs->pStartPos->GetNode() == pNd ? pSpellArgs->pStartPos->GetContentIndex() : 0;
580 // if grammar checking starts inside of a sentence the start position has to be adjusted
581 if( nBeginGrammarCheck )
583 SwContentIndex aStartIndex( pNd->GetTextNode(), nBeginGrammarCheck );
584 SwPosition aStart( *pNd, aStartIndex );
585 SwCursor aCursor(aStart, nullptr);
586 SwPosition aOrigPos = *aCursor.GetPoint();
587 aCursor.GoSentence( SwCursor::START_SENT );
588 if( aOrigPos != *aCursor.GetPoint() )
590 nBeginGrammarCheck = aCursor.GetPoint()->GetContentIndex();
593 nEndGrammarCheck = (&pSpellArgs->pEndPos->GetNode() == pNd)
594 ? pSpellArgs->pEndPos->GetContentIndex()
595 : pNd->GetTextNode()
596 ->GetText().getLength();
599 sal_Int32 nSpellErrorPosition = pNd->GetTextNode()->GetText().getLength();
600 if( (!pConvArgs && pNd->GetTextNode()->Spell( pSpellArgs.get() )) ||
601 ( pConvArgs && pNd->GetTextNode()->Convert( *pConvArgs )))
603 // Cancel and remember position
604 if( pSpellArgs )
605 nSpellErrorPosition = pSpellArgs->pStartPos->GetContentIndex() > pSpellArgs->pEndPos->GetContentIndex() ?
606 pSpellArgs->pEndPos->GetContentIndex() :
607 pSpellArgs->pStartPos->GetContentIndex();
608 if( nCurrNd != nEndNd )
610 pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex());
611 pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex());
612 nCurrNd = nEndNd;
616 if( pSpellArgs && pSpellArgs->bIsGrammarCheck )
618 uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() );
619 if (xGCIterator.is())
621 uno::Reference< lang::XComponent > xDoc = GetDocShell()->GetBaseModel();
622 // Expand the string:
623 const ModelToViewHelper aConversionMap(*pNd->GetTextNode(), pLayout);
624 const OUString& aExpandText = aConversionMap.getViewText();
626 // get XFlatParagraph to use...
627 uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNd->GetTextNode(), aExpandText, aConversionMap );
629 // get error position of cursor in XFlatParagraph
630 linguistic2::ProofreadingResult aResult;
631 bool bGrammarErrors;
634 aConversionMap.ConvertToViewPosition( nBeginGrammarCheck );
635 aResult = xGCIterator->checkSentenceAtPosition(
636 xDoc, xFlatPara, aExpandText, lang::Locale(), nBeginGrammarCheck, -1, -1 );
638 lcl_syncGrammarError( *pNd->GetTextNode(), aResult, aConversionMap );
640 // get suggestions to use for the specific error position
641 bGrammarErrors = aResult.aErrors.hasElements();
642 // if grammar checking doesn't have any progress then quit
643 if( aResult.nStartOfNextSentencePosition <= nBeginGrammarCheck )
644 break;
645 // prepare next iteration
646 nBeginGrammarCheck = aResult.nStartOfNextSentencePosition;
648 while( nSpellErrorPosition > aResult.nBehindEndOfSentencePosition && !bGrammarErrors && aResult.nBehindEndOfSentencePosition < nEndGrammarCheck );
650 if( bGrammarErrors && nSpellErrorPosition >= aResult.nBehindEndOfSentencePosition )
652 aRet <<= aResult;
653 //put the cursor to the current error
654 const linguistic2::SingleProofreadingError &rError = aResult.aErrors[0];
655 pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex());
656 pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex());
657 pSpellArgs->pStartPos->Assign(*pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos );
658 pSpellArgs->pEndPos->Assign(*pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos );
659 nCurrNd = nEndNd;
665 break;
666 case SwNodeType::Section:
667 if( static_cast<SwSectionNode*>(pNd)->GetSection().IsProtect() ||
668 static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() )
669 nCurrNd = pNd->EndOfSectionIndex();
670 break;
671 case SwNodeType::End:
673 break;
675 default: break;
678 bGoOn = nCurrNd < nEndNd;
679 ++nCurrNd;
683 if( !aRet.hasValue() )
685 if (pConvArgs)
686 aRet <<= pConvArgs->aConvText;
687 else
688 aRet <<= pSpellArgs->xSpellAlt;
691 return aRet;
694 namespace {
696 class SwHyphArgs : public SwInterHyphInfo
698 SwNodeIndex m_aNodeIdx;
699 const SwNode *m_pStart;
700 const SwNode *m_pEnd;
701 sal_uInt16 *m_pPageCnt;
702 sal_uInt16 *m_pPageSt;
704 sal_Int32 m_nPamStart;
705 sal_Int32 m_nPamLen;
707 public:
708 SwHyphArgs( const SwPaM *pPam, const Point &rPoint,
709 sal_uInt16* pPageCount, sal_uInt16* pPageStart );
710 void SetPam( SwPaM *pPam ) const;
711 void SetNode( SwNode& rNew ) { m_aNodeIdx.Assign(rNew); }
712 inline void SetRange( const SwNode *pNew );
713 void NextNode() { ++m_aNodeIdx; }
714 sal_uInt16 *GetPageCnt() { return m_pPageCnt; }
715 sal_uInt16 *GetPageSt() { return m_pPageSt; }
720 SwHyphArgs::SwHyphArgs( const SwPaM *pPam, const Point &rCursorPos,
721 sal_uInt16* pPageCount, sal_uInt16* pPageStart )
722 : SwInterHyphInfo( rCursorPos ), m_aNodeIdx(pPam->GetPoint()->GetNode()),
723 m_pPageCnt( pPageCount ), m_pPageSt( pPageStart )
725 // The following constraints have to be met:
726 // 1) there is at least one Selection
727 // 2) SPoint() == Start()
728 OSL_ENSURE( pPam->HasMark(), "SwDoc::Hyphenate: blowing in the wind");
729 OSL_ENSURE( *pPam->GetPoint() <= *pPam->GetMark(),
730 "SwDoc::Hyphenate: New York, New York");
732 const SwPosition *pPoint = pPam->GetPoint();
734 // Set start
735 m_pStart = pPoint->GetNode().GetTextNode();
736 m_nPamStart = pPoint->GetContentIndex();
738 // Set End and Length
739 const SwPosition *pMark = pPam->GetMark();
740 m_pEnd = pMark->GetNode().GetTextNode();
741 m_nPamLen = pMark->GetContentIndex();
742 if( pPoint->GetNode() == pMark->GetNode() )
743 m_nPamLen = m_nPamLen - pPoint->GetContentIndex();
746 inline void SwHyphArgs::SetRange( const SwNode *pNew )
748 m_nStart = m_pStart == pNew ? m_nPamStart : 0;
749 m_nEnd = m_pEnd == pNew ? m_nPamStart + m_nPamLen : SAL_MAX_INT32;
752 void SwHyphArgs::SetPam( SwPaM *pPam ) const
754 pPam->GetPoint()->Assign( m_aNodeIdx, m_nWordStart );
755 pPam->GetMark()->Assign( m_aNodeIdx, m_nWordStart + m_nWordLen );
758 // Returns true if we can proceed.
759 static bool lcl_HyphenateNode( SwNode* pNd, void* pArgs )
761 // Hyphenate returns true if there is a hyphenation point and sets pPam
762 SwTextNode *pNode = pNd->GetTextNode();
763 SwHyphArgs *pHyphArgs = static_cast<SwHyphArgs*>(pArgs);
764 if( pNode )
766 // sw_redlinehide: this will be called once per node for merged nodes;
767 // the fully deleted ones won't have frames so are skipped.
768 SwContentFrame* pContentFrame = pNode->getLayoutFrame( pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
769 if( pContentFrame && !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() )
771 sal_uInt16 *pPageSt = pHyphArgs->GetPageSt();
772 sal_uInt16 *pPageCnt = pHyphArgs->GetPageCnt();
773 if( pPageCnt && *pPageCnt && pPageSt )
775 sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum();
776 if( !*pPageSt )
778 *pPageSt = nPageNr;
779 if( *pPageCnt < *pPageSt )
780 *pPageCnt = *pPageSt;
782 tools::Long nStat = nPageNr >= *pPageSt ? nPageNr - *pPageSt + 1
783 : nPageNr + *pPageCnt - *pPageSt + 1;
784 ::SetProgressState( nStat, pNode->GetDoc().GetDocShell() );
786 pHyphArgs->SetRange( pNd );
787 if( pNode->Hyphenate( *pHyphArgs ) )
789 pHyphArgs->SetNode( *pNd );
790 return false;
794 pHyphArgs->NextNode();
795 return true;
798 uno::Reference< XHyphenatedWord > SwDoc::Hyphenate(
799 SwPaM *pPam, const Point &rCursorPos,
800 sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
802 OSL_ENSURE(this == &pPam->GetDoc(), "SwDoc::Hyphenate: strangers in the night");
804 if( *pPam->GetPoint() > *pPam->GetMark() )
805 pPam->Exchange();
807 SwHyphArgs aHyphArg( pPam, rCursorPos, pPageCnt, pPageSt );
808 SwNodeIndex aTmpIdx( pPam->GetMark()->GetNode(), 1 );
809 GetNodes().ForEach( pPam->GetPoint()->GetNode(), aTmpIdx.GetNode(),
810 lcl_HyphenateNode, &aHyphArg );
811 aHyphArg.SetPam( pPam );
812 return aHyphArg.GetHyphWord(); // will be set by lcl_HyphenateNode
815 // Save the current values to add them as automatic entries to AutoCorrect.
816 void SwDoc::SetAutoCorrExceptWord( std::unique_ptr<SwAutoCorrExceptWord> pNew )
818 mpACEWord = std::move(pNew);
821 void SwDoc::DeleteAutoCorrExceptWord()
823 mpACEWord.reset();
826 void SwDoc::CountWords( const SwPaM& rPaM, SwDocStat& rStat )
828 // This is a modified version of SwDoc::TransliterateText
829 auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
831 const SwNodeOffset nSttNd = pStt->GetNodeIndex();
832 const SwNodeOffset nEndNd = pEnd->GetNodeIndex();
834 const sal_Int32 nSttCnt = pStt->GetContentIndex();
835 const sal_Int32 nEndCnt = pEnd->GetContentIndex();
837 const SwTextNode* pTNd = pStt->GetNode().GetTextNode();
838 if( pStt == pEnd && pTNd ) // no region ?
840 // do nothing
841 return;
844 if( nSttNd != nEndNd )
846 SwNodeIndex aIdx( pStt->GetNode() );
847 if( nSttCnt )
849 ++aIdx;
850 if( pTNd )
851 pTNd->CountWords( rStat, nSttCnt, pTNd->GetText().getLength() );
854 for( ; aIdx.GetIndex() < nEndNd; ++aIdx )
855 if( nullptr != ( pTNd = aIdx.GetNode().GetTextNode() ))
856 pTNd->CountWords( rStat, 0, pTNd->GetText().getLength() );
858 if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() ))
859 pTNd->CountWords( rStat, 0, nEndCnt );
861 else if( pTNd && nSttCnt < nEndCnt )
862 pTNd->CountWords( rStat, nSttCnt, nEndCnt );
866 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */