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;

Reply via email to