sw/inc/ndtxt.hxx | 5 + sw/inc/node.hxx | 2 sw/qa/core/txtnode/data/node-split-style-list-level.odt |binary sw/qa/core/txtnode/txtnode.cxx | 24 ++++++++ sw/source/core/docnode/node.cxx | 2 sw/source/core/txtnode/ndtxt.cxx | 45 +++++++++++++--- 6 files changed, 68 insertions(+), 10 deletions(-)
New commits: commit 2d73d378751ea4ee8abe5504c8a371d071803d65 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Mar 13 12:10:41 2025 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Mar 13 16:35:19 2025 +0100 tdf#165701 sw: fix unexpected list level change on inserting new bullet Go to the end of the 3rd paragraph of the bugdoc, press Enter, list level of both the old and new paragraphs change from 5 to 4, unexpectedly. It seems what happens is that the paragraph in question has a style which has its outline level set to 4. So once the node is split, the newly inserted node will host the old text (so need to set the style on it to the old style, which sets list level to the outline level from the style as a side effect) and the old node will host what looks like a new bullet point on the UI, so there we need to set the paragraph style to the follow style. This second set-style also drops the list level direct formatting and takes the outline level from the style. Fix the problem by keeping the old scenario (paragraph style with a different follow style) unchanged, since a different list level on an actual paragraph style change can be expected -- but avoid setting a list level when this is a simple node split with just the same style on both nodes. That's not seen as an explicit "set style" by the user, so changing the list level there is confusing. In fact the list level is fine after copying the text node's item set from the old node to the new node, just make sure that SwTextNode::ChgTextCollUpdateNum() doesn't set a new list level in the "effectively no style change" case. Make sure to not set a new list level at both places (SwTextNode::MakeNewTextNode() does this for the previous node, SwTextNode::ChgFormatColl() does this for the next node). Change-Id: Ifdf9048f01fef7bb8e99098ebeb5d1ec339fdaa6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182869 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/inc/ndtxt.hxx b/sw/inc/ndtxt.hxx index 5a435a793b21..f7ac6acb0350 100644 --- a/sw/inc/ndtxt.hxx +++ b/sw/inc/ndtxt.hxx @@ -472,9 +472,10 @@ public: bool Convert( SwConversionArgs & ); inline SwTextFormatColl *GetTextColl() const; - virtual SwFormatColl *ChgFormatColl( SwFormatColl* ) override; + virtual SwFormatColl *ChgFormatColl( SwFormatColl*, bool bSetListLevel = true ) override; void ChgTextCollUpdateNum(const SwTextFormatColl* pOld, - const SwTextFormatColl* pNew ); + const SwTextFormatColl* pNew, + bool bSetListLevel = true ); /** Copy collection with all auto formats to dest-node. The latter might be in another document! diff --git a/sw/inc/node.hxx b/sw/inc/node.hxx index 9ddadf952050..6495b4e928a6 100644 --- a/sw/inc/node.hxx +++ b/sw/inc/node.hxx @@ -497,7 +497,7 @@ public: const SwAttrSet *GetpSwAttrSet() const { return mpAttrSet.get(); } bool HasSwAttrSet() const { return mpAttrSet != nullptr; } - virtual SwFormatColl* ChgFormatColl( SwFormatColl* ); + virtual SwFormatColl* ChgFormatColl( SwFormatColl*, bool bSetListLevel = true ); SwFormatColl* GetFormatColl() const { return const_cast<SwFormatColl*>(static_cast<const SwFormatColl*>(GetRegisteredIn())); } inline SwFormatColl& GetAnyFormatColl() const; diff --git a/sw/qa/core/txtnode/data/node-split-style-list-level.odt b/sw/qa/core/txtnode/data/node-split-style-list-level.odt new file mode 100644 index 000000000000..f2535bb69b9c Binary files /dev/null and b/sw/qa/core/txtnode/data/node-split-style-list-level.odt differ diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx index 7f1e6fd59fb5..97d565d0eff9 100644 --- a/sw/qa/core/txtnode/txtnode.cxx +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -586,6 +586,30 @@ CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testCopyCommentsWithReplies) CPPUNIT_ASSERT_EQUAL(comments[2]->GetName(), comments[3]->GetParentName()); } +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testNodeSplitStyleListLevel) +{ + // Given a document with a 3rd paragraph where the list level as direct formatting differs from + // the list level from style: + createSwDoc("node-split-style-list-level.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Down(/*bSelect=*/false, /*nCount=*/2); + pWrtShell->EndPara(); + + // When pressing enter at the end of the paragraph: + pWrtShell->SplitNode(); + + SwTextNode* pNext = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 4 + // - Actual : 3 + // i.e. the list level for the new paragraph changed on a simple node split. + CPPUNIT_ASSERT_EQUAL(4, pNext->GetAttrListLevel()); + pWrtShell->Up(/*bSelect=*/false, /*nCount=*/1); + SwTextNode* pPrevious = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // Same happened for the old paragraph. + CPPUNIT_ASSERT_EQUAL(4, pPrevious->GetAttrListLevel()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/node.cxx b/sw/source/core/docnode/node.cxx index ca0ae015892e..a22c0d8c4e6e 100644 --- a/sw/source/core/docnode/node.cxx +++ b/sw/source/core/docnode/node.cxx @@ -1256,7 +1256,7 @@ SwRect SwContentNode::FindPageFrameRect() const sal_Int32 SwContentNode::Len() const { return 0; } -SwFormatColl *SwContentNode::ChgFormatColl( SwFormatColl *pNewColl ) +SwFormatColl *SwContentNode::ChgFormatColl( SwFormatColl *pNewColl, bool /*bSetListLevel*/ ) { OSL_ENSURE( pNewColl, "Collectionpointer is 0." ); SwFormatColl *pOldColl = GetFormatColl(); diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx index f62ef446fc15..705afada3614 100644 --- a/sw/source/core/txtnode/ndtxt.cxx +++ b/sw/source/core/txtnode/ndtxt.cxx @@ -1658,7 +1658,8 @@ void SwTextNode::Update( } void SwTextNode::ChgTextCollUpdateNum(const SwTextFormatColl* pOldColl, - const SwTextFormatColl* pNewColl) + const SwTextFormatColl* pNewColl, + bool bSetListLevel) { SwDoc& rDoc = GetDoc(); // query the OutlineLevel and if it changed, notify the Nodes-Array! @@ -1668,7 +1669,7 @@ void SwTextNode::ChgTextCollUpdateNum(const SwTextFormatColl* pOldColl, const int nNewLevel = pNewColl && pNewColl->IsAssignedToListLevelOfOutlineStyle() ? pNewColl->GetAssignedOutlineStyleLevel() : MAXLEVEL; - if ( MAXLEVEL != nNewLevel && -1 != nNewLevel ) + if ( MAXLEVEL != nNewLevel && -1 != nNewLevel && bSetListLevel ) { SetAttrListLevel(nNewLevel); } @@ -3069,6 +3070,36 @@ bool SwTextNode::HasMarkedLabel() const } // <- #i27615# +namespace +{ +/// Decides if a list level direct formatting on a paragraph needs copying to a next, new paragraph. +bool CopyDirectListLevel(SwTextNode* pTextNode) +{ + SwTextFormatColl* pColl = pTextNode->GetTextColl(); + if (!pColl) + { + // No style, so can't have a conflict with a direct formatting. + return false; + } + + if (&pColl->GetNextTextFormatColl() != pColl) + { + // Style has a custom follow style, changing list level is OK. + return false; + } + + if (!pColl->IsAssignedToListLevelOfOutlineStyle()) + { + // Paragraph style has no own list level, no conflict. + return false; + } + + // Copy is needed if the old paragraph had a direct formatting, which may be different and has + // to be kept during the paragraph split. + return pTextNode->HasAttrListLevel(); +} +} + SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext, bool bChgFollow ) { @@ -3167,7 +3198,9 @@ SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext, ( bChgFollow && pColl != GetTextColl() )) return pNode; // that ought to be enough? - pNode->ChgTextCollUpdateNum( nullptr, pColl ); // for numbering/outline + bool bSetListLevel = !CopyDirectListLevel(this); + + pNode->ChgTextCollUpdateNum( nullptr, pColl, bSetListLevel ); // for numbering/outline if( bNext || !bChgFollow ) return pNode; @@ -3185,7 +3218,7 @@ SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext, } } } - ChgFormatColl( pNextColl ); + ChgFormatColl( pNextColl, bSetListLevel ); return pNode; } @@ -4153,7 +4186,7 @@ namespace { } } -SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl ) +SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl, bool bSetListLevel ) { OSL_ENSURE( pNewColl,"ChgFormatColl: Collectionpointer has value 0." ); assert( dynamic_cast<const SwTextFormatColl *>(pNewColl) && "ChgFormatColl: is not a Text Collection pointer." ); @@ -4180,7 +4213,7 @@ SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl ) // only for real nodes-array if( GetNodes().IsDocNodes() ) { - ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl) ); + ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl), bSetListLevel ); } return pOldColl;