1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <hintids.hxx>
21 #include <ftninfo.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>
32 #include <poolfmt.hxx>
33 #include <UndoCore.hxx>
34 #include <UndoRedline.hxx>
35 #include <UndoNumbering.hxx>
37 #include <SwUndoFmt.hxx>
43 #include <rootfrm.hxx>
44 #include <redline.hxx>
45 #include <strings.hrc>
46 #include <SwNodeNum.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>
62 void lcl_ResetIndentAttrs(SwDoc
*pDoc
, const SwPaM
&rPam
,
63 const o3tl::sorted_vector
<sal_uInt16
> aResetAttrsArray
,
64 SwRootFrame
const*const pLayout
)
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
);
78 pDoc
->ResetAttrs( rPam
, false, aResetAttrsArray
, true, pLayout
);
82 void ExpandPamForParaPropsNodes(SwPaM
& rPam
, SwRootFrame
const*const pLayout
)
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
)
104 if( nCurLvl
+ 1 >= nLevel
)
105 nCurLvl
-= nLevel
- 1;
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);
119 GetIDocumentUndoRedo().AppendUndo(
120 std::make_unique
<SwUndoOutlineEdit
>(*mpOutlineRule
, rRule
, *this));
125 (*mpOutlineRule
) = rRule
;
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);
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();
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
);
199 bool SwDoc::OutlineUpDown(const SwPaM
& rPam
, short nOffset
,
200 SwRootFrame
const*const pLayout
)
202 if( GetNodes().GetOutLineNds().empty() || !nOffset
)
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
) &&
215 // we're not in an "Outline section"
218 if( rOutlNds
.Seek_Entry( pEndNd
, &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)
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
;
241 /* Find the last occupied level (backward). */
242 for (n
= MAXLEVEL
- 1; n
> 0; n
--)
244 if (aCollArr
[n
] != nullptr)
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
252 if (aCollArr
[n
] != nullptr)
254 while (n
< MAXLEVEL
- 1)
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
;
270 /* Find the first occupied level (forward). */
271 for (n
= 0; n
< MAXLEVEL
- 1; n
++)
273 if (aCollArr
[n
] != nullptr)
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)
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
;
301 Build a move table that states from which level to which other level
302 an outline will be moved.
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
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
333 If outline level n has no assigned paragraph style
334 aMoveArr[n] is set to -1.
336 if (aCollArr
[n
] != nullptr)
341 while (nCount
> 0 && m
+ nStep
>= 0 && m
+ nStep
< MAXLEVEL
)
345 if (aCollArr
[m
] != nullptr)
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
))
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.
382 const int nNewOutlineLevel
= pTextNd
->GetAttrOutlineLevel() + nOffset
;
383 if ( nNewOutlineLevel
< 1 || nNewOutlineLevel
> MAXLEVEL
)
385 bMoveApplicable
= false;
390 if (! bMoveApplicable
)
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
))
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
);
435 if (GetIDocumentUndoRedo().DoesUndo())
437 GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_LR
, nullptr);
441 getIDocumentState().SetModified();
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()))
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
) )
471 return false; // Promoting or demoting before the first outline => no.
473 aSttRg
= *GetNodes().GetOutLineNds()[ nCurrentPos
];
474 else if( 0 > nOffset
)
475 return false; // Promoting at the top of document?!
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();
496 nCurrentPos
= nTmpPos
;
497 if( aEndRg
== aSttRg
)
499 OSL_FAIL( "Moving outlines: Surprising selection" );
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.
510 while( aSttRg
.GetNode().IsStartNode() )
512 pNd
= aSttRg
.GetNode().EndOfSectionNode();
513 if( pNd
->GetIndex() >= aEndRg
.GetIndex() )
520 while( aEndRg
.GetNode().IsStartNode() )
523 while( aEndRg
.GetNode().IsEndNode() )
525 pNd
= aEndRg
.GetNode().StartOfSectionNode();
526 if( pNd
->GetIndex() >= aSttRg
.GetIndex() )
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();
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.
552 pNd
= aInsertPos
.GetNode().EndOfSectionNode();
553 if( pNd
->GetIndex() >= aEndRg
.GetIndex() )
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() )
575 // We do not want to move into tables (at the moment)
577 pNd
= &aInsertPos
.GetNode();
578 if( pNd
->IsTableNode() )
579 pNd
= pNd
->StartOfSectionNode();
580 if( pNd
->FindTableNode() )
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
;
615 // Found "exact", set Pos to the Node
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."
639 std::u16string_view sNum
= o3tl::getToken(rName
, 0, '.', nPos
);
641 return nullptr; // invalid number!
643 sal_uInt16 nLevelVal
[ MAXLEVEL
]; // numbers of all levels
644 memset( nLevelVal
, 0, MAXLEVEL
* sizeof( nLevelVal
[0] ));
646 std::u16string_view
sName( rName
);
651 for( size_t n
= 0; n
< sNum
.size(); ++n
)
653 const sal_Unicode c
{sNum
[ n
]};
654 if( '0' <= c
&& c
<= '9' )
660 break; // "almost" valid number
662 return nullptr; // invalid number!
665 if( MAXLEVEL
> nLevel
)
666 nLevelVal
[ nLevel
++ ] = nVal
;
668 sName
= sName
.substr( nPos
);
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
))
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() )
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
699 nLevel
= std::min
<int>(nLevel
, MAXLEVEL
);
700 for( int n
= 0; n
< nLevel
; ++n
)
702 if ( aLevelVal
[n
] != nLevelVal
[n
] )
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" );
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
);
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())
751 std::u16string_view sTempNum
= o3tl::getToken(sExpandedText
, 0, '.', nPos
);
752 if( sTempNum
.empty() || -1 == nPos
||
753 !comphelper::string::isdigitAsciiString(sTempNum
))
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
774 pNd
= ::lcl_FindOutlineName(rOutlNds
, pLayout
, rName
, false);
781 // #i68289# additional search on hyperlink URL without its outline numbering part
782 if ( sName
!= rName
)
784 pNd
= ::lcl_FindOutlineName(rOutlNds
, pLayout
, sName
, 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
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);
834 SwNumRule::tTextNodeList aTextNodeList
;
835 pOld
->GetTextNodeList( aTextNodeList
);
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
,
867 const bool bResetIndentAttrs
)
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
896 pUndo
->SaveOldNumRule( *pNewOrChangedNumRule
);
898 ::lcl_ChgNumRule( *this, rRule
);
901 pUndo
->SetLRSpaceEndPos();
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();
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
);
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())
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.
957 SwTextFormatColl
* pColl
= pTextNd
->GetTextColl();
960 SwNumRule
* pCollRule
= FindNumRulePtr(pColl
->GetNumRule().GetValue());
961 if ( pCollRule
&& pCollRule
->GetName() == pNewOrChangedNumRule
->GetName() )
963 pTextNd
->ResetAttr( RES_PARATR_NUMRULE
);
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();
995 void SwDoc::SetCounted(const SwPaM
& rPam
, bool bCounted
,
996 SwRootFrame
const*const pLayout
)
1000 const o3tl::sorted_vector
<sal_uInt16
> attrs
{ RES_PARATR_LIST_ISCOUNTED
};
1001 ::lcl_ResetIndentAttrs(this, rPam
, attrs
, pLayout
);
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();
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();
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
)
1062 if ( (*mpNumRuleTable
)[ nPos
] == GetOutlineNumRule() )
1064 OSL_FAIL( "<SwDoc::DelNumRule(..)> - No deletion of outline list style. This is serious defect" );
1068 if( !IsUsed( *(*mpNumRuleTable
)[ nPos
] ))
1070 if (GetIDocumentUndoRedo().DoesUndo())
1072 GetIDocumentUndoRedo().AppendUndo(
1073 std::make_unique
<SwUndoNumruleDelete
>(*(*mpNumRuleTable
)[nPos
], *this));
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();
1095 void SwDoc::ChgNumRuleFormats( const SwNumRule
& rRule
)
1097 SwNumRule
* pRule
= FindNumRulePtr( rRule
.GetName() );
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
);
1111 pUndo
->SetLRSpaceEndPos();
1114 getIDocumentState().SetModified();
1117 bool SwDoc::RenameNumRule(const OUString
& rOldName
, const OUString
& rNewName
,
1120 assert(!FindNumRulePtr(rNewName
));
1122 bool bResult
= false;
1123 SwNumRule
* pNumRule
= FindNumRulePtr(rOldName
);
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
);
1148 BroadcastStyleOperation(rOldName
, SfxStyleFamily::Pseudo
,
1149 SfxHintId::StyleSheetModified
);
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
)
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();
1217 struct ListStyleData
1219 SwNumRule
* pReplaceNumRule
;
1220 bool bCreateNewList
;
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
;
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();
1246 SwNumRule
* pRule
= pCNd
->GetNumRule();
1248 if (pRule
&& pRule
->IsAutoRule() && ! pRule
->IsOutlineRule())
1250 ListStyleData aListStyleData
= aMyNumRuleMap
[pRule
];
1252 if ( aListStyleData
.pReplaceNumRule
== nullptr )
1256 SwPosition
aPos(*pCNd
);
1257 aListStyleData
.pReplaceNumRule
=
1258 const_cast<SwNumRule
*>
1259 (SearchNumRule( aPos
, false, pCNd
->HasNumber(),
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
;
1277 *aListStyleData
.pReplaceNumRule
,
1278 aListStyleData
.bCreateNewList
,
1280 aListStyleData
.sListId
);
1281 if ( aListStyleData
.bCreateNewList
)
1283 aListStyleData
.bCreateNewList
= false;
1284 aListStyleData
.sListId
= pCNd
->GetListId();
1285 aMyNumRuleMap
[pRule
] = aListStyleData
;
1294 bool SwDoc::NoNum( const SwPaM
& rPam
)
1297 bool bRet
= getIDocumentContentOperations().SplitNode( *rPam
.GetPoint(), false );
1298 // Do we actually use Numbering at all?
1301 // Set NoNum and Update
1302 SwTextNode
* pNd
= rPam
.GetPoint()->GetNode().GetTextNode();
1303 const SwNumRule
* pRule
= pNd
->GetNumRule();
1306 pNd
->SetCountedInList(false);
1308 getIDocumentState().SetModified();
1311 bRet
= false; // no Numbering or just always true?
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
));
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
);
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();
1356 pAttrSet
->GetItemState( RES_PARATR_NUMRULE
, false ) == SfxItemState::SET
)
1357 pTNd
->ResetAttr( RES_PARATR_NUMRULE
);
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() )
1379 // Finally, update all
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" );
1401 if( bOverUpper
? nSrchNum
== nNumber
: nSrchNum
>= nNumber
)
1403 else if( nNumber
> rLower
)
1405 else if( nNumber
< rUpper
)
1411 static bool lcl_IsValidPrevNextNumNode( const SwNodeIndex
& rIdx
)
1414 const SwNode
& rNd
= rIdx
.GetNode();
1415 switch( rNd
.GetNodeType() )
1417 case SwNodeType::End
:
1418 bRet
= SwTableBoxStartNode
== rNd
.StartOfSectionNode()->GetStartNodeType() ||
1419 rNd
.StartOfSectionNode()->IsSectionNode();
1422 case SwNodeType::Start
:
1423 bRet
= SwTableBoxStartNode
== static_cast<const SwStartNode
&>(rNd
).GetStartNodeType();
1426 case SwNodeType::Section
: // that one's valid, so proceed
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());
1466 if (pLayout
&& rIndex
.GetNode().IsTextNode())
1468 rIndex
= *sw::GetParaPropsNode(*pLayout
, *rIndex
.GetNode().GetTextNode());
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();
1499 if (pLayout
&& rIndex
.GetNode().IsTextNode())
1501 rIndex
= *sw::GetParaPropsNode(*pLayout
, *rIndex
.GetNode().GetTextNode());
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();
1514 pNd
= sw::GetParaPropsNode(*pLayout
, *pNd
);
1516 if( !pNd
|| nullptr == pNd
->GetNumRule() )
1519 sal_uInt8 nSrchNum
= static_cast<sal_uInt8
>(pNd
->GetActualListLevel());
1521 SwNodeIndex
aIdx( rPos
.GetNode() );
1522 if( ! pNd
->IsCountedInList() )
1524 bool bError
= false;
1526 sw::GotoPrevLayoutTextFrame(aIdx
, pLayout
);
1527 if( aIdx
.GetNode().IsTextNode() )
1529 pNd
= aIdx
.GetNode().GetTextNode();
1530 const SwNumRule
* pRule
= pNd
->GetNumRule();
1534 sal_uInt8 nTmpNum
= static_cast<sal_uInt8
>(pNd
->GetActualListLevel());
1535 if( pNd
->IsCountedInList() || (nTmpNum
< nSrchNum
) )
1542 bError
= !lcl_IsValidPrevNextNumNode( aIdx
);
1549 sal_uInt8 nLower
= nSrchNum
, nUpper
= nSrchNum
;
1552 const SwTextNode
* pLast
;
1555 sw::GotoNextLayoutTextFrame(aIdx
, pLayout
);
1560 sw::GotoPrevLayoutTextFrame(aIdx
, pLayout
);
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();
1573 if( ::lcl_IsNumOk( nSrchNum
, nLower
, nUpper
, bOverUpper
,
1574 static_cast<sal_uInt8
>(pNd
->GetActualListLevel()) ))
1586 else if( !lcl_IsValidPrevNextNumNode( aIdx
))
1590 sw::GotoNextLayoutTextFrame(aIdx
, pLayout
);
1592 sw::GotoPrevLayoutTextFrame(aIdx
, pLayout
);
1595 if( !bRet
&& !bOverUpper
&& pLast
) // do not iterate over higher numbers, but still to the end
1600 rPos
.Assign( *pLast
);
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
,
1623 const bool bOutline
,
1624 int nNonEmptyAllowed
,
1626 SwRootFrame
const* pLayout
,
1627 const bool bInvestigateStartNode
)
1629 const SwNumRule
* pResult
= nullptr;
1630 SwTextNode
* pTextNd
= rPos
.GetNode().GetTextNode();
1633 pTextNd
= sw::GetParaPropsNode(*pLayout
, rPos
.GetNode());
1635 SwNode
* pStartFromNode
= pTextNd
;
1639 SwNodeIndex
aIdx(rPos
.GetNode());
1641 // - the start node has also been investigated, if requested.
1642 const SwNode
* pNode
= nullptr;
1645 if ( !bInvestigateStartNode
)
1648 sw::GotoNextLayoutTextFrame(aIdx
, pLayout
);
1650 sw::GotoPrevLayoutTextFrame(aIdx
, pLayout
);
1653 if (aIdx
.GetNode().IsTextNode())
1655 pTextNd
= aIdx
.GetNode().GetTextNode();
1657 const SwNumRule
* pNumRule
= pTextNd
->GetNumRule();
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();
1671 else if (pTextNd
->Len() > 0 || nullptr != pTextNd
->GetNumRule())
1673 if (nNonEmptyAllowed
== 0)
1678 if (nNonEmptyAllowed
< 0)
1679 nNonEmptyAllowed
= -1;
1683 if ( bInvestigateStartNode
)
1686 sw::GotoNextLayoutTextFrame(aIdx
, pLayout
);
1688 sw::GotoPrevLayoutTextFrame(aIdx
, pLayout
);
1691 pNode
= &aIdx
.GetNode();
1693 while (pNode
!= GetNodes().DocumentSectionStartNode(pStartFromNode
) &&
1694 pNode
!= GetNodes().DocumentSectionEndNode(pStartFromNode
));
1700 bool SwDoc::GotoPrevNum(SwPosition
& rPos
, SwRootFrame
const*const pLayout
,
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();
1724 pTextNd
= sw::GetParaPropsNode(*pLayout
, *pTextNd
);
1726 SwNumRule
* pRule
= pTextNd
->GetNumRule();
1730 if (pRule
->IsOutlineRule())
1731 bOnlyNonOutline
= false;
1733 bOnlyOutline
= false;
1739 sal_Int8 nDiff
= bDown
? 1 : -1;
1742 bRet
= OutlineUpDown(rPam
, nDiff
, pLayout
);
1743 else if (bOnlyNonOutline
)
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
1759 pTNd
= sw::GetParaPropsNode(*pLayout
, *pTNd
);
1762 SwNumRule
* pRule
= pTNd
->GetNumRule();
1766 sal_uInt8 nLevel
= static_cast<sal_uInt8
>(pTNd
->GetActualListLevel());
1767 if( (-1 == nDiff
&& 0 >= nLevel
) ||
1768 (1 == nDiff
&& MAXLEVEL
- 1 <= nLevel
))
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();
1791 pTNd
= sw::GetParaPropsNode(*pLayout
, *pTNd
);
1799 SwNumRule
* pRule
= pTNd
->GetNumRule();
1803 sal_uInt8 nLevel
= static_cast<sal_uInt8
>(pTNd
->GetActualListLevel());
1804 nLevel
= nLevel
+ nDiff
;
1806 pTNd
->SetAttrListLevel(nLevel
);
1812 getIDocumentState().SetModified();
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())
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())
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())
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
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();
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
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
;
1985 // Impossible to move to negative index
1986 if( abs( nOffset
) > nStIdx
)
1989 nInEndIdx
= nStIdx
- 1;
1992 nInStIdx
= nInEndIdx
+ 1;
1993 // The following paragraphs shall be swapped:
1994 // Swap [ nStIdx, nInEndIdx ] with [ nInStIdx, nEndIdx ]
1996 if( nEndIdx
>= GetNodes().GetEndOfContent().GetIndex() )
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();
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();
2056 case SwComparePosition::CollideEnd
:
2057 case SwComparePosition::Before
: // Pos1 comes before Pos2
2059 case SwComparePosition::Inside
: // Pos1 is completely inside Pos2
2060 // that's valid, but check all following for overlapping
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
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
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() )) )
2107 if( nRedlPos
+ 1 < getIDocumentRedlineAccess().GetRedlineTable().size() )
2109 pTmp
= getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos
+1 ];
2110 if( *pTmp
->Start() == *pREnd
)
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
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
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!");
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
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
2180 size_t nRedlines(getIDocumentRedlineAccess().GetRedlineTable().size());
2182 if (nOffset
> SwNodeOffset(0))
2183 assert(oPam
->End()->GetNodeIndex() - oPam
->Start()->GetNodeIndex() + nOffset
== aInsPos
.GetNodeIndex() - oPam
->End()->GetNodeIndex());
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
);
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())
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
);
2220 // We need to remove the last empty Node again
2221 aIdx
= aInsPos
.GetNode();
2222 SwContentNode
* pCNd
= SwNodes::GoPrevious( &aInsPos
);
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
);
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());
2277 getIDocumentRedlineAccess().AppendRedline( pNewRedline
, true );
2279 oPam
.emplace(bound1
, bound2
);
2280 sw::UpdateFramesForAddDeleteRedline(*this, *oPam
);
2282 // avoid setting empty nodes to tracked insertion
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) )
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 )
2306 SwTextNode
const*const pEmptyNode(aIdx2
.GetNode().GetTextNode());
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();
2329 if( !pOwnRedl
&& !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )
2332 getIDocumentRedlineAccess().SplitRedline(aTemp
);
2335 SwNodeOffset
nRedlSttNd(0), nRedlEndNd(0);
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
);
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
2364 pUndo
->SetStartNode( aIdx
.GetIndex() - nMoved
);
2365 GetIDocumentUndoRedo().AppendUndo(std::move(pUndo
));
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();
2380 pREnd
->SetContent( pCNd
->Len() );
2384 getIDocumentState().SetModified();
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();
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
);
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
;
2439 pRet
= pTNd
->GetNumRule();
2445 sal_uInt16
SwDoc::FindNumRule( std::u16string_view rName
) const
2447 for( sal_uInt16 n
= mpNumRuleTable
->size(); n
; )
2448 if( (*mpNumRuleTable
)[ --n
]->GetName() == rName
)
2454 SwNumRule
* SwDoc::FindNumRulePtr( const OUString
& rName
) const
2456 SwNumRule
* pResult
= maNumRuleMap
[rName
];
2460 for (size_t n
= 0; n
< mpNumRuleTable
->size(); ++n
)
2462 if ((*mpNumRuleTable
)[n
]->GetName() == rName
)
2464 pResult
= (*mpNumRuleTable
)[n
];
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
,
2491 const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode
)
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 );
2511 pNew
= new SwNumRule( GetUniqueNumRuleName( &rName
),
2512 eDefaultNumberFormatPositionAndSpaceMode
);
2515 sal_uInt16 nRet
= mpNumRuleTable
->size();
2519 if (GetIDocumentUndoRedo().DoesUndo())
2521 GetIDocumentUndoRedo().AppendUndo(
2522 std::make_unique
<SwUndoNumruleCreate
>(pNew
, *this));
2526 BroadcastStyleOperation(pNew
->GetName(), SfxStyleFamily::Pseudo
,
2527 SfxHintId::StyleSheetCreated
);
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 );
2547 static bool bHack
= (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
2551 static sal_Int64 nIdCounter
= SAL_CONST_INT64(8000000000);
2552 aName
= OUString::number(nIdCounter
++);
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() )
2563 else if( pChkStr
&& !pChkStr
->isEmpty() )
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' )
2581 if( ++nNmLen
< aName
.getLength() )
2583 aName
= aName
.copy(0, nNmLen
);
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
)
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
];
2612 // identify the Number
2623 if( pChkStr
&& !pChkStr
->isEmpty() )
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
,
2640 SwList
* pList
= getIDocumentListsAccess().getListByName( sListId
);
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
);
2663 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */