android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / doc / docnum.cxx
blob2ae2d23e8c63a414a0cd27dfadc2e045f82879e1
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 <hintids.hxx>
21 #include <ftninfo.hxx>
22 #include <ftnidx.hxx>
23 #include <doc.hxx>
24 #include <IDocumentUndoRedo.hxx>
25 #include <IDocumentListsAccess.hxx>
26 #include <IDocumentFieldsAccess.hxx>
27 #include <IDocumentRedlineAccess.hxx>
28 #include <IDocumentState.hxx>
29 #include <IDocumentStylePoolAccess.hxx>
30 #include <pam.hxx>
31 #include <ndtxt.hxx>
32 #include <poolfmt.hxx>
33 #include <UndoCore.hxx>
34 #include <UndoRedline.hxx>
35 #include <UndoNumbering.hxx>
36 #include <swundo.hxx>
37 #include <SwUndoFmt.hxx>
38 #include <rolbck.hxx>
39 #include <paratr.hxx>
40 #include <docary.hxx>
41 #include <mvsave.hxx>
42 #include <txtfrm.hxx>
43 #include <rootfrm.hxx>
44 #include <redline.hxx>
45 #include <strings.hrc>
46 #include <SwNodeNum.hxx>
47 #include <list.hxx>
48 #include <calbck.hxx>
49 #include <comphelper/string.hxx>
50 #include <comphelper/random.hxx>
51 #include <o3tl/safeint.hxx>
52 #include <o3tl/string_view.hxx>
53 #include <osl/diagnose.h>
54 #include <tools/datetimeutils.hxx>
56 #include <map>
57 #include <stdlib.h>
59 #include <wrtsh.hxx>
61 namespace {
62 void lcl_ResetIndentAttrs(SwDoc *pDoc, const SwPaM &rPam,
63 const o3tl::sorted_vector<sal_uInt16> aResetAttrsArray,
64 SwRootFrame const*const pLayout)
66 // #i114929#
67 // On a selection setup a corresponding Point-and-Mark in order to get
68 // the indentation attribute reset on all paragraphs touched by the selection
69 if ( rPam.HasMark() &&
70 rPam.End()->GetNode().GetTextNode() )
72 SwPaM aPam( rPam.Start()->GetNode(), 0,
73 rPam.End()->GetNode(), rPam.End()->GetNode().GetTextNode()->Len() );
74 pDoc->ResetAttrs( aPam, false, aResetAttrsArray, true, pLayout );
76 else
78 pDoc->ResetAttrs( rPam, false, aResetAttrsArray, true, pLayout );
82 void ExpandPamForParaPropsNodes(SwPaM& rPam, SwRootFrame const*const pLayout)
84 if (!pLayout)
85 return;
87 // ensure that selection from the Shell includes the para-props node
88 // to which the attributes should be applied
89 if (rPam.GetPoint()->GetNode().IsTextNode())
91 rPam.GetPoint()->Assign( *sw::GetParaPropsNode(*pLayout, rPam.GetPoint()->GetNode()) );
93 if (rPam.GetMark()->GetNode().IsTextNode())
95 rPam.GetMark()->Assign( *sw::GetParaPropsNode(*pLayout, rPam.GetMark()->GetNode()) );
100 static sal_uInt8 GetUpperLvlChg( sal_uInt8 nCurLvl, sal_uInt8 nLevel, sal_uInt16 nMask )
102 if( 1 < nLevel )
104 if( nCurLvl + 1 >= nLevel )
105 nCurLvl -= nLevel - 1;
106 else
107 nCurLvl = 0;
109 return static_cast<sal_uInt8>((nMask - 1) & ~(( 1 << nCurLvl ) - 1));
112 void SwDoc::SetOutlineNumRule( const SwNumRule& rRule )
114 if (GetIDocumentUndoRedo().DoesUndo())
116 GetIDocumentUndoRedo().StartUndo(SwUndoId::OUTLINE_EDIT, nullptr);
117 if (mpOutlineRule)
119 GetIDocumentUndoRedo().AppendUndo(
120 std::make_unique<SwUndoOutlineEdit>(*mpOutlineRule, rRule, *this));
124 if( mpOutlineRule )
125 (*mpOutlineRule) = rRule;
126 else
128 mpOutlineRule = new SwNumRule( rRule );
130 AddNumRule(mpOutlineRule); // #i36749#
133 mpOutlineRule->SetRuleType( OUTLINE_RULE );
134 mpOutlineRule->SetName(SwNumRule::GetOutlineRuleName(), getIDocumentListsAccess());
136 // assure that the outline numbering rule is an automatic rule
137 mpOutlineRule->SetAutoRule( true );
139 // test whether the optional CharFormats are defined in this Document
140 mpOutlineRule->CheckCharFormats( *this );
142 // notify text nodes, which are registered at the outline style, about the
143 // changed outline style
144 SwNumRule::tTextNodeList aTextNodeList;
145 mpOutlineRule->GetTextNodeList( aTextNodeList );
146 for ( SwTextNode* pTextNd : aTextNodeList )
148 pTextNd->NumRuleChgd();
150 // assure that list level corresponds to outline level
151 if ( pTextNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle() &&
152 pTextNd->GetAttrListLevel() != pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() )
154 pTextNd->SetAttrListLevel( pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() );
158 PropagateOutlineRule();
159 mpOutlineRule->SetInvalidRule(true);
160 UpdateNumRule();
162 // update if we have foot notes && numbering by chapter
163 if( !GetFootnoteIdxs().empty() && FTNNUM_CHAPTER == GetFootnoteInfo().m_eNum )
164 GetFootnoteIdxs().UpdateAllFootnote();
166 getIDocumentFieldsAccess().UpdateExpFields(nullptr, true);
168 if (GetIDocumentUndoRedo().DoesUndo())
170 GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_EDIT, nullptr);
173 getIDocumentState().SetModified();
176 void SwDoc::PropagateOutlineRule()
178 SwNumRule* pMyOutlineRule = GetOutlineNumRule();
179 if (!pMyOutlineRule)
180 return;
182 for (auto pColl : *mpTextFormatCollTable)
184 if(pColl->IsAssignedToListLevelOfOutlineStyle())
186 // Check only the list style, which is set at the paragraph style
187 const SwNumRuleItem & rCollRuleItem = pColl->GetNumRule( false );
189 if ( rCollRuleItem.GetValue().isEmpty() )
191 SwNumRuleItem aNumItem( pMyOutlineRule->GetName() );
192 pColl->SetFormatAttr(aNumItem);
198 // Increase/Decrease
199 bool SwDoc::OutlineUpDown(const SwPaM& rPam, short nOffset,
200 SwRootFrame const*const pLayout)
202 if( GetNodes().GetOutLineNds().empty() || !nOffset )
203 return false;
205 // calculate the range
206 SwPaM aPam(rPam, nullptr);
207 ExpandPamForParaPropsNodes(aPam, pLayout);
208 const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds();
209 SwNode* const pSttNd = &aPam.Start()->GetNode();
210 SwNode* const pEndNd = &aPam.End()->GetNode();
211 SwOutlineNodes::size_type nSttPos, nEndPos;
213 if( !rOutlNds.Seek_Entry( pSttNd, &nSttPos ) &&
214 !nSttPos-- )
215 // we're not in an "Outline section"
216 return false;
218 if( rOutlNds.Seek_Entry( pEndNd, &nEndPos ) )
219 ++nEndPos;
221 // We now have the wanted range in the OutlineNodes array,
222 // so check now if we're not invalidating sublevels
223 // (stepping over the limits)
225 // Here we go:
226 // 1. Create the style array:
227 SwTextFormatColl* aCollArr[ MAXLEVEL ];
228 memset( aCollArr, 0, sizeof( SwTextFormatColl* ) * MAXLEVEL );
230 for( auto pTextFormatColl : *mpTextFormatCollTable )
232 if (pTextFormatColl->IsAssignedToListLevelOfOutlineStyle())
234 const int nLevel = pTextFormatColl->GetAssignedOutlineStyleLevel();
235 aCollArr[ nLevel ] = pTextFormatColl;
239 int n;
241 /* Find the last occupied level (backward). */
242 for (n = MAXLEVEL - 1; n > 0; n--)
244 if (aCollArr[n] != nullptr)
245 break;
248 /* If an occupied level is found, choose next level (which IS
249 unoccupied) until a valid level is found. If no occupied level
250 was found n is 0 and aCollArr[0] is 0. In this case no demoting
251 is possible. */
252 if (aCollArr[n] != nullptr)
254 while (n < MAXLEVEL - 1)
256 n++;
258 SwTextFormatColl *aTmpColl =
259 getIDocumentStylePoolAccess().GetTextCollFromPool(o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n));
261 if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() &&
262 aTmpColl->GetAssignedOutlineStyleLevel() == n )
264 aCollArr[n] = aTmpColl;
265 break;
270 /* Find the first occupied level (forward). */
271 for (n = 0; n < MAXLEVEL - 1; n++)
273 if (aCollArr[n] != nullptr)
274 break;
277 /* If an occupied level is found, choose previous level (which IS
278 unoccupied) until a valid level is found. If no occupied level
279 was found n is MAXLEVEL - 1 and aCollArr[MAXLEVEL - 1] is 0. In
280 this case no demoting is possible. */
281 if (aCollArr[n] != nullptr)
283 while (n > 0)
285 n--;
287 SwTextFormatColl *aTmpColl =
288 getIDocumentStylePoolAccess().GetTextCollFromPool(o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n));
290 if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() &&
291 aTmpColl->GetAssignedOutlineStyleLevel() == n )
293 aCollArr[n] = aTmpColl;
294 break;
299 /* --> #i13747#
301 Build a move table that states from which level to which other level
302 an outline will be moved.
304 the move table:
305 aMoveArr[n] = m: replace aCollArr[n] with aCollArr[m]
307 int aMoveArr[MAXLEVEL];
308 int nStep; // step size for searching in aCollArr: -1 or 1
309 int nNum; // amount of steps for stepping in aCollArr
311 if (nOffset < 0)
313 nStep = -1;
314 nNum = -nOffset;
316 else
318 nStep = 1;
319 nNum = nOffset;
322 /* traverse aCollArr */
323 for (n = 0; n < MAXLEVEL; n++)
325 /* If outline level n has an assigned paragraph style step
326 nNum steps forwards (nStep == 1) or backwards (nStep ==
327 -1). One step is to go to the next non-null entry in
328 aCollArr in the selected direction. If nNum steps were
329 possible write the index of the entry found to aCollArr[n],
330 i.e. outline level n will be replaced by outline level
331 aCollArr[n].
333 If outline level n has no assigned paragraph style
334 aMoveArr[n] is set to -1.
336 if (aCollArr[n] != nullptr)
338 int m = n;
339 int nCount = nNum;
341 while (nCount > 0 && m + nStep >= 0 && m + nStep < MAXLEVEL)
343 m += nStep;
345 if (aCollArr[m] != nullptr)
346 nCount--;
349 if (nCount == 0)
350 aMoveArr[n] = m;
351 else
352 aMoveArr[n] = -1;
354 else
355 aMoveArr[n] = -1;
358 /* If moving of the outline levels is applicable, i.e. for all
359 outline levels occurring in the document there has to be a valid
360 target outline level implied by aMoveArr. */
361 bool bMoveApplicable = true;
362 for (auto i = nSttPos; i < nEndPos; ++i)
364 SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode();
365 if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd))
367 continue;
369 SwTextFormatColl* pColl = pTextNd->GetTextColl();
371 if( pColl->IsAssignedToListLevelOfOutlineStyle() )
373 const int nLevel = pColl->GetAssignedOutlineStyleLevel();
374 if (aMoveArr[nLevel] == -1)
375 bMoveApplicable = false;
378 // Check on outline level attribute of text node, if text node is
379 // not an outline via a to outline style assigned paragraph style.
380 else
382 const int nNewOutlineLevel = pTextNd->GetAttrOutlineLevel() + nOffset;
383 if ( nNewOutlineLevel < 1 || nNewOutlineLevel > MAXLEVEL )
385 bMoveApplicable = false;
390 if (! bMoveApplicable )
391 return false;
393 if (GetIDocumentUndoRedo().DoesUndo())
395 GetIDocumentUndoRedo().StartUndo(SwUndoId::OUTLINE_LR, nullptr);
396 GetIDocumentUndoRedo().AppendUndo(
397 std::make_unique<SwUndoOutlineLeftRight>(aPam, nOffset) );
400 // 2. Apply the new style to all Nodes
401 for (auto i = nSttPos; i < nEndPos; ++i)
403 SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode();
404 if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd))
406 continue;
408 SwTextFormatColl* pColl = pTextNd->GetTextColl();
410 if( pColl->IsAssignedToListLevelOfOutlineStyle() )
412 const int nLevel = pColl->GetAssignedOutlineStyleLevel();
414 OSL_ENSURE(aMoveArr[nLevel] >= 0,
415 "move table: current TextColl not found when building table!");
417 if (nLevel < MAXLEVEL && aMoveArr[nLevel] >= 0)
419 pColl = aCollArr[ aMoveArr[nLevel] ];
421 if (pColl != nullptr)
422 pTextNd->ChgFormatColl( pColl );
426 else if( pTextNd->GetAttrOutlineLevel() > 0)
428 int nLevel = pTextNd->GetAttrOutlineLevel() + nOffset;
429 if( 0 <= nLevel && nLevel <= MAXLEVEL)
430 pTextNd->SetAttrOutlineLevel( nLevel );
433 // Undo ???
435 if (GetIDocumentUndoRedo().DoesUndo())
437 GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_LR, nullptr);
440 ChkCondColls();
441 getIDocumentState().SetModified();
443 return true;
446 // Move up/down
447 bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type nOffset )
449 // Do not move to special sections in the nodes array
450 const SwPosition& rStt = *rPam.Start(),
451 & rEnd = *rPam.End();
452 if( GetNodes().GetOutLineNds().empty() || !nOffset ||
453 (rStt.GetNodeIndex() < GetNodes().GetEndOfExtras().GetIndex()) ||
454 (rEnd.GetNodeIndex() < GetNodes().GetEndOfExtras().GetIndex()))
456 return false;
459 SwOutlineNodes::size_type nCurrentPos = 0;
460 SwNodeIndex aSttRg( rStt.GetNode() ), aEndRg( rEnd.GetNode() );
462 int nOutLineLevel = MAXLEVEL;
463 SwNode* pSrch = &aSttRg.GetNode();
465 if( pSrch->IsTextNode())
466 nOutLineLevel = static_cast<sal_uInt8>(pSrch->GetTextNode()->GetAttrOutlineLevel()-1);
467 SwNode* pEndSrch = &aEndRg.GetNode();
468 if( !GetNodes().GetOutLineNds().Seek_Entry( pSrch, &nCurrentPos ) )
470 if( !nCurrentPos )
471 return false; // Promoting or demoting before the first outline => no.
472 if( --nCurrentPos )
473 aSttRg = *GetNodes().GetOutLineNds()[ nCurrentPos ];
474 else if( 0 > nOffset )
475 return false; // Promoting at the top of document?!
476 else
477 aSttRg = *GetNodes().GetEndOfContent().StartOfSectionNode();
479 SwOutlineNodes::size_type nTmpPos = 0;
480 // If the given range ends at an outlined text node we have to decide if it has to be a part of
481 // the moving range or not. Normally it will be a sub outline of our chapter
482 // and has to be moved, too. But if the chapter ends with a table(or a section end),
483 // the next text node will be chosen and this could be the next outline of the same level.
484 // The criteria has to be the outline level: sub level => incorporate, same/higher level => no.
485 if( GetNodes().GetOutLineNds().Seek_Entry( pEndSrch, &nTmpPos ) )
487 if( !pEndSrch->IsTextNode() || pEndSrch == pSrch ||
488 nOutLineLevel < pEndSrch->GetTextNode()->GetAttrOutlineLevel()-1 )
489 ++nTmpPos; // For sub outlines only!
492 aEndRg = nTmpPos < GetNodes().GetOutLineNds().size()
493 ? *GetNodes().GetOutLineNds()[ nTmpPos ]
494 : GetNodes().GetEndOfContent();
495 if( nOffset >= 0 )
496 nCurrentPos = nTmpPos;
497 if( aEndRg == aSttRg )
499 OSL_FAIL( "Moving outlines: Surprising selection" );
500 ++aEndRg;
503 const SwNode* pNd;
504 // The following code corrects the range to handle sections (start/end nodes)
505 // The range will be extended if the least node before the range is a start node
506 // which ends inside the range => The complete section will be moved.
507 // The range will be shrunk if the last position is a start node.
508 // The range will be shrunk if the last node is an end node which starts before the range.
509 --aSttRg;
510 while( aSttRg.GetNode().IsStartNode() )
512 pNd = aSttRg.GetNode().EndOfSectionNode();
513 if( pNd->GetIndex() >= aEndRg.GetIndex() )
514 break;
515 --aSttRg;
517 ++aSttRg;
519 --aEndRg;
520 while( aEndRg.GetNode().IsStartNode() )
521 --aEndRg;
523 while( aEndRg.GetNode().IsEndNode() )
525 pNd = aEndRg.GetNode().StartOfSectionNode();
526 if( pNd->GetIndex() >= aSttRg.GetIndex() )
527 break;
528 --aEndRg;
530 ++aEndRg;
532 // calculation of the new position
533 if( nOffset < 0 && nCurrentPos < o3tl::make_unsigned(-nOffset) )
534 pNd = GetNodes().GetEndOfContent().StartOfSectionNode();
535 else if( nCurrentPos + nOffset >= GetNodes().GetOutLineNds().size() )
536 pNd = &GetNodes().GetEndOfContent();
537 else
538 pNd = GetNodes().GetOutLineNds()[ nCurrentPos + nOffset ];
540 SwNodeOffset nNewPos = pNd->GetIndex();
542 // And now a correction of the insert position if necessary...
543 SwNodeIndex aInsertPos( *pNd, -1 );
544 while( aInsertPos.GetNode().IsStartNode() )
546 // Just before the insert position starts a section:
547 // when I'm moving forward I do not want to enter the section,
548 // when I'm moving backward I want to stay in the section if I'm already a part of,
549 // I want to stay outside if I was outside before.
550 if( nOffset < 0 )
552 pNd = aInsertPos.GetNode().EndOfSectionNode();
553 if( pNd->GetIndex() >= aEndRg.GetIndex() )
554 break;
556 --aInsertPos;
557 --nNewPos;
560 if( nOffset >= 0 )
562 // When just before the insert position a section ends, it is okay when I'm moving backward
563 // because I want to stay outside the section.
564 // When moving forward I've to check if I started inside or outside the section
565 // because I don't want to enter of leave such a section
566 while( aInsertPos.GetNode().IsEndNode() )
568 pNd = aInsertPos.GetNode().StartOfSectionNode();
569 if( pNd->GetIndex() >= aSttRg.GetIndex() )
570 break;
571 --aInsertPos;
572 --nNewPos;
575 // We do not want to move into tables (at the moment)
576 ++aInsertPos;
577 pNd = &aInsertPos.GetNode();
578 if( pNd->IsTableNode() )
579 pNd = pNd->StartOfSectionNode();
580 if( pNd->FindTableNode() )
581 return false;
583 OSL_ENSURE( aSttRg.GetIndex() > nNewPos || nNewPos >= aEndRg.GetIndex(),
584 "Position lies within Move range" );
586 // If a Position inside the special nodes array sections was calculated,
587 // set it to document start instead.
588 // Sections or Tables at the document start will be pushed backwards.
589 nNewPos = std::max( nNewPos, GetNodes().GetEndOfExtras().GetIndex() + SwNodeOffset(2) );
591 SwNodeOffset nOffs = nNewPos - ( 0 < nOffset ? aEndRg.GetIndex() : aSttRg.GetIndex());
592 SwPaM aPam( aSttRg, aEndRg, SwNodeOffset(0), SwNodeOffset(-1) );
593 return MoveParagraph( aPam, nOffs, true );
596 static SwTextNode* lcl_FindOutlineName(const SwOutlineNodes& rOutlNds,
597 SwRootFrame const*const pLayout, std::u16string_view aName, bool const bExact)
599 SwTextNode * pExactButDeleted(nullptr);
600 SwTextNode* pSavedNode = nullptr;
601 for( auto pOutlNd : rOutlNds )
603 SwTextNode* pTextNd = pOutlNd->GetTextNode();
604 const OUString sText( pTextNd->GetExpandText(pLayout) );
605 if (sText.startsWith(aName))
607 if (sText.getLength() == sal_Int32(aName.size()))
609 if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd))
611 pExactButDeleted = pTextNd;
613 else
615 // Found "exact", set Pos to the Node
616 return pTextNd;
619 if (!bExact && !pSavedNode
620 && (!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd)))
622 // maybe we just found the text's first part
623 pSavedNode = pTextNd;
628 return bExact ? pExactButDeleted : pSavedNode;
631 static SwTextNode* lcl_FindOutlineNum(const SwOutlineNodes& rOutlNds,
632 OUString& rName, SwRootFrame const*const pLayout)
634 // Valid numbers are (always just offsets!):
635 // ([Number]+\.)+ (as a regular expression!)
636 // (Number followed by a period, with 5 repetitions)
637 // i.e.: "1.1.", "1.", "1.1.1."
638 sal_Int32 nPos = 0;
639 std::u16string_view sNum = o3tl::getToken(rName, 0, '.', nPos );
640 if( -1 == nPos )
641 return nullptr; // invalid number!
643 sal_uInt16 nLevelVal[ MAXLEVEL ]; // numbers of all levels
644 memset( nLevelVal, 0, MAXLEVEL * sizeof( nLevelVal[0] ));
645 int nLevel = 0;
646 std::u16string_view sName( rName );
648 while( -1 != nPos )
650 sal_uInt16 nVal = 0;
651 for( size_t n = 0; n < sNum.size(); ++n )
653 const sal_Unicode c {sNum[ n ]};
654 if( '0' <= c && c <= '9' )
656 nVal *= 10;
657 nVal += c - '0';
659 else if( nLevel )
660 break; // "almost" valid number
661 else
662 return nullptr; // invalid number!
665 if( MAXLEVEL > nLevel )
666 nLevelVal[ nLevel++ ] = nVal;
668 sName = sName.substr( nPos );
669 nPos = 0;
670 sNum = o3tl::getToken(sName, 0, '.', nPos );
671 // #i4533# without this check all parts delimited by a dot are treated as outline numbers
672 if(!comphelper::string::isdigitAsciiString(sNum))
673 break;
675 rName = sName; // that's the follow-up text
677 // read all levels, so search the document for this outline
679 // Without OutlineNodes searching doesn't pay off
680 // and we save a crash
681 if( rOutlNds.empty() )
682 return nullptr;
684 // search in the existing outline nodes for the required outline num array
685 for( auto pOutlNd : rOutlNds )
687 SwTextNode* pNd = pOutlNd->GetTextNode();
688 if ( pNd->GetAttrOutlineLevel() == nLevel )
690 // #i51089#, #i68289#
691 // Assure, that text node has the correct numbering level. Otherwise,
692 // its number vector will not fit to the searched level.
693 if (pNd->GetNum(pLayout) && pNd->GetActualListLevel() == nLevel - 1)
695 const SwNodeNum & rNdNum = *(pNd->GetNum(pLayout));
696 SwNumberTree::tNumberVector aLevelVal = rNdNum.GetNumberVector();
697 // now compare with the one searched for
698 bool bEqual = true;
699 nLevel = std::min<int>(nLevel, MAXLEVEL);
700 for( int n = 0; n < nLevel; ++n )
702 if ( aLevelVal[n] != nLevelVal[n] )
704 bEqual = false;
705 break;
708 if (bEqual)
709 return pNd;
711 else
713 // A text node, which has an outline paragraph style applied and
714 // has as hard attribute 'no numbering' set, has an outline level,
715 // but no numbering tree node. Thus, consider this situation in
716 // the assertion condition.
717 OSL_ENSURE( !pNd->GetNumRule(),
718 "<lcl_FindOutlineNum(..)> - text node with outline level and numbering rule, but without numbering tree node. This is a serious defect" );
723 return nullptr;
726 // rName can contain a Number and/or the Text.
727 // First, we try to find the correct Entry via the Number.
728 // If it exists, we compare the Text to see if it's the right one.
729 // If that's not the case, we search again via the Text. If it is
730 // found, we got the right entry. Or else we use the one found by
731 // searching for the Number.
732 // If we don't have a Number, we search via the Text only.
733 bool SwDoc::GotoOutline(SwPosition& rPos, const OUString& rName, SwRootFrame const*const pLayout) const
735 if( !rName.isEmpty() )
737 const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds();
739 // 1. step: via the Number:
740 OUString sName( rName );
741 SwTextNode* pNd = ::lcl_FindOutlineNum(rOutlNds, sName, pLayout);
742 if ( pNd )
744 OUString sExpandedText = pNd->GetExpandText(pLayout);
745 //#i4533# leading numbers followed by a dot have been remove while
746 //searching for the outline position
747 //to compensate this they must be removed from the paragraphs text content, too
748 while(!sExpandedText.isEmpty())
750 sal_Int32 nPos = 0;
751 std::u16string_view sTempNum = o3tl::getToken(sExpandedText, 0, '.', nPos);
752 if( sTempNum.empty() || -1 == nPos ||
753 !comphelper::string::isdigitAsciiString(sTempNum))
754 break;
755 sExpandedText = sExpandedText.copy(nPos);
758 if( sExpandedText != sName )
760 SwTextNode *pTmpNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, true);
761 if ( pTmpNd ) // found via the Name
763 if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTmpNd))
764 { // found the correct node but it's deleted!
765 return false; // avoid fallback to inexact search
767 pNd = pTmpNd;
770 rPos.Assign(*pNd);
771 return true;
774 pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, rName, false);
775 if ( pNd )
777 rPos.Assign(*pNd);
778 return true;
781 // #i68289# additional search on hyperlink URL without its outline numbering part
782 if ( sName != rName )
784 pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, false);
785 if ( pNd )
787 rPos.Assign(*pNd);
788 return true;
792 return false;
795 static void lcl_ChgNumRule( SwDoc& rDoc, const SwNumRule& rRule )
797 SwNumRule* pOld = rDoc.FindNumRulePtr( rRule.GetName() );
798 if (!pOld) //we cannot proceed without the old NumRule
799 return;
801 sal_uInt16 nChgFormatLevel = 0;
802 sal_uInt16 nMask = 1;
804 for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nMask <<= 1 )
806 const SwNumFormat& rOldFormat = pOld->Get( n ), &rNewFormat = rRule.Get( n );
808 if ( rOldFormat != rNewFormat )
810 nChgFormatLevel |= nMask;
812 else if ( SVX_NUM_NUMBER_NONE > rNewFormat.GetNumberingType()
813 && 1 < rNewFormat.GetIncludeUpperLevels()
814 && 0 != ( nChgFormatLevel & GetUpperLvlChg( n, rNewFormat.GetIncludeUpperLevels(), nMask ) ) )
816 nChgFormatLevel |= nMask;
820 if( !nChgFormatLevel ) // Nothing has been changed?
822 const bool bInvalidateNumRule( pOld->IsContinusNum() != rRule.IsContinusNum() );
823 pOld->CheckCharFormats( rDoc );
824 pOld->SetContinusNum( rRule.IsContinusNum() );
826 if ( bInvalidateNumRule )
828 pOld->SetInvalidRule(true);
831 return ;
834 SwNumRule::tTextNodeList aTextNodeList;
835 pOld->GetTextNodeList( aTextNodeList );
836 sal_uInt8 nLvl( 0 );
837 for ( SwTextNode* pTextNd : aTextNodeList )
839 nLvl = static_cast<sal_uInt8>(pTextNd->GetActualListLevel());
841 if( nLvl < MAXLEVEL )
843 if( nChgFormatLevel & ( 1 << nLvl ))
845 pTextNd->NumRuleChgd();
850 for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n )
851 if ( nChgFormatLevel & ( 1 << n ) )
852 pOld->Set( n, rRule.GetNumFormat( n ) );
854 pOld->CheckCharFormats( rDoc );
855 pOld->SetInvalidRule( true );
856 pOld->SetContinusNum( rRule.IsContinusNum() );
858 rDoc.UpdateNumRule();
861 OUString SwDoc::SetNumRule( const SwPaM& rPam,
862 const SwNumRule& rRule,
863 const bool bCreateNewList,
864 SwRootFrame const*const pLayout,
865 const OUString& sContinuedListId,
866 bool bSetItem,
867 const bool bResetIndentAttrs )
869 OUString sListId;
871 SwPaM aPam(rPam, nullptr);
872 ExpandPamForParaPropsNodes(aPam, pLayout);
874 SwUndoInsNum * pUndo = nullptr;
875 if (GetIDocumentUndoRedo().DoesUndo())
877 // Start/End for attributes!
878 GetIDocumentUndoRedo().StartUndo( SwUndoId::INSNUM, nullptr );
879 pUndo = new SwUndoInsNum( aPam, rRule );
880 GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
883 SwNumRule* pNewOrChangedNumRule = FindNumRulePtr( rRule.GetName() );
884 bool bNewNumRuleCreated = false;
885 if ( pNewOrChangedNumRule == nullptr )
887 // create new numbering rule based on given one
888 pNewOrChangedNumRule = ( *mpNumRuleTable )[MakeNumRule( rRule.GetName(), &rRule )];
889 bNewNumRuleCreated = true;
891 else if ( rRule != *pNewOrChangedNumRule )
893 // change existing numbering rule
894 if (pUndo)
896 pUndo->SaveOldNumRule( *pNewOrChangedNumRule );
898 ::lcl_ChgNumRule( *this, rRule );
899 if (pUndo)
901 pUndo->SetLRSpaceEndPos();
905 if ( bSetItem )
907 if ( bCreateNewList )
909 if ( bNewNumRuleCreated )
911 // apply list id of list, which has been created for the new list style
912 sListId = pNewOrChangedNumRule->GetDefaultListId();
914 else
916 // create new list and apply its list id
917 const SwList* pNewList = getIDocumentListsAccess().createList( OUString(), pNewOrChangedNumRule->GetName() );
918 OSL_ENSURE( pNewList,
919 "<SwDoc::SetNumRule(..)> - could not create new list. Serious defect." );
920 sListId = pNewList->GetListId();
923 else if ( !sContinuedListId.isEmpty() )
925 // apply given list id
926 sListId = sContinuedListId;
928 if (!sListId.isEmpty())
930 getIDocumentContentOperations().InsertPoolItem(aPam,
931 SfxStringItem(RES_PARATR_LIST_ID, sListId),
932 SetAttrMode::DEFAULT, pLayout);
936 if (!aPam.HasMark())
938 SwTextNode * pTextNd = aPam.GetPoint()->GetNode().GetTextNode();
939 // robust code: consider case that the PaM doesn't denote a text node - e.g. it denotes a graphic node
940 if ( pTextNd != nullptr )
942 assert(!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd));
943 SwNumRule * pRule = pTextNd->GetNumRule();
945 if (pRule && pRule->GetName() == pNewOrChangedNumRule->GetName())
947 bSetItem = false;
948 if ( !pTextNd->IsInList() )
950 pTextNd->AddToList();
953 // Only clear numbering attribute at text node, if at paragraph
954 // style the new numbering rule is found.
955 else if ( !pRule )
957 SwTextFormatColl* pColl = pTextNd->GetTextColl();
958 if ( pColl )
960 SwNumRule* pCollRule = FindNumRulePtr(pColl->GetNumRule().GetValue());
961 if ( pCollRule && pCollRule->GetName() == pNewOrChangedNumRule->GetName() )
963 pTextNd->ResetAttr( RES_PARATR_NUMRULE );
964 bSetItem = false;
971 if ( bSetItem )
973 getIDocumentContentOperations().InsertPoolItem(aPam,
974 SwNumRuleItem(pNewOrChangedNumRule->GetName()),
975 SetAttrMode::DEFAULT, pLayout);
978 if ( bResetIndentAttrs
979 && pNewOrChangedNumRule->Get( 0 ).GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
981 const o3tl::sorted_vector<sal_uInt16> attrs{ RES_MARGIN_FIRSTLINE, RES_MARGIN_TEXTLEFT, RES_MARGIN_RIGHT };
982 ::lcl_ResetIndentAttrs(this, aPam, attrs, pLayout);
985 if (GetIDocumentUndoRedo().DoesUndo())
987 GetIDocumentUndoRedo().EndUndo( SwUndoId::INSNUM, nullptr );
990 getIDocumentState().SetModified();
992 return sListId;
995 void SwDoc::SetCounted(const SwPaM & rPam, bool bCounted,
996 SwRootFrame const*const pLayout)
998 if ( bCounted )
1000 const o3tl::sorted_vector<sal_uInt16> attrs{ RES_PARATR_LIST_ISCOUNTED };
1001 ::lcl_ResetIndentAttrs(this, rPam, attrs, pLayout);
1003 else
1005 getIDocumentContentOperations().InsertPoolItem(rPam,
1006 SfxBoolItem(RES_PARATR_LIST_ISCOUNTED, false),
1007 SetAttrMode::DEFAULT, pLayout);
1011 void SwDoc::SetNumRuleStart( const SwPosition& rPos, bool bFlag )
1013 SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
1015 if (!pTextNd)
1016 return;
1018 const SwNumRule* pRule = pTextNd->GetNumRule();
1019 if( pRule && !bFlag != !pTextNd->IsListRestart())
1021 if (GetIDocumentUndoRedo().DoesUndo())
1023 GetIDocumentUndoRedo().AppendUndo(
1024 std::make_unique<SwUndoNumRuleStart>(rPos, bFlag) );
1027 pTextNd->SetListRestart(bFlag);
1029 getIDocumentState().SetModified();
1033 void SwDoc::SetNodeNumStart( const SwPosition& rPos, sal_uInt16 nStt )
1035 SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
1037 if (!pTextNd)
1038 return;
1040 if ( !pTextNd->HasAttrListRestartValue() ||
1041 pTextNd->GetAttrListRestartValue() != nStt )
1043 if (GetIDocumentUndoRedo().DoesUndo())
1045 GetIDocumentUndoRedo().AppendUndo(
1046 std::make_unique<SwUndoNumRuleStart>(rPos, nStt) );
1048 pTextNd->SetAttrListRestartValue( nStt );
1050 getIDocumentState().SetModified();
1054 // We can only delete if the Rule is unused!
1055 bool SwDoc::DelNumRule( const OUString& rName, bool bBroadcast )
1057 sal_uInt16 nPos = FindNumRule( rName );
1059 if (nPos == USHRT_MAX)
1060 return false;
1062 if ( (*mpNumRuleTable)[ nPos ] == GetOutlineNumRule() )
1064 OSL_FAIL( "<SwDoc::DelNumRule(..)> - No deletion of outline list style. This is serious defect" );
1065 return false;
1068 if( !IsUsed( *(*mpNumRuleTable)[ nPos ] ))
1070 if (GetIDocumentUndoRedo().DoesUndo())
1072 GetIDocumentUndoRedo().AppendUndo(
1073 std::make_unique<SwUndoNumruleDelete>(*(*mpNumRuleTable)[nPos], *this));
1076 if (bBroadcast)
1077 BroadcastStyleOperation(rName, SfxStyleFamily::Pseudo,
1078 SfxHintId::StyleSheetErased);
1080 getIDocumentListsAccess().deleteListForListStyle( rName );
1081 getIDocumentListsAccess().deleteListsByDefaultListStyle( rName );
1082 // #i34097# DeleteAndDestroy deletes rName if
1083 // rName is directly taken from the numrule.
1084 const OUString aTmpName( rName );
1085 delete (*mpNumRuleTable)[ nPos ];
1086 mpNumRuleTable->erase( mpNumRuleTable->begin() + nPos );
1087 maNumRuleMap.erase(aTmpName);
1089 getIDocumentState().SetModified();
1090 return true;
1092 return false;
1095 void SwDoc::ChgNumRuleFormats( const SwNumRule& rRule )
1097 SwNumRule* pRule = FindNumRulePtr( rRule.GetName() );
1098 if( !pRule )
1099 return;
1101 SwUndoInsNum* pUndo = nullptr;
1102 if (GetIDocumentUndoRedo().DoesUndo())
1104 pUndo = new SwUndoInsNum( *pRule, rRule, *this );
1105 pUndo->GetHistory();
1106 GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) );
1108 ::lcl_ChgNumRule( *this, rRule );
1109 if (pUndo)
1111 pUndo->SetLRSpaceEndPos();
1114 getIDocumentState().SetModified();
1117 bool SwDoc::RenameNumRule(const OUString & rOldName, const OUString & rNewName,
1118 bool bBroadcast)
1120 assert(!FindNumRulePtr(rNewName));
1122 bool bResult = false;
1123 SwNumRule * pNumRule = FindNumRulePtr(rOldName);
1125 if (pNumRule)
1127 if (GetIDocumentUndoRedo().DoesUndo())
1129 GetIDocumentUndoRedo().AppendUndo(
1130 std::make_unique<SwUndoNumruleRename>(rOldName, rNewName, *this));
1133 SwNumRule::tTextNodeList aTextNodeList;
1134 pNumRule->GetTextNodeList( aTextNodeList );
1136 pNumRule->SetName( rNewName, getIDocumentListsAccess() );
1138 SwNumRuleItem aItem(rNewName);
1140 for ( SwTextNode* pTextNd : aTextNodeList )
1142 pTextNd->SetAttr(aItem);
1145 bResult = true;
1147 if (bBroadcast)
1148 BroadcastStyleOperation(rOldName, SfxStyleFamily::Pseudo,
1149 SfxHintId::StyleSheetModified);
1152 return bResult;
1155 void SwDoc::StopNumRuleAnimations( const OutputDevice* pOut )
1157 for( sal_uInt16 n = GetNumRuleTable().size(); n; )
1159 SwNumRule::tTextNodeList aTextNodeList;
1160 GetNumRuleTable()[ --n ]->GetTextNodeList( aTextNodeList );
1161 for ( SwTextNode* pTNd : aTextNodeList )
1163 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTNd);
1164 for(SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
1165 if (pFrame->HasAnimation() &&
1166 (!pFrame->GetMergedPara() || pFrame->GetMergedPara()->pParaPropsNode == pTNd))
1168 pFrame->StopAnimation( pOut );
1174 void SwDoc::ReplaceNumRule( const SwPosition& rPos,
1175 const OUString& rOldRule, const OUString& rNewRule )
1177 SwNumRule *pOldRule = FindNumRulePtr( rOldRule ),
1178 *pNewRule = FindNumRulePtr( rNewRule );
1179 if( !pOldRule || !pNewRule || pOldRule == pNewRule )
1180 return;
1182 SwUndoInsNum* pUndo = nullptr;
1183 if (GetIDocumentUndoRedo().DoesUndo())
1185 // Start/End for attributes!
1186 GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
1187 pUndo = new SwUndoInsNum( rPos, *pNewRule, rOldRule );
1188 GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
1191 SwNumRule::tTextNodeList aTextNodeList;
1192 pOldRule->GetTextNodeList( aTextNodeList );
1193 if ( !aTextNodeList.empty() )
1195 SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr );
1197 const SwTextNode* pGivenTextNode = rPos.GetNode().GetTextNode();
1198 SwNumRuleItem aRule( rNewRule );
1199 for ( SwTextNode* pTextNd : aTextNodeList )
1201 if ( pGivenTextNode &&
1202 pGivenTextNode->GetListId() == pTextNd->GetListId() )
1204 aRegH.RegisterInModify( pTextNd, *pTextNd );
1206 pTextNd->SetAttr( aRule );
1207 pTextNd->NumRuleChgd();
1210 GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
1211 getIDocumentState().SetModified();
1215 namespace
1217 struct ListStyleData
1219 SwNumRule* pReplaceNumRule;
1220 bool bCreateNewList;
1221 OUString sListId;
1223 ListStyleData()
1224 : pReplaceNumRule( nullptr ),
1225 bCreateNewList( false )
1230 void SwDoc::MakeUniqueNumRules(const SwPaM & rPaM)
1232 OSL_ENSURE( &rPaM.GetDoc() == this, "need same doc" );
1234 std::map<SwNumRule *, ListStyleData> aMyNumRuleMap;
1236 bool bFirst = true;
1238 const SwNodeOffset nStt = rPaM.Start()->GetNodeIndex();
1239 const SwNodeOffset nEnd = rPaM.End()->GetNodeIndex();
1240 for (SwNodeOffset n = nStt; n <= nEnd; n++)
1242 SwTextNode * pCNd = GetNodes()[n]->GetTextNode();
1244 if (pCNd)
1246 SwNumRule * pRule = pCNd->GetNumRule();
1248 if (pRule && pRule->IsAutoRule() && ! pRule->IsOutlineRule())
1250 ListStyleData aListStyleData = aMyNumRuleMap[pRule];
1252 if ( aListStyleData.pReplaceNumRule == nullptr )
1254 if (bFirst)
1256 SwPosition aPos(*pCNd);
1257 aListStyleData.pReplaceNumRule =
1258 const_cast<SwNumRule *>
1259 (SearchNumRule( aPos, false, pCNd->HasNumber(),
1260 false, 0,
1261 aListStyleData.sListId, nullptr, true ));
1264 if ( aListStyleData.pReplaceNumRule == nullptr )
1266 aListStyleData.pReplaceNumRule = new SwNumRule(*pRule);
1267 aListStyleData.pReplaceNumRule->SetName( GetUniqueNumRuleName(), getIDocumentListsAccess() );
1268 aListStyleData.bCreateNewList = true;
1271 aMyNumRuleMap[pRule] = aListStyleData;
1274 SwPaM aPam(*pCNd);
1276 SetNumRule( aPam,
1277 *aListStyleData.pReplaceNumRule,
1278 aListStyleData.bCreateNewList,
1279 nullptr,
1280 aListStyleData.sListId );
1281 if ( aListStyleData.bCreateNewList )
1283 aListStyleData.bCreateNewList = false;
1284 aListStyleData.sListId = pCNd->GetListId();
1285 aMyNumRuleMap[pRule] = aListStyleData;
1288 bFirst = false;
1294 bool SwDoc::NoNum( const SwPaM& rPam )
1297 bool bRet = getIDocumentContentOperations().SplitNode( *rPam.GetPoint(), false );
1298 // Do we actually use Numbering at all?
1299 if( bRet )
1301 // Set NoNum and Update
1302 SwTextNode* pNd = rPam.GetPoint()->GetNode().GetTextNode();
1303 const SwNumRule* pRule = pNd->GetNumRule();
1304 if( pRule )
1306 pNd->SetCountedInList(false);
1308 getIDocumentState().SetModified();
1310 else
1311 bRet = false; // no Numbering or just always true?
1313 return bRet;
1316 void SwDoc::DelNumRules(const SwPaM& rPam, SwRootFrame const*const pLayout)
1318 SwPaM aPam(rPam, nullptr);
1319 ExpandPamForParaPropsNodes(aPam, pLayout);
1320 SwNodeOffset nStt = aPam.Start()->GetNodeIndex();
1321 SwNodeOffset const nEnd = aPam.End()->GetNodeIndex();
1323 SwUndoDelNum* pUndo;
1324 if (GetIDocumentUndoRedo().DoesUndo())
1326 pUndo = new SwUndoDelNum( aPam );
1327 GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo));
1329 else
1330 pUndo = nullptr;
1332 SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr );
1334 SwNumRuleItem aEmptyRule;
1335 const SwNode* pOutlNd = nullptr;
1336 for( ; nStt <= nEnd; ++nStt )
1338 SwTextNode* pTNd = GetNodes()[ nStt ]->GetTextNode();
1339 if (pLayout && pTNd)
1341 pTNd = sw::GetParaPropsNode(*pLayout, *pTNd);
1343 SwNumRule* pNumRuleOfTextNode = pTNd ? pTNd->GetNumRule() : nullptr;
1344 if ( pTNd && pNumRuleOfTextNode )
1346 // recognize changes of attribute for undo
1347 aRegH.RegisterInModify( pTNd, *pTNd );
1349 if( pUndo )
1350 pUndo->AddNode( *pTNd );
1352 // directly set list style attribute is reset, otherwise empty
1353 // list style is applied
1354 const SfxItemSet* pAttrSet = pTNd->GetpSwAttrSet();
1355 if ( pAttrSet &&
1356 pAttrSet->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
1357 pTNd->ResetAttr( RES_PARATR_NUMRULE );
1358 else
1359 pTNd->SetAttr( aEmptyRule );
1361 pTNd->ResetAttr( RES_PARATR_LIST_ID );
1362 pTNd->ResetAttr( RES_PARATR_LIST_LEVEL );
1363 pTNd->ResetAttr( RES_PARATR_LIST_ISRESTART );
1364 pTNd->ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
1365 pTNd->ResetAttr( RES_PARATR_LIST_ISCOUNTED );
1367 if( RES_CONDTXTFMTCOLL == pTNd->GetFormatColl()->Which() )
1369 pTNd->ChkCondColl();
1371 else if( !pOutlNd &&
1372 static_cast<SwTextFormatColl*>(pTNd->GetFormatColl())->IsAssignedToListLevelOfOutlineStyle() )
1374 pOutlNd = pTNd;
1379 // Finally, update all
1380 UpdateNumRule();
1382 if( pOutlNd )
1383 GetNodes().UpdateOutlineIdx( *pOutlNd );
1386 void SwDoc::InvalidateNumRules()
1388 for (size_t n = 0; n < mpNumRuleTable->size(); ++n)
1389 (*mpNumRuleTable)[n]->SetInvalidRule(true);
1392 // To the next/preceding Bullet at the same Level
1393 static bool lcl_IsNumOk( sal_uInt8 nSrchNum, sal_uInt8& rLower, sal_uInt8& rUpper,
1394 bool bOverUpper, sal_uInt8 nNumber )
1396 OSL_ENSURE( nNumber < MAXLEVEL,
1397 "<lcl_IsNumOk(..)> - misusage of method" );
1399 bool bRet = false;
1401 if( bOverUpper ? nSrchNum == nNumber : nSrchNum >= nNumber )
1402 bRet = true;
1403 else if( nNumber > rLower )
1404 rLower = nNumber;
1405 else if( nNumber < rUpper )
1406 rUpper = nNumber;
1408 return bRet;
1411 static bool lcl_IsValidPrevNextNumNode( const SwNodeIndex& rIdx )
1413 bool bRet = false;
1414 const SwNode& rNd = rIdx.GetNode();
1415 switch( rNd.GetNodeType() )
1417 case SwNodeType::End:
1418 bRet = SwTableBoxStartNode == rNd.StartOfSectionNode()->GetStartNodeType() ||
1419 rNd.StartOfSectionNode()->IsSectionNode();
1420 break;
1422 case SwNodeType::Start:
1423 bRet = SwTableBoxStartNode == static_cast<const SwStartNode&>(rNd).GetStartNodeType();
1424 break;
1426 case SwNodeType::Section: // that one's valid, so proceed
1427 bRet = true;
1428 break;
1430 default: break;
1432 return bRet;
1435 namespace sw {
1437 void
1438 GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout)
1440 if (pLayout && pLayout->HasMergedParas())
1442 if (rIndex.GetNode().IsTextNode())
1444 if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None)
1446 // not a tracked row deletion in Hide Changes mode
1447 if (SwContentFrame* pFrame = rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))
1449 if (sw::MergedPara* pMerged = static_cast<SwTextFrame*>(pFrame)->GetMergedPara())
1451 rIndex = pMerged->pFirstNode->GetIndex();
1456 else if (rIndex.GetNode().IsEndNode())
1458 if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1460 rIndex = *rIndex.GetNode().StartOfSectionNode();
1461 assert(rIndex.GetNode().IsTableNode());
1465 --rIndex;
1466 if (pLayout && rIndex.GetNode().IsTextNode())
1468 rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode());
1472 void
1473 GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout)
1475 if (pLayout && pLayout->HasMergedParas())
1477 if (rIndex.GetNode().IsTextNode())
1479 if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None)
1481 if (SwContentFrame* pFrame = rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))
1483 if (sw::MergedPara* pMerged = static_cast<SwTextFrame*>(pFrame)->GetMergedPara())
1485 rIndex = pMerged->pLastNode->GetIndex();
1490 else if (rIndex.GetNode().IsTableNode())
1492 if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1494 rIndex = *rIndex.GetNode().EndOfSectionNode();
1498 ++rIndex;
1499 if (pLayout && rIndex.GetNode().IsTextNode())
1501 rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode());
1505 } // namespace sw
1507 static bool lcl_GotoNextPrevNum( SwPosition& rPos, bool bNext,
1508 bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower,
1509 SwRootFrame const*const pLayout)
1511 const SwTextNode* pNd = rPos.GetNode().GetTextNode();
1512 if (pNd && pLayout)
1514 pNd = sw::GetParaPropsNode(*pLayout, *pNd);
1516 if( !pNd || nullptr == pNd->GetNumRule() )
1517 return false;
1519 sal_uInt8 nSrchNum = static_cast<sal_uInt8>(pNd->GetActualListLevel());
1521 SwNodeIndex aIdx( rPos.GetNode() );
1522 if( ! pNd->IsCountedInList() )
1524 bool bError = false;
1525 do {
1526 sw::GotoPrevLayoutTextFrame(aIdx, pLayout);
1527 if( aIdx.GetNode().IsTextNode() )
1529 pNd = aIdx.GetNode().GetTextNode();
1530 const SwNumRule* pRule = pNd->GetNumRule();
1532 if( pRule )
1534 sal_uInt8 nTmpNum = static_cast<sal_uInt8>(pNd->GetActualListLevel());
1535 if( pNd->IsCountedInList() || (nTmpNum < nSrchNum ) )
1536 break; // found it!
1538 else
1539 bError = true;
1541 else
1542 bError = !lcl_IsValidPrevNextNumNode( aIdx );
1544 } while( !bError );
1545 if( bError )
1546 return false;
1549 sal_uInt8 nLower = nSrchNum, nUpper = nSrchNum;
1550 bool bRet = false;
1552 const SwTextNode* pLast;
1553 if( bNext )
1555 sw::GotoNextLayoutTextFrame(aIdx, pLayout);
1556 pLast = pNd;
1558 else
1560 sw::GotoPrevLayoutTextFrame(aIdx, pLayout);
1561 pLast = nullptr;
1564 while( bNext ? ( aIdx.GetIndex() < aIdx.GetNodes().Count() - 1 )
1565 : aIdx.GetIndex() != SwNodeOffset(0) )
1567 if( aIdx.GetNode().IsTextNode() )
1569 pNd = aIdx.GetNode().GetTextNode();
1570 const SwNumRule* pRule = pNd->GetNumRule();
1571 if( pRule )
1573 if( ::lcl_IsNumOk( nSrchNum, nLower, nUpper, bOverUpper,
1574 static_cast<sal_uInt8>(pNd->GetActualListLevel()) ))
1576 rPos.Assign(aIdx);
1577 bRet = true;
1578 break;
1580 else
1581 pLast = pNd;
1583 else
1584 break;
1586 else if( !lcl_IsValidPrevNextNumNode( aIdx ))
1587 break;
1589 if( bNext )
1590 sw::GotoNextLayoutTextFrame(aIdx, pLayout);
1591 else
1592 sw::GotoPrevLayoutTextFrame(aIdx, pLayout);
1595 if( !bRet && !bOverUpper && pLast ) // do not iterate over higher numbers, but still to the end
1597 if( bNext )
1598 rPos.Assign(aIdx);
1599 else
1600 rPos.Assign( *pLast );
1601 bRet = true;
1604 if( bRet )
1606 if( pUpper )
1607 *pUpper = nUpper;
1608 if( pLower )
1609 *pLower = nLower;
1611 return bRet;
1614 bool SwDoc::GotoNextNum(SwPosition& rPos, SwRootFrame const*const pLayout,
1615 bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower)
1617 return ::lcl_GotoNextPrevNum(rPos, true, bOverUpper, pUpper, pLower, pLayout);
1620 const SwNumRule * SwDoc::SearchNumRule(const SwPosition & rPos,
1621 const bool bForward,
1622 const bool bNum,
1623 const bool bOutline,
1624 int nNonEmptyAllowed,
1625 OUString& sListId,
1626 SwRootFrame const* pLayout,
1627 const bool bInvestigateStartNode)
1629 const SwNumRule * pResult = nullptr;
1630 SwTextNode * pTextNd = rPos.GetNode().GetTextNode();
1631 if (pLayout)
1633 pTextNd = sw::GetParaPropsNode(*pLayout, rPos.GetNode());
1635 SwNode * pStartFromNode = pTextNd;
1637 if (pTextNd)
1639 SwNodeIndex aIdx(rPos.GetNode());
1641 // - the start node has also been investigated, if requested.
1642 const SwNode * pNode = nullptr;
1645 if ( !bInvestigateStartNode )
1647 if (bForward)
1648 sw::GotoNextLayoutTextFrame(aIdx, pLayout);
1649 else
1650 sw::GotoPrevLayoutTextFrame(aIdx, pLayout);
1653 if (aIdx.GetNode().IsTextNode())
1655 pTextNd = aIdx.GetNode().GetTextNode();
1657 const SwNumRule * pNumRule = pTextNd->GetNumRule();
1658 if (pNumRule)
1660 if ( ( pNumRule->IsOutlineRule() == bOutline ) &&
1661 ( ( bNum && pNumRule->Get(0).IsEnumeration()) ||
1662 ( !bNum && pNumRule->Get(0).IsItemize() ) ) ) // #i22362#, #i29560#
1664 pResult = pTextNd->GetNumRule();
1665 // provide also the list id, to which the text node belongs.
1666 sListId = pTextNd->GetListId();
1669 break;
1671 else if (pTextNd->Len() > 0 || nullptr != pTextNd->GetNumRule())
1673 if (nNonEmptyAllowed == 0)
1674 break;
1676 nNonEmptyAllowed--;
1678 if (nNonEmptyAllowed < 0)
1679 nNonEmptyAllowed = -1;
1683 if ( bInvestigateStartNode )
1685 if (bForward)
1686 sw::GotoNextLayoutTextFrame(aIdx, pLayout);
1687 else
1688 sw::GotoPrevLayoutTextFrame(aIdx, pLayout);
1691 pNode = &aIdx.GetNode();
1693 while (pNode != GetNodes().DocumentSectionStartNode(pStartFromNode) &&
1694 pNode != GetNodes().DocumentSectionEndNode(pStartFromNode));
1697 return pResult;
1700 bool SwDoc::GotoPrevNum(SwPosition& rPos, SwRootFrame const*const pLayout,
1701 bool bOverUpper)
1703 return ::lcl_GotoNextPrevNum(rPos, false, bOverUpper, nullptr, nullptr, pLayout);
1706 bool SwDoc::NumUpDown(const SwPaM& rPam, bool bDown, SwRootFrame const*const pLayout)
1708 SwPaM aPam(rPam, nullptr);
1709 ExpandPamForParaPropsNodes(aPam, pLayout);
1710 SwNodeOffset nStt = aPam.Start()->GetNodeIndex();
1711 SwNodeOffset const nEnd = aPam.End()->GetNodeIndex();
1713 // -> outline nodes are promoted or demoted differently
1714 bool bOnlyOutline = true;
1715 bool bOnlyNonOutline = true;
1716 for (SwNodeOffset n = nStt; n <= nEnd; n++)
1718 SwTextNode * pTextNd = GetNodes()[n]->GetTextNode();
1720 if (pTextNd)
1722 if (pLayout)
1724 pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd);
1726 SwNumRule * pRule = pTextNd->GetNumRule();
1728 if (pRule)
1730 if (pRule->IsOutlineRule())
1731 bOnlyNonOutline = false;
1732 else
1733 bOnlyOutline = false;
1738 bool bRet = true;
1739 sal_Int8 nDiff = bDown ? 1 : -1;
1741 if (bOnlyOutline)
1742 bRet = OutlineUpDown(rPam, nDiff, pLayout);
1743 else if (bOnlyNonOutline)
1745 /* #i24560#
1746 Only promote or demote if all selected paragraphs are
1747 promotable resp. demotable.
1749 for (SwNodeOffset nTmp = nStt; nTmp <= nEnd; ++nTmp)
1751 SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode();
1753 // Make code robust: consider case that the node doesn't denote a
1754 // text node.
1755 if ( pTNd )
1757 if (pLayout)
1759 pTNd = sw::GetParaPropsNode(*pLayout, *pTNd);
1762 SwNumRule * pRule = pTNd->GetNumRule();
1764 if (pRule)
1766 sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel());
1767 if( (-1 == nDiff && 0 >= nLevel) ||
1768 (1 == nDiff && MAXLEVEL - 1 <= nLevel))
1769 bRet = false;
1774 if( bRet )
1776 if (GetIDocumentUndoRedo().DoesUndo())
1778 GetIDocumentUndoRedo().AppendUndo(
1779 std::make_unique<SwUndoNumUpDown>(aPam, nDiff) );
1782 SwTextNode* pPrev = nullptr;
1783 for(SwNodeOffset nTmp = nStt; nTmp <= nEnd; ++nTmp )
1785 SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode();
1787 if( pTNd)
1789 if (pLayout)
1791 pTNd = sw::GetParaPropsNode(*pLayout, *pTNd);
1792 if (pTNd == pPrev)
1794 continue;
1796 pPrev = pTNd;
1799 SwNumRule * pRule = pTNd->GetNumRule();
1801 if (pRule)
1803 sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel());
1804 nLevel = nLevel + nDiff;
1806 pTNd->SetAttrListLevel(nLevel);
1811 ChkCondColls();
1812 getIDocumentState().SetModified();
1816 return bRet;
1819 // this function doesn't contain any numbering-related code, but it is
1820 // primarily called to move numbering-relevant paragraphs around, hence
1821 // it will expand its selection to include full SwTextFrames.
1822 bool SwDoc::MoveParagraph(SwPaM& rPam, SwNodeOffset nOffset, bool const bIsOutlMv)
1824 MakeAllOutlineContentTemporarilyVisible a(this);
1826 // sw_redlinehide: as long as a layout with Hide mode exists, only
1827 // move nodes that have merged frames *completely*
1828 SwRootFrame const* pLayout(nullptr);
1829 for (SwRootFrame const*const pLay : GetAllLayouts())
1831 if (pLay->HasMergedParas())
1833 pLayout = pLay;
1836 if (pLayout)
1838 std::pair<SwTextNode *, SwTextNode *> nodes(
1839 sw::GetFirstAndLastNode(*pLayout, rPam.Start()->GetNode()));
1840 if (nodes.first && nodes.first != &rPam.Start()->GetNode())
1842 assert(nodes.second);
1843 if (nOffset < SwNodeOffset(0))
1845 nOffset += rPam.Start()->GetNodeIndex() - nodes.first->GetIndex();
1846 if (SwNodeOffset(0) <= nOffset) // hack: there are callers that know what
1847 { // node they want; those should never need
1848 nOffset = SwNodeOffset(-1); // this; other callers just pass in -1
1849 } // and those should still move
1851 if (!rPam.HasMark())
1853 rPam.SetMark();
1855 assert(nodes.first->GetIndex() < rPam.Start()->GetNodeIndex());
1856 rPam.Start()->Assign(*nodes.first);
1858 nodes = sw::GetFirstAndLastNode(*pLayout, rPam.End()->GetNode());
1859 if (nodes.second && nodes.second != &rPam.End()->GetNode())
1861 assert(nodes.first);
1862 if (SwNodeOffset(0) < nOffset)
1864 nOffset -= nodes.second->GetIndex() - rPam.End()->GetNodeIndex();
1865 if (nOffset <= SwNodeOffset(0)) // hack: there are callers that know what
1866 { // node they want; those should never need
1867 nOffset = SwNodeOffset(+1); // this; other callers just pass in +1
1868 } // and those should still move
1870 if (!rPam.HasMark())
1872 rPam.SetMark();
1874 assert(rPam.End()->GetNodeIndex() < nodes.second->GetIndex());
1875 // until end, otherwise Impl will detect overlapping redline
1876 rPam.End()->Assign(*nodes.second, nodes.second->GetTextNode()->Len());
1879 if (nOffset > SwNodeOffset(0))
1880 { // sw_redlinehide: avoid moving into delete redline, skip forward
1881 if (GetNodes().GetEndOfContent().GetIndex() <= rPam.End()->GetNodeIndex() + nOffset)
1883 return false; // can't move
1885 SwNode const* pNode(GetNodes()[rPam.End()->GetNodeIndex() + nOffset + 1]);
1886 if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None
1887 && pNode->GetRedlineMergeFlag() != SwNode::Merge::First)
1889 for ( ; ; ++nOffset)
1891 pNode = GetNodes()[rPam.End()->GetNodeIndex() + nOffset];
1892 if (pNode->IsTextNode())
1894 nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode());
1895 assert(nodes.first && nodes.second);
1896 nOffset += nodes.second->GetIndex() - pNode->GetIndex();
1897 // on last; will be incremented below to behind-last
1898 break;
1903 else
1904 { // sw_redlinehide: avoid moving into delete redline, skip backward
1905 if (rPam.Start()->GetNodeIndex() + nOffset < SwNodeOffset(1))
1907 return false; // can't move
1909 SwNode const* pNode(GetNodes()[rPam.Start()->GetNodeIndex() + nOffset]);
1910 if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None
1911 && pNode->GetRedlineMergeFlag() != SwNode::Merge::First)
1913 for ( ; ; --nOffset)
1915 pNode = GetNodes()[rPam.Start()->GetNodeIndex() + nOffset];
1916 if (pNode->IsTextNode())
1918 nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode());
1919 assert(nodes.first && nodes.second);
1920 nOffset -= pNode->GetIndex() - nodes.first->GetIndex();
1921 // on first
1922 break;
1928 return MoveParagraphImpl(rPam, nOffset, bIsOutlMv, pLayout);
1931 bool SwDoc::MoveParagraphImpl(SwPaM& rPam, SwNodeOffset const nOffset,
1932 bool const bIsOutlMv, SwRootFrame const*const pLayout)
1934 auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*
1936 SwNodeOffset nStIdx = pStt->GetNodeIndex();
1937 SwNodeOffset nEndIdx = pEnd->GetNodeIndex();
1939 // Here are some sophisticated checks whether the wished PaM will be moved or not.
1940 // For moving outlines (bIsOutlMv) I've already done some checks, so here are two different
1941 // checks...
1942 SwNode *pTmp1;
1943 SwNode *pTmp2;
1944 if( bIsOutlMv )
1946 // For moving chapters (outline) the following reason will deny the move:
1947 // if a start node is inside the moved range and its end node outside or vice versa.
1948 // If a start node is the first moved paragraph, its end node has to be within the moved
1949 // range, too (e.g. as last node).
1950 // If an end node is the last node of the moved range, its start node has to be a part of
1951 // the moved section, too.
1952 pTmp1 = GetNodes()[ nStIdx ];
1953 if( pTmp1->IsStartNode() )
1955 // coverity[copy_paste_error : FALSE] - First is a start node
1956 pTmp2 = pTmp1->EndOfSectionNode();
1957 if( pTmp2->GetIndex() > nEndIdx )
1958 return false; // Its end node is behind the moved range
1960 pTmp1 = pTmp1->StartOfSectionNode()->EndOfSectionNode();
1961 if( pTmp1->GetIndex() <= nEndIdx )
1962 return false; // End node inside but start node before moved range => no.
1963 pTmp1 = GetNodes()[ nEndIdx ];
1964 if( pTmp1->IsEndNode() )
1965 { // The last one is an end node
1966 pTmp1 = pTmp1->StartOfSectionNode();
1967 if( pTmp1->GetIndex() < nStIdx )
1968 return false; // Its start node is before the moved range.
1970 pTmp1 = pTmp1->StartOfSectionNode();
1971 if( pTmp1->GetIndex() >= nStIdx )
1972 return false; // A start node which ends behind the moved range => no.
1975 SwNodeOffset nInStIdx, nInEndIdx;
1976 SwNodeOffset nOffs = nOffset;
1977 if( nOffset > SwNodeOffset(0) )
1979 nInEndIdx = nEndIdx;
1980 nEndIdx += nOffset;
1981 ++nOffs;
1983 else
1985 // Impossible to move to negative index
1986 if( abs( nOffset ) > nStIdx)
1987 return false;
1989 nInEndIdx = nStIdx - 1;
1990 nStIdx += nOffset;
1992 nInStIdx = nInEndIdx + 1;
1993 // The following paragraphs shall be swapped:
1994 // Swap [ nStIdx, nInEndIdx ] with [ nInStIdx, nEndIdx ]
1996 if( nEndIdx >= GetNodes().GetEndOfContent().GetIndex() )
1997 return false;
1999 if( !bIsOutlMv )
2000 { // And here the restrictions for moving paragraphs other than chapters (outlines)
2001 // The plan is to exchange [nStIdx,nInEndIdx] and [nStartIdx,nEndIdx]
2002 // It will checked if the both "start" nodes as well as the both "end" notes belongs to
2003 // the same start-end-section. This is more restrictive than the conditions checked above.
2004 // E.g. a paragraph will not escape from a section or be inserted to another section.
2005 pTmp1 = GetNodes()[ nStIdx ]->StartOfSectionNode();
2006 pTmp2 = GetNodes()[ nInStIdx ]->StartOfSectionNode();
2007 if( pTmp1 != pTmp2 )
2008 return false; // "start" nodes in different sections
2009 pTmp1 = GetNodes()[ nEndIdx ];
2010 bool bIsEndNode = pTmp1->IsEndNode();
2011 if( !pTmp1->IsStartNode() )
2013 pTmp1 = pTmp1->StartOfSectionNode();
2014 if( bIsEndNode ) // For end nodes the first start node is of course inside the range,
2015 pTmp1 = pTmp1->StartOfSectionNode(); // I've to check the start node of the start node.
2017 pTmp1 = pTmp1->EndOfSectionNode();
2018 pTmp2 = GetNodes()[ nInEndIdx ];
2019 if( !pTmp2->IsStartNode() )
2021 bIsEndNode = pTmp2->IsEndNode();
2022 pTmp2 = pTmp2->StartOfSectionNode();
2023 if( bIsEndNode )
2024 pTmp2 = pTmp2->StartOfSectionNode();
2026 pTmp2 = pTmp2->EndOfSectionNode();
2027 if( pTmp1 != pTmp2 )
2028 return false; // The "end" notes are in different sections
2031 // Test for Redlining - Can the Selection be moved at all, actually?
2032 if( !getIDocumentRedlineAccess().IsIgnoreRedline() )
2034 SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->GetNode(), RedlineType::Delete );
2035 if( SwRedlineTable::npos != nRedlPos )
2037 SwContentNode* pCNd = pEnd->GetNode().GetContentNode();
2038 SwPosition aStPos( pStt->GetNode() );
2039 SwPosition aEndPos( pEnd->GetNode(), pCNd, pCNd ? pCNd->Len() : 1 );
2040 bool bCheckDel = true;
2042 // There is a some Redline Delete Object for the range
2043 for( ; nRedlPos < getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedlPos )
2045 const SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2046 if( !bCheckDel || RedlineType::Delete == pTmp->GetType() )
2048 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
2049 switch( ComparePosition( *pRStt, *pREnd, aStPos, aEndPos ))
2051 case SwComparePosition::CollideStart:
2052 case SwComparePosition::Behind: // Pos1 comes after Pos2
2053 nRedlPos = getIDocumentRedlineAccess().GetRedlineTable().size();
2054 break;
2056 case SwComparePosition::CollideEnd:
2057 case SwComparePosition::Before: // Pos1 comes before Pos2
2058 break;
2059 case SwComparePosition::Inside: // Pos1 is completely inside Pos2
2060 // that's valid, but check all following for overlapping
2061 bCheckDel = false;
2062 break;
2064 case SwComparePosition::Outside: // Pos2 is completely inside Pos1
2065 case SwComparePosition::Equal: // Pos1 is equal to Pos2
2066 case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 in the beginning
2067 case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at the end
2068 return false;
2076 // Send DataChanged before moving. We then can detect
2077 // which objects are still in the range.
2078 // After the move they could come before/after the
2079 // Position.
2080 SwDataChanged aTmp( rPam );
2083 SwNodeIndex aIdx( nOffset > SwNodeOffset(0) ? pEnd->GetNode() : pStt->GetNode(), nOffs );
2084 SwNodeRange aMvRg( pStt->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(+1) );
2086 SwRangeRedline* pOwnRedl = nullptr;
2087 if( getIDocumentRedlineAccess().IsRedlineOn() )
2089 // If the range is completely in the own Redline, we can move it!
2090 SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->GetNode(), RedlineType::Insert );
2091 if( SwRedlineTable::npos != nRedlPos )
2093 SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
2094 auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition*
2095 SwRangeRedline aTmpRedl( RedlineType::Insert, rPam );
2096 const SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode();
2097 // Is completely in the range and is the own Redline too?
2098 if( aTmpRedl.IsOwnRedline( *pTmp ) &&
2099 (pRStt->GetNode() < pStt->GetNode() ||
2100 (pRStt->GetNode() == pStt->GetNode() && !pRStt->GetContentIndex()) ) &&
2101 (pEnd->GetNode() < pREnd->GetNode() ||
2102 (pEnd->GetNode() == pREnd->GetNode() &&
2103 pCEndNd ? pREnd->GetContentIndex() == pCEndNd->Len()
2104 : !pREnd->GetContentIndex() )) )
2106 pOwnRedl = pTmp;
2107 if( nRedlPos + 1 < getIDocumentRedlineAccess().GetRedlineTable().size() )
2109 pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos+1 ];
2110 if( *pTmp->Start() == *pREnd )
2111 // then don't!
2112 pOwnRedl = nullptr;
2115 if( pOwnRedl &&
2116 ( pRStt->GetNode() > aIdx.GetNode() || aIdx > pREnd->GetNode() ||
2117 // pOwnRedl doesn't start at the beginning of a node, so it's not
2118 // possible to resize it to contain the line moved before it
2119 ( pRStt->GetNode() == aIdx.GetNode() && pRStt->GetContentIndex() > 0 ) ) )
2121 // it's not in itself, so don't move it
2122 pOwnRedl = nullptr;
2127 if( !pOwnRedl )
2129 GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
2131 // First the Insert, then the Delete
2132 SwPosition aInsPos( aIdx );
2134 std::optional<SwPaM> oPam( std::in_place, pStt->GetNode(), 0, aMvRg.aEnd.GetNode(), 0 );
2136 SwPaM& rOrigPam(rPam);
2137 rOrigPam.DeleteMark();
2138 rOrigPam.GetPoint()->Assign(aIdx.GetIndex() - 1);
2140 bool bDelLastPara = !aInsPos.GetNode().IsContentNode();
2141 SwNodeOffset nOrigIdx = aIdx.GetIndex();
2143 /* When copying to a non-content node Copy will
2144 insert a paragraph before that node and insert before
2145 that inserted node. Copy creates an SwUndoInserts that
2146 does not cover the extra paragraph. Thus we insert the
2147 extra paragraph ourselves, _with_ correct undo
2148 information. */
2149 if (bDelLastPara)
2151 /* aInsPos points to the non-content node. Move it to
2152 the previous content node. */
2153 SwPaM aInsPam(aInsPos);
2154 const bool bMoved = aInsPam.Move(fnMoveBackward);
2155 OSL_ENSURE(bMoved, "No content node found!");
2157 if (bMoved)
2159 /* Append the new node after the content node
2160 found. The new position to insert the moved
2161 paragraph at is before the inserted
2162 paragraph. */
2163 getIDocumentContentOperations().AppendTextNode(*aInsPam.GetPoint());
2164 aInsPos = *aInsPam.GetPoint();
2168 --aIdx; // move before insertion
2170 // adjust empty nodes later
2171 SwTextNode const*const pIsEmptyNode(nOffset < SwNodeOffset(0)
2172 ? aInsPos.GetNode().GetTextNode()
2173 : aIdx.GetNode().GetTextNode());
2174 bool bIsEmptyNode = pIsEmptyNode && pIsEmptyNode->Len() == 0;
2176 getIDocumentContentOperations().CopyRange(*oPam, aInsPos, SwCopyFlags::CheckPosInFly);
2178 // now delete all the delete redlines that were copied
2179 #ifndef NDEBUG
2180 size_t nRedlines(getIDocumentRedlineAccess().GetRedlineTable().size());
2181 #endif
2182 if (nOffset > SwNodeOffset(0))
2183 assert(oPam->End()->GetNodeIndex() - oPam->Start()->GetNodeIndex() + nOffset == aInsPos.GetNodeIndex() - oPam->End()->GetNodeIndex());
2184 else
2185 assert(oPam->Start()->GetNodeIndex() - oPam->End()->GetNodeIndex() + nOffset == aInsPos.GetNodeIndex() - oPam->End()->GetNodeIndex());
2186 SwRedlineTable::size_type i;
2187 getIDocumentRedlineAccess().GetRedline(*oPam->End(), &i);
2188 for ( ; 0 < i; --i)
2189 { // iterate backwards and offset via the start nodes difference
2190 SwRangeRedline const*const pRedline = getIDocumentRedlineAccess().GetRedlineTable()[i - 1];
2191 if (*pRedline->End() < *oPam->Start())
2193 break;
2195 if (pRedline->GetType() == RedlineType::Delete &&
2196 // tdf#145066 skip full-paragraph deletion which was jumped over
2197 // in Show Changes mode to avoid of deleting an extra row
2198 *oPam->Start() <= *pRedline->Start())
2200 SwRangeRedline* pNewRedline;
2202 SwPaM pam(*pRedline, nullptr);
2203 SwNodeOffset const nCurrentOffset(
2204 nOrigIdx - oPam->Start()->GetNodeIndex());
2205 pam.GetPoint()->Assign(pam.GetPoint()->GetNodeIndex() + nCurrentOffset,
2206 pam.GetPoint()->GetContentIndex());
2207 pam.GetMark()->Assign(pam.GetMark()->GetNodeIndex() + nCurrentOffset,
2208 pam.GetMark()->GetContentIndex());
2210 pNewRedline = new SwRangeRedline( RedlineType::Delete, pam );
2212 // note: effectively this will DeleteAndJoin the pam!
2213 getIDocumentRedlineAccess().AppendRedline(pNewRedline, true);
2214 assert(getIDocumentRedlineAccess().GetRedlineTable().size() <= nRedlines);
2218 if( bDelLastPara )
2220 // We need to remove the last empty Node again
2221 aIdx = aInsPos.GetNode();
2222 SwContentNode* pCNd = SwNodes::GoPrevious( &aInsPos );
2223 if (pCNd)
2224 aInsPos.AssignEndIndex( *pCNd );
2226 // All, that are in the to-be-deleted Node, need to be
2227 // moved to the next Node
2228 for(SwRangeRedline* pTmp : getIDocumentRedlineAccess().GetRedlineTable())
2230 SwPosition* pPos = &pTmp->GetBound();
2231 if( pPos->GetNode() == aIdx.GetNode() )
2233 pPos->Adjust(SwNodeOffset(1));
2235 pPos = &pTmp->GetBound(false);
2236 if( pPos->GetNode() == aIdx.GetNode() )
2238 pPos->Adjust(SwNodeOffset(1));
2241 CorrRel( aIdx.GetNode(), aInsPos );
2243 if (pCNd)
2244 pCNd->JoinNext();
2247 rOrigPam.GetPoint()->Adjust(SwNodeOffset(1));
2248 assert(*oPam->GetMark() < *oPam->GetPoint());
2249 if (oPam->GetPoint()->GetNode().IsEndNode())
2250 { // ensure redline ends on content node
2251 oPam->GetPoint()->Adjust(SwNodeOffset(-1));
2252 assert(oPam->GetPoint()->GetNode().IsTextNode());
2253 SwTextNode *const pNode(oPam->GetPoint()->GetNode().GetTextNode());
2254 oPam->GetPoint()->SetContent(pNode->Len());
2257 RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags();
2258 if (GetIDocumentUndoRedo().DoesUndo())
2260 // this should no longer happen in calls from the UI but maybe via API
2261 SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask,
2262 "sw.core", "redlines will be moved in DeleteAndJoin");
2264 getIDocumentRedlineAccess().SetRedlineFlags(
2265 RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete );
2266 GetIDocumentUndoRedo().AppendUndo(
2267 std::make_unique<SwUndoRedlineDelete>(*oPam, SwUndoId::DELETE));
2270 SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, *oPam );
2272 // prevent assertion from aPam's target being deleted
2273 SwNodeIndex bound1(oPam->GetBound().GetNode());
2274 SwNodeIndex bound2(oPam->GetBound(false).GetNode());
2275 oPam.reset();
2277 getIDocumentRedlineAccess().AppendRedline( pNewRedline, true );
2279 oPam.emplace(bound1, bound2);
2280 sw::UpdateFramesForAddDeleteRedline(*this, *oPam);
2282 // avoid setting empty nodes to tracked insertion
2283 if ( bIsEmptyNode )
2285 SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable();
2286 SwRedlineTable::size_type nRedlPosWithEmpty =
2287 getIDocumentRedlineAccess().GetRedlinePos( pStt->GetNode(), RedlineType::Insert );
2288 if ( SwRedlineTable::npos != nRedlPosWithEmpty )
2290 pOwnRedl = rTable[nRedlPosWithEmpty];
2291 SwPosition *pRPos = nOffset < SwNodeOffset(0) ? pOwnRedl->End() : pOwnRedl->Start();
2292 SwNodeIndex aIdx2 ( pRPos->GetNode() );
2293 SwTextNode const*const pEmptyNode0(aIdx2.GetNode().GetTextNode());
2294 if ( nOffset < SwNodeOffset(0) )
2296 // move up
2297 --aIdx2;
2298 SwTextNode const*const pEmptyNode(aIdx2.GetNode().GetTextNode());
2299 if ( pEmptyNode && pEmptyNode->Len() == 0 )
2300 pRPos->Adjust(SwNodeOffset(-1));
2302 else if ( pEmptyNode0 && pEmptyNode0->Len() == 0 )
2304 // move down
2305 ++aIdx2;
2306 SwTextNode const*const pEmptyNode(aIdx2.GetNode().GetTextNode());
2307 if (pEmptyNode)
2308 pRPos->Adjust(SwNodeOffset(+1));
2311 // sort redlines, when the trimmed range results bad redline order
2312 if ( nRedlPosWithEmpty + 1 < rTable.size() &&
2313 *rTable[nRedlPosWithEmpty + 1] < *rTable[nRedlPosWithEmpty] )
2315 rTable.Remove(nRedlPosWithEmpty);
2316 rTable.Insert(pOwnRedl);
2321 getIDocumentRedlineAccess().SetRedlineFlags( eOld );
2322 GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
2323 getIDocumentState().SetModified();
2325 return true;
2329 if( !pOwnRedl && !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )
2331 SwPaM aTemp(aIdx);
2332 getIDocumentRedlineAccess().SplitRedline(aTemp);
2335 SwNodeOffset nRedlSttNd(0), nRedlEndNd(0);
2336 if( pOwnRedl )
2338 const SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End();
2339 nRedlSttNd = pRStt->GetNodeIndex();
2340 nRedlEndNd = pREnd->GetNodeIndex();
2343 std::unique_ptr<SwUndoMoveNum> pUndo;
2344 SwNodeOffset nMoved(0);
2345 if (GetIDocumentUndoRedo().DoesUndo())
2347 pUndo.reset(new SwUndoMoveNum( rPam, nOffset, bIsOutlMv ));
2348 nMoved = rPam.End()->GetNodeIndex() - rPam.Start()->GetNodeIndex() + 1;
2351 (void) pLayout; // note: move will insert between aIdx-1 and aIdx
2352 assert(!pLayout // check not moving *into* delete redline (caller's fault)
2353 || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::None
2354 || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::First);
2355 getIDocumentContentOperations().MoveNodeRange( aMvRg, aIdx.GetNode(), SwMoveFlags::REDLINES );
2357 if( pUndo )
2359 // i57907: Under circumstances (sections at the end of a chapter)
2360 // the rPam.Start() is not moved to the new position.
2361 // But aIdx should be at the new end position and as long as the
2362 // number of moved paragraphs is nMoved, I know, where the new
2363 // position is.
2364 pUndo->SetStartNode( aIdx.GetIndex() - nMoved );
2365 GetIDocumentUndoRedo().AppendUndo(std::move(pUndo));
2368 if( pOwnRedl )
2370 auto [pRStt, pREnd] = pOwnRedl->StartEnd(); // SwPosition*
2371 if( pRStt->GetNodeIndex() != nRedlSttNd )
2373 pRStt->Assign(nRedlSttNd);
2375 if( pREnd->GetNodeIndex() != nRedlEndNd )
2377 pREnd->Assign(nRedlEndNd);
2378 SwContentNode* pCNd = pREnd->GetNode().GetContentNode();
2379 if(pCNd)
2380 pREnd->SetContent( pCNd->Len() );
2384 getIDocumentState().SetModified();
2385 return true;
2388 bool SwDoc::NumOrNoNum( SwNode& rIdx, bool bDel )
2390 bool bResult = false;
2391 SwTextNode * pTextNd = rIdx.GetTextNode();
2393 if (pTextNd && pTextNd->GetNumRule() != nullptr &&
2394 (pTextNd->HasNumber() || pTextNd->HasBullet()))
2396 if ( !pTextNd->IsCountedInList() == !bDel)
2398 bool bOldNum = bDel;
2399 bool bNewNum = !bDel;
2400 pTextNd->SetCountedInList(bNewNum);
2402 getIDocumentState().SetModified();
2404 bResult = true;
2406 if (GetIDocumentUndoRedo().DoesUndo())
2408 GetIDocumentUndoRedo().AppendUndo(
2409 std::make_unique<SwUndoNumOrNoNum>(rIdx, bOldNum, bNewNum));
2412 else if (bDel && pTextNd->GetNumRule(false) &&
2413 pTextNd->GetActualListLevel() >= 0 &&
2414 pTextNd->GetActualListLevel() < MAXLEVEL)
2416 SwPaM aPam(*pTextNd);
2417 DelNumRules(aPam);
2419 bResult = true;
2423 return bResult;
2426 SwNumRule* SwDoc::GetNumRuleAtPos(SwPosition& rPos,
2427 SwRootFrame const*const pLayout)
2429 SwNumRule* pRet = nullptr;
2430 SwTextNode* pTNd = rPos.GetNode().GetTextNode();
2432 if ( pTNd != nullptr )
2434 if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTNd))
2436 pTNd = static_cast<SwTextFrame*>(pTNd->getLayoutFrame(pLayout))->GetMergedPara()->pParaPropsNode;
2437 rPos.Assign(*pTNd);
2439 pRet = pTNd->GetNumRule();
2442 return pRet;
2445 sal_uInt16 SwDoc::FindNumRule( std::u16string_view rName ) const
2447 for( sal_uInt16 n = mpNumRuleTable->size(); n; )
2448 if( (*mpNumRuleTable)[ --n ]->GetName() == rName )
2449 return n;
2451 return USHRT_MAX;
2454 SwNumRule* SwDoc::FindNumRulePtr( const OUString& rName ) const
2456 SwNumRule * pResult = maNumRuleMap[rName];
2458 if ( !pResult )
2460 for (size_t n = 0; n < mpNumRuleTable->size(); ++n)
2462 if ((*mpNumRuleTable)[n]->GetName() == rName)
2464 pResult = (*mpNumRuleTable)[n];
2466 break;
2471 return pResult;
2474 void SwDoc::AddNumRule(SwNumRule * pRule)
2476 if ((SAL_MAX_UINT16 - 1) <= mpNumRuleTable->size())
2478 OSL_ENSURE(false, "SwDoc::AddNumRule: table full.");
2479 abort(); // this should never happen on real documents
2481 mpNumRuleTable->push_back(pRule);
2482 maNumRuleMap[pRule->GetName()] = pRule;
2483 pRule->SetNumRuleMap(&maNumRuleMap);
2485 getIDocumentListsAccess().createListForListStyle( pRule->GetName() );
2488 sal_uInt16 SwDoc::MakeNumRule( const OUString &rName,
2489 const SwNumRule* pCpy,
2490 bool bBroadcast,
2491 const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode )
2493 SwNumRule* pNew;
2494 if( pCpy )
2496 pNew = new SwNumRule( *pCpy );
2498 pNew->SetName( GetUniqueNumRuleName( &rName ), getIDocumentListsAccess() );
2500 if( pNew->GetName() != rName )
2502 pNew->SetPoolFormatId( USHRT_MAX );
2503 pNew->SetPoolHelpId( USHRT_MAX );
2504 pNew->SetPoolHlpFileId( UCHAR_MAX );
2505 pNew->SetDefaultListId( OUString() );
2507 pNew->CheckCharFormats( *this );
2509 else
2511 pNew = new SwNumRule( GetUniqueNumRuleName( &rName ),
2512 eDefaultNumberFormatPositionAndSpaceMode );
2515 sal_uInt16 nRet = mpNumRuleTable->size();
2517 AddNumRule(pNew);
2519 if (GetIDocumentUndoRedo().DoesUndo())
2521 GetIDocumentUndoRedo().AppendUndo(
2522 std::make_unique<SwUndoNumruleCreate>(pNew, *this));
2525 if (bBroadcast)
2526 BroadcastStyleOperation(pNew->GetName(), SfxStyleFamily::Pseudo,
2527 SfxHintId::StyleSheetCreated);
2529 return nRet;
2532 OUString SwDoc::GetUniqueNumRuleName( const OUString* pChkStr, bool bAutoNum ) const
2534 // If we got pChkStr, then the caller expects that in case it's not yet
2535 // used, it'll be returned.
2536 if( IsInMailMerge() && !pChkStr )
2538 OUString newName = "MailMergeNumRule"
2539 + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US )
2540 + OUString::number( mpNumRuleTable->size() + 1 );
2541 return newName;
2544 OUString aName;
2545 if( bAutoNum )
2547 static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
2549 if (bHack)
2551 static sal_Int64 nIdCounter = SAL_CONST_INT64(8000000000);
2552 aName = OUString::number(nIdCounter++);
2554 else
2556 unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
2557 std::numeric_limits<unsigned int>::max()));
2558 aName = OUString::number(n);
2560 if( pChkStr && pChkStr->isEmpty() )
2561 pChkStr = nullptr;
2563 else if( pChkStr && !pChkStr->isEmpty() )
2564 aName = *pChkStr;
2565 else
2567 pChkStr = nullptr;
2568 aName = SwResId( STR_NUMRULE_DEFNAME );
2571 sal_uInt16 nNum(0), nTmp, nFlagSize = ( mpNumRuleTable->size() / 8 ) +2;
2572 std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]);
2573 memset( pSetFlags.get(), 0, nFlagSize );
2575 sal_Int32 nNmLen = aName.getLength();
2576 if( !bAutoNum && pChkStr )
2578 while( nNmLen-- && '0' <= aName[nNmLen] && aName[nNmLen] <= '9' )
2579 ; //nop
2581 if( ++nNmLen < aName.getLength() )
2583 aName = aName.copy(0, nNmLen );
2584 pChkStr = nullptr;
2588 for( auto const & pNumRule: *mpNumRuleTable )
2589 if( nullptr != pNumRule )
2591 const OUString sNm = pNumRule->GetName();
2592 if( sNm.startsWith( aName ) )
2594 // Determine Number and set the Flag
2595 nNum = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sNm.subView( nNmLen )));
2596 if( nNum-- && nNum < mpNumRuleTable->size() )
2597 pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 ));
2599 if( pChkStr && *pChkStr==sNm )
2600 pChkStr = nullptr;
2603 if( !pChkStr )
2605 // All Numbers have been flagged accordingly, so identify the right Number
2606 nNum = mpNumRuleTable->size();
2607 for( sal_uInt16 n = 0; n < nFlagSize; ++n )
2609 nTmp = pSetFlags[ n ];
2610 if( 0xff != nTmp )
2612 // identify the Number
2613 nNum = n * 8;
2614 while( nTmp & 1 )
2616 ++nNum;
2617 nTmp >>= 1;
2619 break;
2623 if( pChkStr && !pChkStr->isEmpty() )
2624 return *pChkStr;
2625 return aName + OUString::number( ++nNum );
2628 void SwDoc::UpdateNumRule()
2630 const SwNumRuleTable& rNmTable = GetNumRuleTable();
2631 for( size_t n = 0; n < rNmTable.size(); ++n )
2632 if( rNmTable[ n ]->IsInvalidRule() )
2633 rNmTable[ n ]->Validate(*this);
2636 void SwDoc::MarkListLevel( const OUString& sListId,
2637 const int nListLevel,
2638 const bool bValue )
2640 SwList* pList = getIDocumentListsAccess().getListByName( sListId );
2642 if ( pList )
2644 // Set new marked list level and notify all affected nodes of the changed mark.
2645 pList->MarkListLevel( nListLevel, bValue );
2649 bool SwDoc::IsFirstOfNumRuleAtPos(const SwPosition & rPos,
2650 SwRootFrame const& rLayout)
2652 bool bResult = false;
2654 const SwTextNode *const pTextNode = sw::GetParaPropsNode(rLayout, rPos.GetNode());
2655 if ( pTextNode != nullptr )
2657 bResult = pTextNode->IsFirstOfNumRule(rLayout);
2660 return bResult;
2663 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */