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 a8ff11cc84db2dc6a6aa97ac2be9c3bd882140ca Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Mar 13 12:10:41 2025 +0100 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Thu Mar 13 14:10:04 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/+/182861 Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com> Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/sw/inc/ndtxt.hxx b/sw/inc/ndtxt.hxx index 1024974c60b6..9d6b03ab6aa1 100644 --- a/sw/inc/ndtxt.hxx +++ b/sw/inc/ndtxt.hxx @@ -464,9 +464,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 e42b6e80e53c..3708209b78f9 100644 --- a/sw/inc/node.hxx +++ b/sw/inc/node.hxx @@ -493,7 +493,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 e967e58436d6..428b7561d852 100644 --- a/sw/qa/core/txtnode/txtnode.cxx +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -598,6 +598,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 ca38e625a0e4..957d6a2cb10f 100644 --- a/sw/source/core/docnode/node.cxx +++ b/sw/source/core/docnode/node.cxx @@ -1258,7 +1258,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 458d1e99f637..ec2ad33d28c5 100644 --- a/sw/source/core/txtnode/ndtxt.cxx +++ b/sw/source/core/txtnode/ndtxt.cxx @@ -1666,7 +1666,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! @@ -1675,7 +1676,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); } @@ -3075,6 +3076,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 ) { @@ -3173,7 +3204,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; @@ -3191,7 +3224,7 @@ SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext, } } } - ChgFormatColl( pNextColl ); + ChgFormatColl( pNextColl, bSetListLevel ); return pNode; } @@ -4135,7 +4168,7 @@ namespace { // End of method <HandleModifyAtTextNode> } -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." ); @@ -4164,7 +4197,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;