sw/qa/extras/layout/data/three_sections.fodt | 18 ++++++ sw/qa/extras/layout/layout.cxx | 43 ++++++++++++++ sw/source/core/inc/sectfrm.hxx | 2 sw/source/core/layout/frmtool.cxx | 63 +++++++-------------- sw/source/core/layout/layhelp.hxx | 3 + sw/source/core/layout/sectfrm.cxx | 79 ++++++++++++++------------- 6 files changed, 129 insertions(+), 79 deletions(-)
New commits: commit eae41835161a90bf395ea29559db953fb5b820e2 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Thu Apr 20 11:18:34 2023 +0300 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Jun 27 16:57:03 2023 +0200 tdf#154113: do not forget to split the outermost section frame ... when the inserted section node is not the first one. The very first frame, where InsertCnt_ puts content to, may already have some content after the insertion position. When an inner section ends, the current section needs a new frame, and the rest of content must go to that new frame. Previously, the new empty frame was created without taking the content move into account. This moves the split into the single place inside InsertCnt_, to avoid special processing in MakeFrames. Change-Id: I1335ebbc620af0f2b064141e8267e5bd1af0b195 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/150675 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> (cherry-picked from commit efb3c57851d29440ef086c68a6c1ddbb8bc8fc00) Change-Id: I021d868835b85226ab62ed8728c8aacdaf8e4528 diff --git a/sw/qa/extras/layout/data/three_sections.fodt b/sw/qa/extras/layout/data/three_sections.fodt new file mode 100644 index 000000000000..9233fed89085 --- /dev/null +++ b/sw/qa/extras/layout/data/three_sections.fodt @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:p>Select the text below, copy to clipboard, and paste from clipboard, replacing the selection.</text:p> + <text:section text:name="Section1"> + <text:p><-- Start selection here. Section1</text:p> + </text:section> + <text:section text:name="Section2"> + <text:p>Section2</text:p> + </text:section> + <text:section text:name="Section3"> + <text:p>Section3. End selection here --></text:p> + </text:section> + </office:text> + </office:body> +</office:document> \ No newline at end of file diff --git a/sw/qa/extras/layout/layout.cxx b/sw/qa/extras/layout/layout.cxx index a6015fedaac3..6debcdbd0f17 100644 --- a/sw/qa/extras/layout/layout.cxx +++ b/sw/qa/extras/layout/layout.cxx @@ -4098,6 +4098,49 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testTdf134548) } } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testTdf154113) +{ + createDoc("three_sections.fodt"); + Scheduler::ProcessEventsToIdle(); + + lcl_dispatchCommand(mxComponent, ".uno:GoToStartOfDoc", {}); + lcl_dispatchCommand(mxComponent, ".uno:GoToNextPara", {}); + lcl_dispatchCommand(mxComponent, ".uno:EndOfDocumentSel", {}); // to the end of current section! + lcl_dispatchCommand(mxComponent, ".uno:EndOfDocumentSel", {}); // to the end of the document. + + css::uno::Reference <frame::XModel> xModel(mxComponent, css::uno::UNO_QUERY_THROW); + css::uno::Reference <container::XIndexAccess> xSelected(xModel->getCurrentSelection(), css::uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelected->getCount()); + css::uno::Reference <text::XTextRange> xRange (xSelected->getByIndex(0), css::uno::UNO_QUERY_THROW); + + //css::uno::Reference < css::uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(OUString("<-- Start selection here. Section1" SAL_NEWLINE_STRING + "Section2" SAL_NEWLINE_STRING "Section3. End selection here -->"), + xRange->getString()); + + lcl_dispatchCommand(mxComponent, ".uno:Cut", {}); + + xSelected = css::uno::Reference <container::XIndexAccess> (xModel->getCurrentSelection(), css::uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelected->getCount()); + xRange = css::uno::Reference <text::XTextRange> (xSelected->getByIndex(0), css::uno::UNO_QUERY_THROW); + CPPUNIT_ASSERT_EQUAL(OUString(), xRange->getString()); + + lcl_dispatchCommand(mxComponent, ".uno:Paste", {}); + + xmlDocPtr pXml = parseLayoutDump(); + + // Without the fix in place, this would fail with + // - Expected: 3 + // - Actual : 2 + assertXPath(pXml, "/root/page/body/section", 3); + assertXPath(pXml, "/root/page/body/section[1]/txt/LineBreak", "Line", + "<-- Start selection here. Section1"); + assertXPath(pXml, "/root/page/body/section[2]/txt/LineBreak", "Line", + "Section2"); + assertXPath(pXml, "/root/page/body/section[3]/txt/LineBreak", "Line", + "Section3. End selection here -->"); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/sectfrm.hxx b/sw/source/core/inc/sectfrm.hxx index 3b890b385e2a..9f75c83cac7b 100644 --- a/sw/source/core/inc/sectfrm.hxx +++ b/sw/source/core/inc/sectfrm.hxx @@ -96,7 +96,7 @@ public: * Splits the SectionFrame surrounding the pFrame up in two parts: * pFrame and the start of the 2nd part */ - bool SplitSect( SwFrame* pFrame, bool bApres ); + SwSectionFrame* SplitSect( SwFrame* pFrameStartAfter, SwFrame* pFramePutAfter ); void DelEmpty( bool bRemove ); // Like Cut(), except for that Follow chaining is maintained SwFootnoteContFrame* ContainsFootnoteCont( const SwFootnoteContFrame* pCont = nullptr ) const; bool Growable() const; diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index c27ddcd750c5..d425af7b8b64 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -1693,6 +1693,9 @@ void InsertCnt_( SwLayoutFrame *pLay, SwDoc *pDoc, nIndex = pNode->EndOfSectionIndex(); else { + if (pActualSection) + pActualSection->SetLastPos(pPrv); + pFrame = pNode->MakeFrame( pLay ); pActualSection.reset( new SwActualSection( pActualSection.release(), static_cast<SwSectionFrame*>(pFrame), pNode ) ); @@ -1840,33 +1843,30 @@ void InsertCnt_( SwLayoutFrame *pLay, SwDoc *pDoc, } // new section frame - pFrame = pActualSection->GetSectionNode()->MakeFrame( pLay ); - pFrame->InsertBehind( pLay, pPrv ); - static_cast<SwSectionFrame*>(pFrame)->Init(); - - // OD 12.08.2003 #i17969# - consider horizontal/vertical layout - // for setting position at newly inserted frame - lcl_SetPos( *pFrame, *pLay ); - - SwSectionFrame* pOuterSectionFrame = pActualSection->GetSectionFrame(); - - // a follow has to be appended to the new section frame - SwSectionFrame* pFollow = pOuterSectionFrame ? pOuterSectionFrame->GetFollow() : nullptr; - if ( pFollow ) + if (SwSectionFrame* pOuterSectionFrame = pActualSection->GetSectionFrame()) { - pOuterSectionFrame->SetFollow( nullptr ); - pOuterSectionFrame->InvalidateSize(); - static_cast<SwSectionFrame*>(pFrame)->SetFollow( pFollow ); - } + // Splitting moves the trailing content to the next frame + pFrame = pOuterSectionFrame->SplitSect(pActualSection->GetLastPos(), pPrv); - // We don't want to leave empty parts back. - if (pOuterSectionFrame && - ! pOuterSectionFrame->IsColLocked() && - ! pOuterSectionFrame->ContainsContent() ) + // We don't want to leave empty parts back. + if (! pOuterSectionFrame->IsColLocked() && + ! pOuterSectionFrame->ContainsContent() ) + { + pOuterSectionFrame->DelEmpty( true ); + SwFrame::DestroyFrame(pOuterSectionFrame); + } + } + else { - pOuterSectionFrame->DelEmpty( true ); - SwFrame::DestroyFrame(pOuterSectionFrame); + pFrame = pActualSection->GetSectionNode()->MakeFrame( pLay ); + pFrame->InsertBehind( pLay, pPrv ); + static_cast<SwSectionFrame*>(pFrame)->Init(); + + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); } + pActualSection->SetSectionFrame( static_cast<SwSectionFrame*>(pFrame) ); pLay = static_cast<SwLayoutFrame*>(pFrame); @@ -2091,20 +2091,7 @@ void MakeFrames( SwDoc *pDoc, const SwNodeIndex &rSttIdx, } else { - bool bSplit; SwFrame* pPrv = bApres ? pFrame : pFrame->GetPrev(); - // If the section frame is inserted into another one, it must be split. - if( pSct && rSttIdx.GetNode().IsSectionNode() ) - { - bSplit = pSct->SplitSect( pFrame, bApres ); - if( !bSplit && !bApres ) - { - pUpper = pSct->GetUpper(); - pPrv = pSct->GetPrev(); - } - } - else - bSplit = false; ::InsertCnt_( pUpper, pDoc, rSttIdx.GetIndex(), false, nEndIdx, pPrv, eMode ); @@ -2117,10 +2104,6 @@ void MakeFrames( SwDoc *pDoc, const SwNodeIndex &rSttIdx, AppendAllObjs( pTable, pUpper ); } - // If nothing was added (e.g. a hidden section), the split must be reversed. - if( bSplit && pSct && pSct->GetNext() - && pSct->GetNext()->IsSctFrame() ) - pSct->MergeNext( static_cast<SwSectionFrame*>(pSct->GetNext()) ); if( pFrame->IsInFly() ) pFrame->FindFlyFrame()->Invalidate_(); if( pFrame->IsInTab() ) diff --git a/sw/source/core/layout/layhelp.hxx b/sw/source/core/layout/layhelp.hxx index 9dc5a916b25f..6dfa4b507639 100644 --- a/sw/source/core/layout/layhelp.hxx +++ b/sw/source/core/layout/layhelp.hxx @@ -85,6 +85,7 @@ class SwActualSection { SwActualSection *pUpper; SwSectionFrame *pSectFrame; + SwFrame* m_pLastPos = nullptr; // Split it *after* this child frame SwSectionNode *pSectNode; public: SwActualSection( SwActualSection *pUpper, @@ -96,6 +97,8 @@ public: SwSectionNode *GetSectionNode() { return pSectNode;} void SetUpper(SwActualSection *p) { pUpper = p; } SwActualSection *GetUpper() { return pUpper; } + void SetLastPos(SwFrame* p) { m_pLastPos = p; } + SwFrame* GetLastPos() const { return m_pLastPos; } }; /// Helps during the InsertCnt_ function to create new pages. diff --git a/sw/source/core/layout/sectfrm.cxx b/sw/source/core/layout/sectfrm.cxx index 48debbcc399a..1c9f22653bbe 100644 --- a/sw/source/core/layout/sectfrm.cxx +++ b/sw/source/core/layout/sectfrm.cxx @@ -498,50 +498,53 @@ void SwSectionFrame::MergeNext( SwSectionFrame* pNxt ) } /** -|* Divides a SectionFrame into two parts. The second one starts with the -|* passed frame. +|* Divides a SectionFrame into two parts. The content of the second one +|* starts after pFrameStartAfter; the created second section frame itself +|* is put after pFramePutAfter. +|* If pFrameStartAfter is nullptr, the split happens at the start. |* This is required when inserting an inner section, because the MoveFwd |* cannot have the desired effect within a frame or a table cell. +|* Splitting at the start/end makes sense, because the empty frame would +|* be removed after the InsertCnt_ finished. |*/ -bool SwSectionFrame::SplitSect( SwFrame* pFrame, bool bApres ) +SwSectionFrame* SwSectionFrame::SplitSect( SwFrame* pFrameStartAfter, SwFrame* pFramePutAfter ) { - assert(pFrame && "SplitSect: Why?"); - SwFrame* pOther = bApres ? pFrame->FindNext() : pFrame->FindPrev(); - if( !pOther ) - return false; - SwSectionFrame* pSect = pOther->FindSctFrame(); - if( pSect != this ) - return false; + assert(!pFrameStartAfter || pFrameStartAfter->FindSctFrame() == this); + SwFrame* pSav = pFrameStartAfter ? pFrameStartAfter->FindNext() : ContainsAny(); + if (pSav && pSav->FindSctFrame() != this) + pSav = nullptr; // we are at the very end + // Put the content aside - SwFrame* pSav = ::SaveContent( this, bApres ? pOther : pFrame ); - OSL_ENSURE( pSav, "SplitSect: What's on?" ); - if( pSav ) // be robust - { // Create a new SctFrame, not as a Follower/master - SwSectionFrame* pNew = new SwSectionFrame( *pSect->GetSection(), pSect ); - pNew->InsertBehind( pSect->GetUpper(), pSect ); - pNew->Init(); - SwRectFnSet aRectFnSet(this); - aRectFnSet.MakePos( *pNew, nullptr, pSect, true ); - // OD 25.03.2003 #108339# - restore content: - // determine layout frame for restoring content after the initialization - // of the section frame. In the section initialization the columns are - // created. - { - SwLayoutFrame* pLay = pNew; - // Search for last layout frame, e.g. for columned sections. - while( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) - pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); - ::RestoreContent( pSav, pLay, nullptr ); - } - InvalidateSize_(); - if( HasFollow() ) - { - pNew->SetFollow( GetFollow() ); - SetFollow( nullptr ); - } - return true; + if (pSav) + pSav = ::SaveContent( this, pSav ); + + // Create a new SctFrame, not as a Follower/master + if (!pFramePutAfter) + pFramePutAfter = this; + SwSectionFrame* pNew = new SwSectionFrame( *GetSection(), this ); + pNew->InsertBehind( pFramePutAfter->GetUpper(), pFramePutAfter ); + pNew->Init(); + SwRectFnSet aRectFnSet(this); + aRectFnSet.MakePos( *pNew, nullptr, pFramePutAfter, true ); + // OD 25.03.2003 #108339# - restore content: + // determine layout frame for restoring content after the initialization + // of the section frame. In the section initialization the columns are + // created. + if (pSav) + { + SwLayoutFrame* pLay = pNew; + // Search for last layout frame, e.g. for columned sections. + while( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); + ::RestoreContent( pSav, pLay, nullptr ); + } + InvalidateSize_(); + if( HasFollow() ) + { + pNew->SetFollow( GetFollow() ); + SetFollow( nullptr ); } - return false; + return pNew; } /**