diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/docnode | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/docnode')
25 files changed, 17276 insertions, 0 deletions
diff --git a/sw/source/core/docnode/cancellablejob.cxx b/sw/source/core/docnode/cancellablejob.cxx new file mode 100644 index 0000000000..d636078e2c --- /dev/null +++ b/sw/source/core/docnode/cancellablejob.cxx @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include "cancellablejob.hxx" +#include <observablethread.hxx> +#include <utility> + +CancellableJob::CancellableJob( rtl::Reference< ObservableThread > xThread ) : + mrThread(std::move( xThread )) +{ +} + +// css::util::XCancellable: +void SAL_CALL CancellableJob::cancel() +{ + mrThread->join(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/cancellablejob.hxx b/sw/source/core/docnode/cancellablejob.hxx new file mode 100644 index 0000000000..3cd2119299 --- /dev/null +++ b/sw/source/core/docnode/cancellablejob.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_DOCNODE_CANCELLABLEJOB_HXX +#define INCLUDED_SW_SOURCE_CORE_DOCNODE_CANCELLABLEJOB_HXX + +#include <sal/config.h> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/util/XCancellable.hpp> + +#include <rtl/ref.hxx> + +#include <observablethread.hxx> + +class CancellableJob : public ::cppu::WeakImplHelper<css::util::XCancellable> +{ +public: + explicit CancellableJob(::rtl::Reference<ObservableThread> xThread); + + // css::util::XCancellable: + virtual void SAL_CALL cancel() override; + +private: + CancellableJob(CancellableJob const&) = delete; + void operator=(CancellableJob const&) = delete; + + ::rtl::Reference<ObservableThread> mrThread; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/finalthreadmanager.cxx b/sw/source/core/docnode/finalthreadmanager.cxx new file mode 100644 index 0000000000..460d9ab13d --- /dev/null +++ b/sw/source/core/docnode/finalthreadmanager.cxx @@ -0,0 +1,426 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <finalthreadmanager.hxx> + +#include <osl/diagnose.h> +#include <osl/thread.hxx> +#include <pausethreadstarting.hxx> +#include <swthreadjoiner.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <rtl/ustring.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <mutex> +#include <thread> +#include <utility> + +/** thread to cancel a give list of cancellable jobs + + helper class for FinalThreadManager +*/ +class CancelJobsThread : public osl::Thread +{ + public: + explicit CancelJobsThread( std::list< css::uno::Reference< css::util::XCancellable > >&& rJobs ) + : maJobs( std::move(rJobs) ), + mbAllJobsCancelled( false ), + mbStopped( false ) + { + } + + void addJobs( std::list< css::uno::Reference< css::util::XCancellable > >& rJobs ); + bool allJobsCancelled() const; + void stopWhenAllJobsCancelled(); + + private: + bool existJobs() const; + + css::uno::Reference< css::util::XCancellable > getNextJob(); + + bool stopped() const; + virtual void SAL_CALL run() override; + mutable std::mutex maMutex; + + std::list< css::uno::Reference< css::util::XCancellable > > maJobs; + + bool mbAllJobsCancelled; + bool mbStopped; +}; + +void CancelJobsThread::addJobs( std::list< css::uno::Reference< css::util::XCancellable > >& rJobs ) +{ + std::scoped_lock aGuard(maMutex); + + maJobs.insert( maJobs.end(), rJobs.begin(), rJobs.end() ); + mbAllJobsCancelled = !maJobs.empty(); +} + +bool CancelJobsThread::existJobs() const +{ + std::scoped_lock aGuard(maMutex); + + return !maJobs.empty(); +} + +bool CancelJobsThread::allJobsCancelled() const +{ + std::scoped_lock aGuard(maMutex); + + return maJobs.empty() && mbAllJobsCancelled; +} + +void CancelJobsThread::stopWhenAllJobsCancelled() +{ + std::scoped_lock aGuard(maMutex); + + mbStopped = true; +} + +css::uno::Reference< css::util::XCancellable > CancelJobsThread::getNextJob() +{ + css::uno::Reference< css::util::XCancellable > xRet; + + { + std::scoped_lock aGuard(maMutex); + + if ( !maJobs.empty() ) + { + xRet = maJobs.front(); + maJobs.pop_front(); + } + } + + return xRet; +} + +bool CancelJobsThread::stopped() const +{ + std::scoped_lock aGuard(maMutex); + + return mbStopped; +} + +void SAL_CALL CancelJobsThread::run() +{ + osl_setThreadName("sw CancelJobsThread"); + + while ( !stopped() ) + { + while ( existJobs() ) + { + css::uno::Reference< css::util::XCancellable > aJob( getNextJob() ); + if ( aJob.is() ) + aJob->cancel(); + } + + mbAllJobsCancelled = true; + + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } +} + +/** thread to terminate office, when all jobs are cancelled. + + helper class for FinalThreadManager +*/ +class TerminateOfficeThread : public osl::Thread +{ + public: + TerminateOfficeThread( CancelJobsThread const & rCancelJobsThread, + css::uno::Reference< css::uno::XComponentContext > xContext ) + : mrCancelJobsThread( rCancelJobsThread ), + mbStopOfficeTermination( false ), + mxContext(std::move( xContext )) + { + } + + void StopOfficeTermination(); + + private: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + bool OfficeTerminationStopped(); + void PerformOfficeTermination(); + + osl::Mutex maMutex; + + const CancelJobsThread& mrCancelJobsThread; + bool mbStopOfficeTermination; + + css::uno::Reference< css::uno::XComponentContext > mxContext; +}; + +void TerminateOfficeThread::StopOfficeTermination() +{ + osl::MutexGuard aGuard(maMutex); + + mbStopOfficeTermination = true; +} + +bool TerminateOfficeThread::OfficeTerminationStopped() +{ + osl::MutexGuard aGuard(maMutex); + + return mbStopOfficeTermination; +} + +void SAL_CALL TerminateOfficeThread::run() +{ + osl_setThreadName("sw TerminateOfficeThread"); + + while ( !OfficeTerminationStopped() ) + { + osl::MutexGuard aGuard(maMutex); + + if ( mrCancelJobsThread.allJobsCancelled() ) + break; + } + + if ( !OfficeTerminationStopped() ) + PerformOfficeTermination(); +} + +void TerminateOfficeThread::PerformOfficeTermination() +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(mxContext); + + css::uno::Reference< css::container::XElementAccess > xList = xDesktop->getFrames(); + if ( !xList.is() ) + { + OSL_FAIL( "<TerminateOfficeThread::PerformOfficeTermination()> - no XElementAccess!" ); + return; + } + + if ( !xList->hasElements() ) + { + if ( !OfficeTerminationStopped() ) + xDesktop->terminate(); + } +} + +void SAL_CALL TerminateOfficeThread::onTerminated() +{ + if ( OfficeTerminationStopped() ) + delete this; +} + +FinalThreadManager::FinalThreadManager(css::uno::Reference< css::uno::XComponentContext > context) + : m_xContext(std::move(context)), + mpTerminateOfficeThread( nullptr ), + mbRegisteredAtDesktop( false ) +{ + +} + +void FinalThreadManager::registerAsListenerAtDesktop() +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext); + xDesktop->addTerminateListener( css::uno::Reference< css::frame::XTerminateListener >( this ) ); +} + +FinalThreadManager::~FinalThreadManager() +{ + if ( mpPauseThreadStarting ) + { + mpPauseThreadStarting.reset(); + } + + if ( mpTerminateOfficeThread != nullptr ) + { + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + mpTerminateOfficeThread = nullptr; + } + + if ( !maThreads.empty() ) + { + OSL_FAIL( "<FinalThreadManager::~FinalThreadManager()> - still registered jobs are existing -> perform cancellation" ); + cancelAllJobs(); + } + + if ( mpCancelJobsThread != nullptr ) + { + if ( !mpCancelJobsThread->allJobsCancelled() ) + OSL_FAIL( "<FinalThreadManager::~FinalThreadManager()> - cancellation of registered jobs not yet finished -> wait for its finish" ); + + mpCancelJobsThread->stopWhenAllJobsCancelled(); + mpCancelJobsThread->join(); + mpCancelJobsThread.reset(); + } +} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL FinalThreadManager::getImplementationName() +{ + return "com.sun.star.util.comp.FinalThreadManager"; +} + +sal_Bool SAL_CALL FinalThreadManager::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +css::uno::Sequence< OUString > SAL_CALL FinalThreadManager::getSupportedServiceNames() +{ + return { "com.sun.star.util.JobManager" }; +} + +// css::util::XJobManager: +void SAL_CALL FinalThreadManager::registerJob(const css::uno::Reference< css::util::XCancellable > & Job) +{ + osl::MutexGuard aGuard(maMutex); + + maThreads.push_back( Job ); + + if ( !mbRegisteredAtDesktop ) + { + registerAsListenerAtDesktop(); + mbRegisteredAtDesktop = true; + } +} + +void SAL_CALL FinalThreadManager::releaseJob(const css::uno::Reference< css::util::XCancellable > & Job) +{ + osl::MutexGuard aGuard(maMutex); + + maThreads.remove( Job ); +} + +void SAL_CALL FinalThreadManager::cancelAllJobs() +{ + std::list< css::uno::Reference< css::util::XCancellable > > aThreads; + { + osl::MutexGuard aGuard(maMutex); + + aThreads.insert( aThreads.end(), maThreads.begin(), maThreads.end() ); + maThreads.clear(); + } + + if ( aThreads.empty() ) + return; + + osl::MutexGuard aGuard(maMutex); + + if ( mpCancelJobsThread == nullptr ) + { + mpCancelJobsThread.reset(new CancelJobsThread( std::list(aThreads) )); + if ( !mpCancelJobsThread->create() ) + { + mpCancelJobsThread.reset(); + for (auto const& elem : aThreads) + { + elem->cancel(); + } + aThreads.clear(); + } + } + else + mpCancelJobsThread->addJobs( aThreads ); +} + +// css::frame::XTerminateListener +void SAL_CALL FinalThreadManager::queryTermination( const css::lang::EventObject& ) +{ + osl::MutexGuard aGuard(maMutex); + + cancelAllJobs(); + // Sleep 1 second to give the thread for job cancellation some time. + // Probably, all started threads have already finished its work. + if ( mpCancelJobsThread != nullptr && + !mpCancelJobsThread->allJobsCancelled() ) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + if ( mpCancelJobsThread != nullptr && + !mpCancelJobsThread->allJobsCancelled() ) + { + if ( mpTerminateOfficeThread != nullptr ) + { + if ( mpTerminateOfficeThread->isRunning() ) + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + else + delete mpTerminateOfficeThread; + + mpTerminateOfficeThread = nullptr; + } + mpTerminateOfficeThread = new TerminateOfficeThread( *mpCancelJobsThread, + m_xContext ); + if ( !mpTerminateOfficeThread->create() ) + { + delete mpTerminateOfficeThread; + mpTerminateOfficeThread = nullptr; + } + + throw css::frame::TerminationVetoException(); + } + + mpPauseThreadStarting.reset(new SwPauseThreadStarting()); +} + +void SAL_CALL FinalThreadManager::cancelTermination( const css::lang::EventObject& ) +{ + mpPauseThreadStarting.reset(); +} + +void SAL_CALL FinalThreadManager::notifyTermination( const css::lang::EventObject& ) +{ + if ( mpTerminateOfficeThread != nullptr ) + { + if ( mpTerminateOfficeThread->isRunning() ) + mpTerminateOfficeThread->StopOfficeTermination(); // thread kills itself. + else + delete mpTerminateOfficeThread; + + mpTerminateOfficeThread = nullptr; + } + + if ( !maThreads.empty() ) + cancelAllJobs(); + + if ( mpCancelJobsThread != nullptr ) + { + mpCancelJobsThread->stopWhenAllJobsCancelled(); + mpCancelJobsThread->join(); + mpCancelJobsThread.reset(); + } + + // get reference of this + css::uno::Reference< css::uno::XInterface > aOwnRef( getXWeak()); + // notify <SwThreadJoiner> to release its reference + SwThreadJoiner::ReleaseThreadJoiner(); +} + +// ::com::sun:star::lang::XEventListener (inherited via css::frame::XTerminateListener) +void SAL_CALL FinalThreadManager::disposing( const css::lang::EventObject& ) +{ + // nothing to do, because instance doesn't hold any references of observed objects +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_util_comp_FinalThreadManager_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new FinalThreadManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndcopy.cxx b/sw/source/core/docnode/ndcopy.cxx new file mode 100644 index 0000000000..8a63949fa3 --- /dev/null +++ b/sw/source/core/docnode/ndcopy.cxx @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <node.hxx> +#include <frmfmt.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <swtblfmt.hxx> +#include <cellatr.hxx> +#include <ddefld.hxx> +#include <swddetbl.hxx> +#include <ndindex.hxx> +#include <frameformats.hxx> +#include <vector> +#include <osl/diagnose.h> +#include <svl/numformat.hxx> + + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +namespace { + +// Structure for the mapping from old and new frame formats to the +// boxes and lines of a table +struct MapTableFrameFormat +{ + const SwFrameFormat *pOld; + SwFrameFormat *pNew; + MapTableFrameFormat( const SwFrameFormat *pOldFormat, SwFrameFormat*pNewFormat ) + : pOld( pOldFormat ), pNew( pNewFormat ) + {} +}; + +} + +typedef std::vector<MapTableFrameFormat> MapTableFrameFormats; + +SwContentNode* SwTextNode::MakeCopy(SwDoc& rDoc, SwNode& rIdx, bool const bNewFrames) const +{ + // the Copy-Textnode is the Node with the Text, the Copy-Attrnode is the + // node with the collection and hard attributes. Normally is the same + // node, but if insert a glossary without formatting, then the Attrnode + // is the prev node of the destination position in dest. document. + SwTextNode* pCpyTextNd = const_cast<SwTextNode*>(this); + SwTextNode* pCpyAttrNd = pCpyTextNd; + + // Copy the formats to the other document + SwTextFormatColl* pColl = nullptr; + if( rDoc.IsInsOnlyTextGlossary() ) + { + SwNodeIndex aIdx( rIdx, -1 ); + if( aIdx.GetNode().IsTextNode() ) + { + pCpyAttrNd = aIdx.GetNode().GetTextNode(); + pColl = &pCpyAttrNd->GetTextColl()->GetNextTextFormatColl(); + } + } + if( !pColl ) + pColl = rDoc.CopyTextColl( *GetTextColl() ); + + SwTextNode* pTextNd = rDoc.GetNodes().MakeTextNode(rIdx, pColl, bNewFrames); + + // METADATA: register copy + pTextNd->RegisterAsCopyOf(*pCpyTextNd); + + // Copy Attribute/Text + if( !pCpyAttrNd->HasSwAttrSet() ) + // An AttrSet was added for numbering, so delete it + pTextNd->ResetAllAttr(); + + // if Copy-Textnode unequal to Copy-Attrnode, then copy first + // the attributes into the new Node. + if( pCpyAttrNd != pCpyTextNd ) + { + pCpyAttrNd->CopyAttr( pTextNd, 0, 0 ); + if( pCpyAttrNd->HasSwAttrSet() ) + { + SwAttrSet aSet( *pCpyAttrNd->GetpSwAttrSet() ); + aSet.ClearItem( RES_PAGEDESC ); + aSet.ClearItem( RES_BREAK ); + aSet.CopyToModify( *pTextNd ); + } + } + + // Is that enough? What about PostIts/Fields/FieldTypes? + // #i96213# - force copy of all attributes + pCpyTextNd->CopyText( pTextNd, SwContentIndex( pCpyTextNd ), + pCpyTextNd->GetText().getLength(), true ); + + if( RES_CONDTXTFMTCOLL == pColl->Which() ) + pTextNd->ChkCondColl(); + + return pTextNd; +} + +static bool lcl_SrchNew( const MapTableFrameFormat& rMap, SwFrameFormat** pPara ) +{ + if( rMap.pOld != *pPara ) + return true; + *pPara = rMap.pNew; + return false; +} + +namespace { + +struct CopyTable +{ + SwDoc& m_rDoc; + SwNodeOffset m_nOldTableSttIdx; + MapTableFrameFormats& m_rMapArr; + SwTableLine* m_pInsLine; + SwTableBox* m_pInsBox; + SwTableNode *m_pTableNd; + const SwTable *m_pOldTable; + + CopyTable(SwDoc& rDc, MapTableFrameFormats& rArr, SwNodeOffset nOldStt, + SwTableNode& rTableNd, const SwTable* pOldTable) + : m_rDoc(rDc), m_nOldTableSttIdx(nOldStt), m_rMapArr(rArr), + m_pInsLine(nullptr), m_pInsBox(nullptr), m_pTableNd(&rTableNd), m_pOldTable(pOldTable) + {} +}; + +} + +static void lcl_CopyTableLine( const SwTableLine* pLine, CopyTable* pCT ); + +static void lcl_CopyTableBox( SwTableBox* pBox, CopyTable* pCT ) +{ + SwTableBoxFormat * pBoxFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + for (const auto& rMap : pCT->m_rMapArr) + if ( !lcl_SrchNew( rMap, reinterpret_cast<SwFrameFormat**>(&pBoxFormat) ) ) + break; + + if (pBoxFormat == pBox->GetFrameFormat()) // Create a new one? + { + const SwTableBoxFormula* pFormulaItem = pBoxFormat->GetItemIfSet( RES_BOXATR_FORMULA, false ); + if( pFormulaItem && pFormulaItem->IsIntrnlName() ) + { + const_cast<SwTableBoxFormula*>(pFormulaItem)->PtrToBoxNm(pCT->m_pOldTable); + } + + pBoxFormat = pCT->m_rDoc.MakeTableBoxFormat(); + pBoxFormat->CopyAttrs( *pBox->GetFrameFormat() ); + + if( pBox->GetSttIdx() ) + { + SvNumberFormatter* pN = pCT->m_rDoc.GetNumberFormatter(false); + const SwTableBoxNumFormat* pFormatItem; + if( pN && pN->HasMergeFormatTable() && + (pFormatItem = pBoxFormat->GetItemIfSet( RES_BOXATR_FORMAT, false )) ) + { + sal_uLong nOldIdx = pFormatItem->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + pBoxFormat->SetFormatAttr( SwTableBoxNumFormat( nNewIdx )); + + } + } + + pCT->m_rMapArr.emplace_back(pBox->GetFrameFormat(), pBoxFormat); + } + + sal_uInt16 nLines = pBox->GetTabLines().size(); + SwTableBox* pNewBox; + if( nLines ) + pNewBox = new SwTableBox(pBoxFormat, nLines, pCT->m_pInsLine); + else + { + SwNodeIndex aNewIdx(*pCT->m_pTableNd, pBox->GetSttIdx() - pCT->m_nOldTableSttIdx); + assert(aNewIdx.GetNode().IsStartNode() && "Index is not on the start node"); + + pNewBox = new SwTableBox(pBoxFormat, aNewIdx, pCT->m_pInsLine); + pNewBox->setRowSpan( pBox->getRowSpan() ); + } + + pCT->m_pInsLine->GetTabBoxes().push_back( pNewBox ); + + if (nLines) + { + CopyTable aPara(*pCT); + aPara.m_pInsBox = pNewBox; + for( const SwTableLine* pLine : pBox->GetTabLines() ) + lcl_CopyTableLine( pLine, &aPara ); + } + else if (pNewBox->IsInHeadline(&pCT->m_pTableNd->GetTable())) + { + // In the headline, the paragraphs must match conditional styles + pNewBox->GetSttNd()->CheckSectionCondColl(); + } +} + +static void lcl_CopyTableLine( const SwTableLine* pLine, CopyTable* pCT ) +{ + SwTableLineFormat * pLineFormat = static_cast<SwTableLineFormat*>(pLine->GetFrameFormat()); + for (const auto& rMap : pCT->m_rMapArr) + if ( !lcl_SrchNew( rMap, reinterpret_cast<SwFrameFormat**>(&pLineFormat) ) ) + break; + + if( pLineFormat == pLine->GetFrameFormat() ) // Create a new one? + { + pLineFormat = pCT->m_rDoc.MakeTableLineFormat(); + pLineFormat->CopyAttrs( *pLine->GetFrameFormat() ); + pCT->m_rMapArr.emplace_back(pLine->GetFrameFormat(), pLineFormat); + } + + SwTableLine* pNewLine = new SwTableLine(pLineFormat, pLine->GetTabBoxes().size(), pCT->m_pInsBox); + // Insert the new row into the table + if (pCT->m_pInsBox) + { + pCT->m_pInsBox->GetTabLines().push_back(pNewLine); + } + else + { + pCT->m_pTableNd->GetTable().GetTabLines().push_back(pNewLine); + } + + pCT->m_pInsLine = pNewLine; + for( auto& rpBox : const_cast<SwTableLine*>(pLine)->GetTabBoxes() ) + lcl_CopyTableBox(rpBox, pCT); +} + +SwTableNode* SwTableNode::MakeCopy( SwDoc& rDoc, const SwNodeIndex& rIdx ) const +{ + // In which array are we? Nodes? UndoNodes? + SwNodes& rNds = const_cast<SwNodes&>(GetNodes()); + + { + if( rIdx < rDoc.GetNodes().GetEndOfInserts().GetIndex() && + rIdx >= rDoc.GetNodes().GetEndOfInserts().StartOfSectionIndex() ) + return nullptr; + } + + // Copy the TableFrameFormat + OUString sTableName( GetTable().GetFrameFormat()->GetName() ); + if( !rDoc.IsCopyIsMove() ) + { + const sw::TableFrameFormats& rTableFormats = *rDoc.GetTableFrameFormats(); + for( size_t n = rTableFormats.size(); n; ) + { + const SwTableFormat* pFormat = rTableFormats[--n]; + if (pFormat->GetName() == sTableName && rDoc.IsUsed(*pFormat)) + { + sTableName = rDoc.GetUniqueTableName(); + break; + } + } + } + + SwFrameFormat* pTableFormat = rDoc.MakeTableFrameFormat( sTableName, rDoc.GetDfltFrameFormat() ); + pTableFormat->CopyAttrs( *GetTable().GetFrameFormat() ); + SwTableNode* pTableNd = new SwTableNode( rIdx.GetNode() ); + SwEndNode* pEndNd = new SwEndNode( rIdx.GetNode(), *pTableNd ); + SwNodeIndex aInsPos( *pEndNd ); + + SwTable& rTable = pTableNd->GetTable(); + rTable.SetTableStyleName(GetTable().GetTableStyleName()); + rTable.RegisterToFormat( *pTableFormat ); + + rTable.SetRowsToRepeat( GetTable().GetRowsToRepeat() ); + rTable.SetTableChgMode( GetTable().GetTableChgMode() ); + rTable.SetTableModel( GetTable().IsNewModel() ); + + SwDDEFieldType* pDDEType = nullptr; + if( auto pSwDDETable = dynamic_cast<const SwDDETable*>( &GetTable() ) ) + { + // We're copying a DDE table + // Is the field type available in the new document? + pDDEType = const_cast<SwDDETable*>(pSwDDETable)->GetDDEFieldType(); + if( pDDEType->IsDeleted() ) + rDoc.getIDocumentFieldsAccess().InsDeletedFieldType( *pDDEType ); + else + pDDEType = static_cast<SwDDEFieldType*>(rDoc.getIDocumentFieldsAccess().InsertFieldType( *pDDEType )); + OSL_ENSURE( pDDEType, "unknown FieldType" ); + + // Swap the table pointers in the node + std::unique_ptr<SwDDETable> pNewTable(new SwDDETable( pTableNd->GetTable(), pDDEType )); + pTableNd->SetNewTable( std::move(pNewTable), false ); + } + // First copy the content of the tables, we will later assign the + // boxes/lines and create the frames + SwNodeRange aRg( *this, SwNodeOffset(+1), *EndOfSectionNode() ); + + // If there is a table in this table, the table format for the outer table + // does not seem to be used, because the table does not have any contents yet + // (see IsUsed). Therefore the inner table gets the same name as the outer table. + // We have to make sure that the table node of the SwTable is accessible, even + // without any content in m_TabSortContentBoxes. #i26629# + pTableNd->GetTable().SetTableNode( pTableNd ); + rNds.Copy_( aRg, aInsPos.GetNode(), false ); + pTableNd->GetTable().SetTableNode( nullptr ); + + // Special case for a single box + if( 1 == GetTable().GetTabSortBoxes().size() ) + { + aRg.aStart.Assign( *pTableNd, 1 ); + aRg.aEnd.Assign( *pTableNd->EndOfSectionNode() ); + rDoc.GetNodes().SectionDown( &aRg, SwTableBoxStartNode ); + } + + // Delete all frames from the copied area, they will be created + // during the generation of the table frame + pTableNd->DelFrames(); + + MapTableFrameFormats aMapArr; + CopyTable aPara( rDoc, aMapArr, GetIndex(), *pTableNd, &GetTable() ); + + for( const SwTableLine* pLine : GetTable().GetTabLines() ) + lcl_CopyTableLine( pLine, &aPara ); + + if( pDDEType ) + pDDEType->IncRefCnt(); + + CHECK_TABLE( GetTable() ); + return pTableNd; +} + +void SwTextNode::CopyCollFormat(SwTextNode& rDestNd, bool const bUndoForChgFormatColl) +{ + // Copy the formats into the other document: + // Special case for PageBreak/PageDesc/ColBrk + SwDoc& rDestDoc = rDestNd.GetDoc(); + SwAttrSet aPgBrkSet( rDestDoc.GetAttrPool(), aBreakSetRange ); + const SwAttrSet* pSet; + + pSet = rDestNd.GetpSwAttrSet(); + if( nullptr != pSet ) + { + // Special cases for Break-Attributes + const SfxPoolItem* pAttr; + if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pAttr ) ) + aPgBrkSet.Put( *pAttr ); + + if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pAttr ) ) + aPgBrkSet.Put( *pAttr ); + } + + // this may create undo action SwUndoFormatCreate + auto const pCopy( rDestDoc.CopyTextColl( *GetTextColl() ) ); + if (bUndoForChgFormatColl) + { + rDestNd.ChgFormatColl(pCopy); + } + else // tdf#138897 + { + ::sw::UndoGuard const ug(rDestDoc.GetIDocumentUndoRedo()); + rDestNd.ChgFormatColl(pCopy); + } + pSet = GetpSwAttrSet(); + if( nullptr != pSet ) + { + // note: this may create undo actions but not for setting the items + pSet->CopyToModify( rDestNd ); + } + + if( aPgBrkSet.Count() ) + rDestNd.SetAttr( aPgBrkSet ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndnotxt.cxx b/sw/source/core/docnode/ndnotxt.cxx new file mode 100644 index 0000000000..e4641de223 --- /dev/null +++ b/sw/source/core/docnode/ndnotxt.cxx @@ -0,0 +1,294 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> +#include <osl/diagnose.h> +#include <tools/poly.hxx> +#include <svl/stritem.hxx> +#include <svx/contdlg.hxx> +#include <vcl/svapp.hxx> +#include <doc.hxx> +#include <fmtcol.hxx> +#include <ndnotxt.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <ndindex.hxx> +#include <istyleaccess.hxx> +#include <SwStyleNameMapper.hxx> + +#include <frmfmt.hxx> + +SwNoTextNode::SwNoTextNode( SwNode& rWhere, + const SwNodeType nNdType, + SwGrfFormatColl *pGrfColl, + SwAttrSet const * pAutoAttr ) : + SwContentNode( rWhere, nNdType, pGrfColl ), + m_bAutomaticContour( false ), + m_bContourMapModeValid( true ), + m_bPixelContour( false ) +{ + // Should this set a hard attribute? + if( pAutoAttr ) + SetAttr( *pAutoAttr ); +} + +SwNoTextNode::~SwNoTextNode() +{ +} + +/// Creates an AttrSet for all derivations with ranges for frame- +/// and graphics-attributes. +void SwNoTextNode::NewAttrSet( SwAttrPool& rPool ) +{ + OSL_ENSURE( !mpAttrSet, "AttrSet is already set" ); + SwAttrSet aNewAttrSet( rPool, aNoTextNodeSetRange ); + + // put names of parent style and conditional style: + const SwFormatColl* pFormatColl = GetFormatColl(); + OUString sVal; + SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + SfxStringItem aFormatColl( RES_FRMATR_STYLE_NAME, sVal ); + aNewAttrSet.Put( aFormatColl ); + + aNewAttrSet.SetParent( &GetFormatColl()->GetAttrSet() ); + mpAttrSet = GetDoc().GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_NOTXT ); +} + +/// Dummies for loading/saving of persistent data +/// when working with graphics and OLE objects +bool SwNoTextNode::RestorePersistentData() +{ + return true; +} + +bool SwNoTextNode::SavePersistentData() +{ + return true; +} + +void SwNoTextNode::SetContour( const tools::PolyPolygon *pPoly, bool bAutomatic ) +{ + if ( pPoly ) + m_pContour = *pPoly; + else + m_pContour.reset(); + m_bAutomaticContour = bAutomatic; + m_bContourMapModeValid = true; + m_bPixelContour = false; +} + +void SwNoTextNode::CreateContour() +{ + OSL_ENSURE( !m_pContour, "Contour available." ); + m_pContour = SvxContourDlg::CreateAutoContour(GetGraphic()); + m_bAutomaticContour = true; + m_bContourMapModeValid = true; + m_bPixelContour = false; +} + +const tools::PolyPolygon *SwNoTextNode::HasContour() const +{ + if( !m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + bool bPixelGrf = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + const MapMode aContourMap( bPixelGrf ? MapUnit::MapPixel : MapUnit::Map100thMM ); + if( bPixelGrf ? !m_bPixelContour : aGrfMap != aContourMap ) + { + double nGrfDPIx = 0.0; + double nGrfDPIy = 0.0; + { + if ( !bPixelGrf && m_bPixelContour ) + { + basegfx::B2DSize aDPI = GetGraphic().GetPPI(); + nGrfDPIx = aDPI.getWidth(); + nGrfDPIy = aDPI.getHeight(); + } + } + OSL_ENSURE( !bPixelGrf || aGrfMap == aContourMap, + "scale factor for pixel unsupported" ); + OutputDevice* pOutDev = + (bPixelGrf || m_bPixelContour) ? Application::GetDefaultDevice() + : nullptr; + sal_uInt16 nPolyCount = m_pContour->Count(); + for( sal_uInt16 j=0; j<nPolyCount; j++ ) + { + tools::Polygon& rPoly = (*m_pContour)[j]; + + sal_uInt16 nCount = rPoly.GetSize(); + for( sal_uInt16 i=0 ; i<nCount; i++ ) + { + if( bPixelGrf ) + rPoly[i] = pOutDev->LogicToPixel( rPoly[i], + aContourMap ); + else if( m_bPixelContour ) + { + rPoly[i] = pOutDev->PixelToLogic( rPoly[i], aGrfMap ); + + if ( nGrfDPIx != 0 && nGrfDPIy != 0 ) + { + rPoly[i] = Point( rPoly[i].getX() * pOutDev->GetDPIX() / nGrfDPIx, + rPoly[i].getY() * pOutDev->GetDPIY() / nGrfDPIy ); + } + } + else + rPoly[i] = OutputDevice::LogicToLogic( rPoly[i], + aContourMap, + aGrfMap ); + } + } + } + m_bContourMapModeValid = true; + m_bPixelContour = false; + } + + return m_pContour ? &*m_pContour : nullptr; +} + +void SwNoTextNode::GetContour( tools::PolyPolygon &rPoly ) const +{ + OSL_ENSURE( m_pContour, "Contour not available." ); + rPoly = *HasContour(); +} + +void SwNoTextNode::SetContourAPI( const tools::PolyPolygon *pPoly ) +{ + if ( pPoly ) + m_pContour = *pPoly; + else + m_pContour.reset(); + m_bContourMapModeValid = false; +} + +bool SwNoTextNode::GetContourAPI( tools::PolyPolygon &rContour ) const +{ + if( !m_pContour ) + return false; + + rContour = *m_pContour; + if( m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + const MapMode aContourMap( MapUnit::Map100thMM ); + OSL_ENSURE( aGrfMap.GetMapUnit() != MapUnit::MapPixel || + aGrfMap == MapMode( MapUnit::MapPixel ), + "scale factor for pixel unsupported" ); + if( aGrfMap.GetMapUnit() != MapUnit::MapPixel && + aGrfMap != aContourMap ) + { + sal_uInt16 nPolyCount = rContour.Count(); + for( sal_uInt16 j=0; j<nPolyCount; j++ ) + { + tools::Polygon& rPoly = rContour[j]; + + sal_uInt16 nCount = rPoly.GetSize(); + for( sal_uInt16 i=0 ; i<nCount; i++ ) + { + rPoly[i] = OutputDevice::LogicToLogic( rPoly[i], aGrfMap, + aContourMap ); + } + } + } + } + + return true; +} + +bool SwNoTextNode::IsPixelContour() const +{ + bool bRet; + if( m_bContourMapModeValid ) + { + const MapMode aGrfMap( GetGraphic().GetPrefMapMode() ); + bRet = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + } + else + { + bRet = m_bPixelContour; + } + + return bRet; +} + +Graphic SwNoTextNode::GetGraphic() const +{ + Graphic aRet; + if ( GetGrfNode() ) + { + aRet = static_cast<const SwGrfNode*>(this)->GetGrf(true); + } + else + { + OSL_ENSURE( GetOLENode(), "new type of Node?" ); + aRet = *const_cast<SwOLENode*>(static_cast<const SwOLENode*>(this))->SwOLENode::GetGraphic(); + } + return aRet; +} + +// #i73249# +void SwNoTextNode::SetTitle( const OUString& rTitle ) +{ + // Title attribute of <SdrObject> replaces own AlternateText attribute + SwFlyFrameFormat* pFlyFormat = dynamic_cast<SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::SetTitle(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return; + } + + pFlyFormat->SetObjTitle( rTitle ); +} + +OUString SwNoTextNode::GetTitle() const +{ + const SwFlyFrameFormat* pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::GetTitle(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return OUString(); + } + + return pFlyFormat->GetObjTitle(); +} + +void SwNoTextNode::SetDescription( const OUString& rDescription ) +{ + SwFlyFrameFormat* pFlyFormat = dynamic_cast<SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::SetDescription(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return; + } + + pFlyFormat->SetObjDescription( rDescription ); +} + +OUString SwNoTextNode::GetDescription() const +{ + const SwFlyFrameFormat* pFlyFormat = dynamic_cast<const SwFlyFrameFormat*>(GetFlyFormat()); + OSL_ENSURE( pFlyFormat, "<SwNoTextNode::GetDescription(..)> - missing <SwFlyFrameFormat> instance" ); + if ( !pFlyFormat ) + { + return OUString(); + } + + return pFlyFormat->GetObjDescription(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndnum.cxx b/sw/source/core/docnode/ndnum.cxx new file mode 100644 index 0000000000..af17a2a68e --- /dev/null +++ b/sw/source/core/docnode/ndnum.cxx @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <node.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <ndtxt.hxx> +#include <fldbas.hxx> +#include <osl/diagnose.h> + +bool CompareSwOutlineNodes::operator()( SwNode* const& lhs, SwNode* const& rhs) const +{ + return lhs->GetIndex() < rhs->GetIndex(); +} + +bool SwOutlineNodes::Seek_Entry(SwNode* rP, size_type* pnPos) const +{ + const_iterator it = lower_bound(rP); + *pnPos = it - begin(); + return it != end() && rP->GetIndex() == (*it)->GetIndex(); +} + +void SwNodes::UpdateOutlineNode(SwNode & rNd) +{ + assert(IsDocNodes()); // no point in m_pOutlineNodes for undo nodes + + SwTextNode * pTextNd = rNd.GetTextNode(); + + if (!pTextNd || !pTextNd->IsOutlineStateChanged()) + return; + + bool bFound = m_aOutlineNodes.find(pTextNd) != m_aOutlineNodes.end(); + + if (pTextNd->IsOutline()) + { + if (! bFound) + { + // assure that text is in the correct nodes array + if ( &(pTextNd->GetNodes()) == this ) + { + m_aOutlineNodes.insert(pTextNd); + } + else + { + OSL_FAIL( "<SwNodes::UpdateOutlineNode(..)> - given text node isn't in the correct nodes array. This is a serious defect" ); + } + } + } + else + { + if (bFound) + m_aOutlineNodes.erase(pTextNd); + } + + pTextNd->UpdateOutlineState(); + + // update the structure fields + GetDoc().getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Chapter )->UpdateFields(); +} + +void SwNodes::UpdateOutlineIdx( const SwNode& rNd ) +{ + if( m_aOutlineNodes.empty() ) // no OutlineNodes present ? + return; + + SwNode* const pSrch = const_cast<SwNode*>(&rNd); + + SwOutlineNodes::size_type nPos; + if (!m_aOutlineNodes.Seek_Entry(pSrch, &nPos)) + return; + if( nPos == m_aOutlineNodes.size() ) // none present for updating ? + return; + + if( nPos ) + --nPos; + + if( !GetDoc().IsInDtor() && IsDocNodes() ) + UpdateOutlineNode( *m_aOutlineNodes[ nPos ]); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndsect.cxx b/sw/source/core/docnode/ndsect.cxx new file mode 100644 index 0000000000..cf96e1d509 --- /dev/null +++ b/sw/source/core/docnode/ndsect.cxx @@ -0,0 +1,1456 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_wasm_strip.h> +#include <libxml/xmlwriter.h> +#include <hintids.hxx> +#include <osl/diagnose.h> +#include <sfx2/linkmgr.hxx> +#include <svl/itemiter.hxx> +#include <sal/log.hxx> +#include <fmtcntnt.hxx> +#include <txtftn.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <section.hxx> +#include <UndoSection.hxx> +#include <UndoDelete.hxx> +#include <swundo.hxx> +#include <calc.hxx> +#include <swtable.hxx> +#include <swserv.hxx> +#include <frmfmt.hxx> +#include <frmtool.hxx> +#include <ftnidx.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <node2lay.hxx> +#include <doctxm.hxx> +#include <fmtftntx.hxx> +#include <strings.hrc> +#include <viewsh.hxx> +#include <memory> +#include "ndsect.hxx" +#include <tools/datetimeutils.hxx> +#include <o3tl/string_view.hxx> + +// #i21457# - new implementation of local method <lcl_IsInSameTableBox(..)>. +// Method now determines the previous/next on its own. Thus, it can be controlled, +// for which previous/next is checked, if it's visible. +static bool lcl_IsInSameTableBox( SwNodes const & _rNds, + const SwNode& _rNd, + const bool _bPrev ) +{ + const SwTableNode* pTableNd = _rNd.FindTableNode(); + if ( !pTableNd ) + { + return true; + } + + // determine index to be checked. Its assumed that a previous/next exist. + SwNodeIndex aChkIdx( _rNd ); + + // determine index of previous/next - skip hidden ones, which are + // inside the table. + // If found one is before/after table, this one isn't in the same + // table box as <_rNd>. + for(;;) + { + if ( _bPrev + ? !SwNodes::GoPrevSection( &aChkIdx, false, false ) + : !_rNds.GoNextSection( &aChkIdx, false, false ) ) + { + OSL_FAIL( "<lcl_IsInSameTableBox(..)> - no previous/next!" ); + return false; + } + + if ( aChkIdx < pTableNd->GetIndex() || + aChkIdx > pTableNd->EndOfSectionNode()->GetIndex() ) + { + return false; + } + + // check, if found one isn't inside a hidden section, which + // is also inside the table. + SwSectionNode* pSectNd = aChkIdx.GetNode().FindSectionNode(); + if ( !pSectNd || + pSectNd->GetIndex() < pTableNd->GetIndex() || + !pSectNd->GetSection().IsHiddenFlag() ) + { + break; + } + } + + // Find the Box's StartNode + const SwTableSortBoxes& rSortBoxes = pTableNd->GetTable().GetTabSortBoxes(); + SwNodeOffset nIdx = _rNd.GetIndex(); + for (size_t n = 0; n < rSortBoxes.size(); ++n) + { + const SwStartNode* pNd = rSortBoxes[ n ]->GetSttNd(); + if ( pNd->GetIndex() < nIdx && nIdx < pNd->EndOfSectionIndex() ) + { + // The other index needs to be within the same Section + nIdx = aChkIdx.GetIndex(); + return pNd->GetIndex() < nIdx && nIdx < pNd->EndOfSectionIndex(); + } + } + + return true; +} + +static void lcl_CheckEmptyLayFrame( SwNodes const & rNds, SwSectionData& rSectionData, + const SwNode& rStt, const SwNode& rEnd ) +{ + SwNodeIndex aIdx( rStt ); + if( !SwNodes::GoPrevSection( &aIdx, true, false ) || + !CheckNodesRange( rStt, aIdx.GetNode(), true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, rStt, true )) + { + aIdx = rEnd; + if( !rNds.GoNextSection( &aIdx, true, false ) || + !CheckNodesRange( rEnd, aIdx.GetNode(), true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, rEnd, false )) + { + rSectionData.SetHidden( false ); + } + } +} + +SwSection * +SwDoc::InsertSwSection(SwPaM const& rRange, SwSectionData & rNewData, + std::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const*const pTOXBaseAndMode, + SfxItemSet const*const pAttr, bool const bUpdate) +{ + const SwNode* pPrvNd = nullptr; + sal_uInt16 nRegionRet = 0; + if( rRange.HasMark() ) + { + nRegionRet = IsInsRegionAvailable( rRange, &pPrvNd ); + if( 0 == nRegionRet ) + { + // demoted to info because this is called from SwXTextSection::attach, + // so it could be invalid input + SAL_INFO("sw.core" , "InsertSwSection: rRange overlaps other sections"); + return nullptr; + } + } + + // See if the whole Document should be hidden, which we currently are not able to do. + if (rNewData.IsHidden() && rRange.HasMark()) + { + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + if( !pStt->GetContentIndex() && + pEnd->GetNode().GetContentNode()->Len() == + pEnd->GetContentIndex() ) + { + ::lcl_CheckEmptyLayFrame( GetNodes(), + rNewData, + pStt->GetNode(), + pEnd->GetNode() ); + } + } + + SwUndoInsSection* pUndoInsSect = nullptr; + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + pUndoInsSect = new SwUndoInsSection(rRange, rNewData, pAttr, pTOXBaseAndMode); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndoInsSect) ); + GetIDocumentUndoRedo().DoUndo(false); + } + + SwSectionFormat* const pFormat = MakeSectionFormat(); + pFormat->SetFormatName(rNewData.GetSectionName()); + if ( pAttr ) + { + pFormat->SetFormatAttr( *pAttr ); + } + + SwTOXBase const*const pTOXBase(pTOXBaseAndMode ? std::get<0>(*pTOXBaseAndMode) : nullptr); + SwSectionNode* pNewSectNode = nullptr; + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern( (eOld & ~RedlineFlags::ShowMask) | RedlineFlags::Ignore ); + + if( rRange.HasMark() ) + { + auto [pSttPos, pEndPos] = const_cast<SwPaM&>(rRange).StartEnd(); // SwPosition* + if( pPrvNd && 3 == nRegionRet ) + { + OSL_ENSURE( pPrvNd, "The SectionNode is missing" ); + SwNodeIndex aStt( pSttPos->GetNode() ), aEnd( pEndPos->GetNode(), +1 ); + while( pPrvNd != aStt.GetNode().StartOfSectionNode() ) + --aStt; + while( pPrvNd != aEnd.GetNode().StartOfSectionNode() ) + ++aEnd; + + --aEnd; // End is inclusive in the InsertSection + pNewSectNode = GetNodes().InsertTextSection( + aStt.GetNode(), *pFormat, rNewData, pTOXBase, & aEnd.GetNode()); + } + else + { + if( pUndoInsSect ) + { + if( !( pPrvNd && 1 == nRegionRet ) && + pSttPos->GetContentIndex() ) + { + SwTextNode* const pTNd = + pSttPos->GetNode().GetTextNode(); + if (pTNd) + { + pUndoInsSect->SaveSplitNode( pTNd, true ); + } + } + + if ( !( pPrvNd && 2 == nRegionRet ) ) + { + SwTextNode *const pTNd = + pEndPos->GetNode().GetTextNode(); + if (pTNd && (pTNd->GetText().getLength() + != pEndPos->GetContentIndex())) + { + pUndoInsSect->SaveSplitNode( pTNd, false ); + } + } + } + + if( pPrvNd && 1 == nRegionRet ) + { + pSttPos->Assign( *pPrvNd ); + } + else if( pSttPos->GetContentIndex() ) + { + getIDocumentContentOperations().SplitNode( *pSttPos, false ); + } + + if( pPrvNd && 2 == nRegionRet ) + { + pEndPos->Assign( *pPrvNd ); + } + else + { + const SwContentNode* pCNd = pEndPos->GetNode().GetContentNode(); + if( pCNd && pCNd->Len() != pEndPos->GetContentIndex() ) + { + sal_Int32 nContent = pSttPos->GetContentIndex(); + getIDocumentContentOperations().SplitNode( *pEndPos, false ); + + SwTextNode* pTNd; + if( pEndPos->GetNodeIndex() == pSttPos->GetNodeIndex() ) + { + pSttPos->Adjust(SwNodeOffset(-1)); + pEndPos->Adjust(SwNodeOffset(-1)); + pTNd = pSttPos->GetNode().GetTextNode(); + pSttPos->SetContent( nContent ); + } + else + { + // Set to the end of the previous + pEndPos->Adjust(SwNodeOffset(-1)); + pTNd = pEndPos->GetNode().GetTextNode(); + } + nContent = pTNd ? pTNd->GetText().getLength() : 0; + pEndPos->SetContent( nContent ); + } + } + pNewSectNode = GetNodes().InsertTextSection( + pSttPos->GetNode(), *pFormat, rNewData, pTOXBase, &pEndPos->GetNode()); + } + } + else + { + const SwPosition* pPos = rRange.GetPoint(); + const SwContentNode* pCNd = pPos->GetNode().GetContentNode(); + if( !pPos->GetContentIndex() ) + { + pNewSectNode = GetNodes().InsertTextSection( + pPos->GetNode(), *pFormat, rNewData, pTOXBase, nullptr); + } + else if( pPos->GetContentIndex() == pCNd->Len() ) + { + pNewSectNode = GetNodes().InsertTextSection( + pPos->GetNode(), *pFormat, rNewData, pTOXBase, nullptr, false); + } + else + { + if( pUndoInsSect && pCNd->IsTextNode() ) + { + pUndoInsSect->SaveSplitNode( const_cast<SwTextNode*>(static_cast<const SwTextNode*>(pCNd)), true ); + } + getIDocumentContentOperations().SplitNode( *pPos, false ); + pNewSectNode = GetNodes().InsertTextSection( + pPos->GetNode(), *pFormat, rNewData, pTOXBase, nullptr); + } + } + + pNewSectNode->CheckSectionCondColl(); + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pNewSectNode->EndOfSectionNode(), *pNewSectNode, SwNodeOffset(1) ); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + { + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + else + { + getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + + // Is a Condition set? + if (rNewData.IsHidden() && !rNewData.GetCondition().isEmpty()) + { + // The calculate up to that position + SwCalc aCalc( *this ); + if( ! IsInReading() ) + { + getIDocumentFieldsAccess().FieldsToCalc(aCalc, pNewSectNode->GetIndex(), SAL_MAX_INT32); + } + SwSection& rNewSect = pNewSectNode->GetSection(); + rNewSect.SetCondHidden( aCalc.Calculate( rNewSect.GetCondition() ).GetBool() ); + } + + bool bUpdateFootnote = false; + if( !GetFootnoteIdxs().empty() && pAttr ) + { + sal_uInt16 nVal = pAttr->Get( RES_FTN_AT_TXTEND ).GetValue(); + if( ( FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal ) || + ( FTNEND_ATTXTEND_OWNNUMSEQ == ( nVal = pAttr->Get( RES_END_AT_TXTEND ).GetValue() ) || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal )) + { + bUpdateFootnote = true; + } + } + + if( pUndoInsSect ) + { + pUndoInsSect->SetSectNdPos( pNewSectNode->GetIndex() ); + pUndoInsSect->SetUpdateFootnoteFlag( bUpdateFootnote ); + GetIDocumentUndoRedo().DoUndo(bUndo); + } + + if (rNewData.IsLinkType()) + { + pNewSectNode->GetSection().CreateLink( bUpdate ? LinkCreateType::Update : LinkCreateType::Connect ); + } + + if( bUpdateFootnote ) + { + GetFootnoteIdxs().UpdateFootnote( *pNewSectNode ); + } + + getIDocumentState().SetModified(); + return &pNewSectNode->GetSection(); +} + +sal_uInt16 SwDoc::IsInsRegionAvailable( const SwPaM& rRange, + const SwNode** ppSttNd ) +{ + sal_uInt16 nRet = 1; + if( rRange.HasMark() ) + { + // See if we have a valid Section + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + + const SwContentNode* pCNd = pEnd->GetNode().GetContentNode(); + const SwNode* pNd = &pStt->GetNode(); + const SwSectionNode* pSectNd = pNd->FindSectionNode(); + const SwSectionNode* pEndSectNd = pCNd ? pCNd->FindSectionNode() : nullptr; + if( pSectNd && pEndSectNd && pSectNd != pEndSectNd ) + { + // Try to create an enclosing Section, but only if Start is + // located at the Section's beginning and End at it's end + nRet = 0; + if( !pStt->GetContentIndex() + && pSectNd->GetIndex() == pStt->GetNodeIndex() - 1 + && pEnd->GetContentIndex() == pCNd->Len() ) + { + SwNodeIndex aIdx( pStt->GetNode(), -1 ); + SwNodeOffset nCmp = pEnd->GetNodeIndex(); + const SwStartNode* pPrvNd; + const SwEndNode* pNxtNd; + while( nullptr != ( pPrvNd = (pNd = &aIdx.GetNode())->GetSectionNode() ) && + ( aIdx.GetIndex() >= nCmp || + nCmp >= pPrvNd->EndOfSectionIndex() ) ) + { + --aIdx; + } + if( !pPrvNd ) + pPrvNd = pNd->IsStartNode() ? static_cast<const SwStartNode*>(pNd) + : pNd->StartOfSectionNode(); + + aIdx = pEnd->GetNodeIndex() + 1; + nCmp = pStt->GetNodeIndex(); + while( nullptr != ( pNxtNd = (pNd = &aIdx.GetNode())->GetEndNode() ) && + pNxtNd->StartOfSectionNode()->IsSectionNode() && + ( pNxtNd->StartOfSectionIndex() >= nCmp || + nCmp >= aIdx.GetIndex() ) ) + { + ++aIdx; + } + if( !pNxtNd ) + pNxtNd = pNd->EndOfSectionNode(); + + if( pPrvNd && pNxtNd && pPrvNd == pNxtNd->StartOfSectionNode() ) + { + nRet = 3; + + if( ppSttNd ) + *ppSttNd = pPrvNd; + } + } + } + else if( !pSectNd && pEndSectNd ) + { + // Try to create an enclosing Section, but only if the End + // is at the Section's end. + nRet = 0; + if( pEnd->GetContentIndex() == pCNd->Len() ) + { + SwNodeIndex aIdx( pEnd->GetNode(), 1 ); + if( aIdx.GetNode().IsEndNode() && + nullptr != aIdx.GetNode().FindSectionNode() ) + { + do { + ++aIdx; + } while( aIdx.GetNode().IsEndNode() && + nullptr != aIdx.GetNode().FindSectionNode() ); + { + nRet = 2; + if( ppSttNd ) + { + --aIdx; + *ppSttNd = &aIdx.GetNode(); + } + } + } + } + } + else if( pSectNd && !pEndSectNd ) + { + // Try to create an enclosing Section, but only if Start + // is at the Section's start. + nRet = 0; + if( !pStt->GetContentIndex() ) + { + SwNodeIndex aIdx( pStt->GetNode(), -1 ); + if( aIdx.GetNode().IsSectionNode() ) + { + do { + --aIdx; + } while( aIdx.GetNode().IsSectionNode() ); + if( !aIdx.GetNode().IsSectionNode() ) + { + nRet = 1; + if( ppSttNd ) + { + ++aIdx; + *ppSttNd = &aIdx.GetNode(); + } + } + } + } + } + } + return nRet; +} + +SwSection* SwDoc::GetCurrSection( const SwPosition& rPos ) +{ + const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode(); + if( pSectNd ) + return const_cast<SwSection*>(&pSectNd->GetSection()); + return nullptr; +} + +SwSectionFormat* SwDoc::MakeSectionFormat() +{ + SwSectionFormat* pNew = new SwSectionFormat( mpDfltFrameFormat.get(), this ); + mpSectionFormatTable->push_back( pNew ); + return pNew; +} + +void SwDoc::DelSectionFormat( SwSectionFormat *pFormat, bool bDelNodes ) +{ + SwSectionFormats::iterator itFormatPos = std::find( mpSectionFormatTable->begin(), mpSectionFormatTable->end(), pFormat ); + + GetIDocumentUndoRedo().StartUndo(SwUndoId::DELSECTION, nullptr); + + if( mpSectionFormatTable->end() != itFormatPos ) + { + const SwNodeIndex* pIdx = pFormat->GetContent( false ).GetContentIdx(); + const SfxPoolItem* pFootnoteEndAtTextEnd = pFormat->GetItemIfSet( + RES_FTN_AT_TXTEND); + if( !pFootnoteEndAtTextEnd ) + pFootnoteEndAtTextEnd = pFormat->GetItemIfSet(RES_END_AT_TXTEND); + + const SwSectionNode* pSectNd; + + if( GetIDocumentUndoRedo().DoesUndo() ) + { + if( bDelNodes && pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aUpdIdx( *pIdx ); + SwPaM aPaM( *pSectNd->EndOfSectionNode(), *pSectNd ); + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(aPaM, SwDeleteFlags::Default)); + if( pFootnoteEndAtTextEnd ) + GetFootnoteIdxs().UpdateFootnote( aUpdIdx.GetNode() ); + getIDocumentState().SetModified(); + //#126178# start/end undo have to be pairs! + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + return ; + } + GetIDocumentUndoRedo().AppendUndo( MakeUndoDelSection( *pFormat ) ); + } + else if( bDelNodes && pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aUpdIdx( *pIdx ); + getIDocumentContentOperations().DeleteSection( const_cast<SwNode*>(static_cast<SwNode const *>(pSectNd)) ); + if( pFootnoteEndAtTextEnd ) + GetFootnoteIdxs().UpdateFootnote( aUpdIdx.GetNode() ); + getIDocumentState().SetModified(); + //#126178# start/end undo have to be pairs! + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + return ; + } + + pFormat->RemoveAllUnos(); + + // A ClearRedo could result in a recursive call of this function and delete some section + // formats, thus the position inside the SectionFormatTable could have changed + itFormatPos = std::find( mpSectionFormatTable->begin(), mpSectionFormatTable->end(), pFormat ); + + // WARNING: First remove from the array and then delete, + // as the Section DTOR tries to delete it's format itself. + mpSectionFormatTable->erase( itFormatPos ); + + SwNodeOffset nCnt(0), nSttNd(0); + if( pIdx && &GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + nSttNd = pSectNd->GetIndex(); + nCnt = pSectNd->EndOfSectionIndex() - nSttNd - 1; + } + + delete pFormat; + + if( nSttNd && pFootnoteEndAtTextEnd ) + { + SwNodeIndex aUpdIdx( GetNodes(), nSttNd ); + GetFootnoteIdxs().UpdateFootnote( aUpdIdx.GetNode() ); + } + + SwContentNode* pCNd; + for( ; nCnt--; ++nSttNd ) + if( nullptr != (pCNd = GetNodes()[ nSttNd ]->GetContentNode() ) && + RES_CONDTXTFMTCOLL == pCNd->GetFormatColl()->Which() ) + pCNd->ChkCondColl(); + } + + GetIDocumentUndoRedo().EndUndo(SwUndoId::DELSECTION, nullptr); + + if (GetIDocumentUndoRedo().DoesUndo()) + { // TODO is this ever needed? + getIDocumentState().SetModified(); + } +} + +void SwDoc::UpdateSection( size_t const nPos, SwSectionData & rNewData, + SfxItemSet const*const pAttr, bool const bPreventLinkUpdate ) +{ + SwSectionFormat* pFormat = (*mpSectionFormatTable)[ nPos ]; + SwSection* pSection = pFormat->GetSection(); + + /// remember hidden condition flag of SwSection before changes + bool bOldCondHidden = pSection->IsCondHidden(); + + if (pSection->DataEquals(rNewData)) + { + // Check Attributes + bool bOnlyAttrChg = false; + if( pAttr && pAttr->Count() ) + { + SfxItemIter aIter( *pAttr ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if (pFormat->GetFormatAttr(pItem->Which()) != *pItem) + { + bOnlyAttrChg = true; + break; + } + + pItem = aIter.NextItem(); + } while (pItem); + } + + if( bOnlyAttrChg ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + MakeUndoUpdateSection( *pFormat, true ) ); + } + // #i32968# Inserting columns in the section causes MakeFrameFormat + // to put two objects of type SwUndoFrameFormat on the undo stack. + // We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + pFormat->SetFormatAttr( *pAttr ); + getIDocumentState().SetModified(); + } + return; + } + + // Test if the whole Content Section (Document/TableBox/Fly) should be hidden, + // which we're currently not able to do. + const SwNodeIndex* pIdx = nullptr; + { + if (rNewData.IsHidden()) + { + pIdx = pFormat->GetContent().GetContentIdx(); + if (pIdx) + { + const SwSectionNode* pSectNd = + pIdx->GetNode().GetSectionNode(); + if (pSectNd) + { + ::lcl_CheckEmptyLayFrame( GetNodes(), rNewData, + *pSectNd, *pSectNd->EndOfSectionNode() ); + } + } + } + } + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(MakeUndoUpdateSection(*pFormat, false)); + } + // #i32968# Inserting columns in the section causes MakeFrameFormat to put two + // objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // The LinkFileName could only consist of separators + OUString sCompareString = OUStringChar(sfx2::cTokenSeparator) + OUStringChar(sfx2::cTokenSeparator); + const bool bUpdate = + (!pSection->IsLinkType() && rNewData.IsLinkType()) + || (!rNewData.GetLinkFileName().isEmpty() + && (rNewData.GetLinkFileName() != sCompareString) + && (rNewData.GetLinkFileName() != pSection->GetLinkFileName())); + + OUString sSectName( rNewData.GetSectionName() ); + if (sSectName != pSection->GetSectionName()) + sSectName = GetUniqueSectionName( &sSectName ); + else + sSectName.clear(); + + /// In SwSection::operator=(..) class member m_bCondHiddenFlag is always set to true. + /// IMHO this have to be changed, but I can't estimate the consequences: + /// Either it is set to true using corresponding method <SwSection.SetCondHidden(..)>, + /// or it is set to the value of SwSection which is assigned to it. + /// Discussion with AMA results that the adjustment to the assignment operator + /// could be very risky. + pSection->SetSectionData(rNewData); + + if( pAttr ) + pSection->GetFormat()->SetFormatAttr( *pAttr ); + + if( !sSectName.isEmpty() ) + { + pSection->SetSectionName( sSectName ); + } + + // Is a Condition set + if( pSection->IsHidden() && !pSection->GetCondition().isEmpty() ) + { + // Then calculate up to that position + SwCalc aCalc( *this ); + if( !pIdx ) + pIdx = pFormat->GetContent().GetContentIdx(); + getIDocumentFieldsAccess().FieldsToCalc(aCalc, pIdx->GetIndex(), SAL_MAX_INT32); + + /// Because on using SwSection::operator=() to set up <pSection> + /// with <rNewData> and the above given note, the hidden condition flag + /// has to be set to false, if hidden condition flag of <pFormat->GetSection()> + /// (SwSection before the changes) is false (already saved in <bOldCondHidden>) + /// and new calculated condition is true. + /// This is necessary, because otherwise the <SetCondHidden> would have + /// no effect. + bool bCalculatedCondHidden = + aCalc.Calculate( pSection->GetCondition() ).GetBool(); + if ( bCalculatedCondHidden && !bOldCondHidden ) + { + pSection->SetCondHidden( false ); + } + pSection->SetCondHidden( bCalculatedCondHidden ); + } + + if( bUpdate ) + pSection->CreateLink( bPreventLinkUpdate ? LinkCreateType::Connect : LinkCreateType::Update ); + else if( !pSection->IsLinkType() && pSection->IsConnected() ) + { + pSection->Disconnect(); + getIDocumentLinksAdministration().GetLinkManager().Remove( &pSection->GetBaseLink() ); + } + + getIDocumentState().SetModified(); +} + +void sw_DeleteFootnote( SwSectionNode *pNd, SwNodeOffset nStt, SwNodeOffset nEnd ) +{ + SwFootnoteIdxs& rFootnoteArr = pNd->GetDoc().GetFootnoteIdxs(); + if( rFootnoteArr.empty() ) + return; + + size_t nPos = 0; + rFootnoteArr.SeekEntry( *pNd, &nPos ); + SwTextFootnote* pSrch; + + // Delete all succeeding Footnotes + while( nPos < rFootnoteArr.size() && + SwTextFootnote_GetIndex( (pSrch = rFootnoteArr[ nPos ]) ) <= nEnd ) + { + // If the Nodes are not deleted, they need to deregister at the Pages + // (delete Frames) or else they will remain there (Undo does not delete them!) + pSrch->DelFrames(nullptr); + ++nPos; + } + + while( nPos-- && + SwTextFootnote_GetIndex( (pSrch = rFootnoteArr[ nPos ]) ) >= nStt ) + { + // If the Nodes are not deleted, they need to deregister at the Pages + // (delete Frames) or else they will remain there (Undo does not delete them!) + pSrch->DelFrames(nullptr); + } +} + +static bool lcl_IsTOXSection(SwSectionData const& rSectionData) +{ + return (SectionType::ToxContent == rSectionData.GetType()) + || (SectionType::ToxHeader == rSectionData.GetType()); +} + +SwSectionNode* SwNodes::InsertTextSection(SwNode& rNd, + SwSectionFormat& rSectionFormat, + SwSectionData const& rSectionData, + SwTOXBase const*const pTOXBase, + SwNode const*const pEndNd, + bool const bInsAtStart, bool const bCreateFrames) +{ + SwNodeIndex aInsPos( rNd ); + if( !pEndNd ) // No Area, thus create a new Section before/after it + { + // #i26762# + OSL_ENSURE(!pEndNd || rNd.GetIndex() <= pEndNd->GetIndex(), + "Section start and end in wrong order!"); + + if( bInsAtStart ) + { + if (!lcl_IsTOXSection(rSectionData)) + { + do { + --aInsPos; + } while( aInsPos.GetNode().IsSectionNode() ); + ++aInsPos; + } + } + else + { + ++aInsPos; + if (!lcl_IsTOXSection(rSectionData)) + { + SwNode* pNd; + while( aInsPos.GetIndex() < Count() - 1 && + ( pNd = &aInsPos.GetNode())->IsEndNode() && + pNd->StartOfSectionNode()->IsSectionNode()) + { + ++aInsPos; + } + } + } + } + + SwSectionNode *const pSectNd = + new SwSectionNode(aInsPos.GetNode(), rSectionFormat, pTOXBase); + + if (lcl_IsTOXSection(rSectionData)) + { + // We're inserting a ToX. Make sure that if a redline ends right before the ToX start, then + // that end now doesn't cross a section start node. + SwRedlineTable& rRedlines = GetDoc().getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type nIndex = 0; nIndex < rRedlines.size(); ++nIndex) + { + SwRangeRedline* pRedline = rRedlines[nIndex]; + if ( RedlineType::Delete != pRedline->GetType() || + !pRedline->HasMark() || pRedline->GetMark()->GetNode() != aInsPos.GetNode() ) + { + continue; + } + + // The redline ends at the new section content start, so it originally ended before the + // section start: move it back. + SwPaM aRedlineEnd(*pRedline->GetMark()); + aRedlineEnd.Move(fnMoveBackward); + *pRedline->GetMark() = *aRedlineEnd.GetPoint(); + break; + } + } + + if( pEndNd ) + { + // Special case for the Reader/Writer + if( *pEndNd != GetEndOfContent() ) + aInsPos = pEndNd->GetIndex()+1; + // #i58710: We created a RTF document with a section break inside a table cell + // We are not able to handle a section start inside a table and the section end outside. + const SwNode* pLastNode = pSectNd->StartOfSectionNode()->EndOfSectionNode(); + if( aInsPos > pLastNode->GetIndex() ) + aInsPos = pLastNode->GetIndex(); + // Another way round: if the section starts outside a table but the end is inside... + // aInsPos is at the moment the Position where my EndNode will be inserted + const SwStartNode* pStartNode = aInsPos.GetNode().StartOfSectionNode(); + // This StartNode should be in front of me, but if not, I want to survive + SwNodeOffset nMyIndex = pSectNd->GetIndex(); + if( pStartNode->GetIndex() > nMyIndex ) // Suspicious! + { + const SwNode* pTemp; + do + { + pTemp = pStartNode; // pTemp is a suspicious one + pStartNode = pStartNode->StartOfSectionNode(); + } + while( pStartNode->GetIndex() > nMyIndex ); + pTemp = pTemp->EndOfSectionNode(); + // If it starts behind me but ends behind my end... + if( pTemp->GetIndex() >= aInsPos.GetIndex() ) + aInsPos = pTemp->GetIndex()+1; // ...I have to correct my end position + } + } + else + { + SwTextNode* pCpyTNd = rNd.GetTextNode(); + if( pCpyTNd ) + { + SwTextNode* pTNd = new SwTextNode( aInsPos.GetNode(), pCpyTNd->GetTextColl() ); + if( pCpyTNd->HasSwAttrSet() ) + { + // Move PageDesc/Break to the first Node of the section + const SfxItemSet& rSet = *pCpyTNd->GetpSwAttrSet(); + if( SfxItemState::SET == rSet.GetItemState( RES_BREAK ) || + SfxItemState::SET == rSet.GetItemState( RES_PAGEDESC )) + { + SfxItemSet aSet( rSet ); + if( bInsAtStart ) + pCpyTNd->ResetAttr( RES_PAGEDESC, RES_BREAK ); + else + { + aSet.ClearItem( RES_PAGEDESC ); + aSet.ClearItem( RES_BREAK ); + } + pTNd->SetAttr( aSet ); + } + else + pTNd->SetAttr( rSet ); + } + // Do not forget to create the Frame! + pCpyTNd->MakeFramesForAdjacentContentNode(*pTNd); + } + else + new SwTextNode( aInsPos.GetNode(), GetDoc().GetDfltTextFormatColl() ); + } + new SwEndNode( aInsPos.GetNode(), *pSectNd ); + + pSectNd->GetSection().SetSectionData(rSectionData); + SwSectionFormat* pSectFormat = pSectNd->GetSection().GetFormat(); + + // We could optimize this, by not removing already contained Frames and recreating them, + // but by simply rewiring them + bool bInsFrame = bCreateFrames && !pSectNd->GetSection().IsHiddenFlag() && + GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + std::optional<SwNode2LayoutSaveUpperFrames> oNode2Layout; + if( bInsFrame ) + { + if( !pSectNd->GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() ) ) + // Collect all Uppers + oNode2Layout.emplace(*pSectNd); + } + + // Set the right StartNode for all in this Area + SwNodeOffset nEnd = pSectNd->EndOfSectionIndex(); + SwNodeOffset nStart = pSectNd->GetIndex()+1; + SwNodeOffset nSkipIdx = NODE_OFFSET_MAX; + for( SwNodeOffset n = nStart; n < nEnd; ++n ) + { + SwNode* pNd = (*this)[n]; + + // Attach all Sections in the NodeSection underneath the new one + if( NODE_OFFSET_MAX == nSkipIdx ) + pNd->m_pStartOfSection = pSectNd; + else if( n >= nSkipIdx ) + nSkipIdx = NODE_OFFSET_MAX; + + if( pNd->IsStartNode() ) + { + // Make up the Format's nesting + if( pNd->IsSectionNode() ) + { + static_cast<SwSectionNode*>(pNd)->GetSection().GetFormat()-> + SetDerivedFrom( pSectFormat ); + static_cast<SwSectionNode*>(pNd)->DelFrames(); + n = pNd->EndOfSectionIndex(); + } + else + { + if( pNd->IsTableNode() ) + static_cast<SwTableNode*>(pNd)->DelFrames(); + + if( NODE_OFFSET_MAX == nSkipIdx ) + nSkipIdx = pNd->EndOfSectionIndex(); + } + } + else if( pNd->IsContentNode() ) + static_cast<SwContentNode*>(pNd)->DelFrames(nullptr); + } + + sw_DeleteFootnote( pSectNd, nStart, nEnd ); + + if( bInsFrame ) + { + if( oNode2Layout ) + { + SwNodeOffset nIdx = pSectNd->GetIndex(); + oNode2Layout->RestoreUpperFrames( pSectNd->GetNodes(), nIdx, nIdx + 1 ); + oNode2Layout.reset(); + } + else + pSectNd->MakeOwnFrames(&aInsPos); + } + + return pSectNd; +} + +SwSectionNode* SwNode::FindSectionNode() +{ + if( IsSectionNode() ) + return GetSectionNode(); + SwStartNode* pTmp = m_pStartOfSection; + while( !pTmp->IsSectionNode() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return pTmp->GetSectionNode(); +} + +// SwSectionNode + +// ugly hack to make m_pSection const +static SwSectionFormat & +lcl_initParent(SwSectionNode & rThis, SwSectionFormat & rFormat) +{ + SwSectionNode *const pParent = + rThis.StartOfSectionNode()->FindSectionNode(); + if( pParent ) + { + // Register the Format at the right Parent + rFormat.SetDerivedFrom( pParent->GetSection().GetFormat() ); + } + return rFormat; +} + +SwSectionNode::SwSectionNode(const SwNode& rWhere, + SwSectionFormat & rFormat, SwTOXBase const*const pTOXBase) + : SwStartNode( rWhere, SwNodeType::Section ) + , m_pSection( pTOXBase + ? new SwTOXBaseSection(*pTOXBase, lcl_initParent(*this, rFormat)) + : new SwSection( SectionType::Content, rFormat.GetName(), + lcl_initParent(*this, rFormat) ) ) +{ + // Set the connection from Format to Node + // Suppress Modify; no one's interested anyway + rFormat.LockModify(); + rFormat.SetFormatAttr( SwFormatContent( this ) ); + rFormat.UnlockModify(); +} + +SwSectionNode::~SwSectionNode() +{ + // mba: test if iteration works as clients will be removed in callback + // use hint which allows to specify, if the content shall be saved or not + m_pSection->GetFormat()->CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( true ) ); + SwSectionFormat* pFormat = m_pSection->GetFormat(); + if( pFormat ) + { + // Remove the Attribute, because the Section deletes it's Format + // and it will neutralize the Section, if the Content Attribute is set + pFormat->LockModify(); + pFormat->ResetFormatAttr( RES_CNTNT ); + pFormat->UnlockModify(); + } +} + +SwFrame *SwSectionNode::MakeFrame( SwFrame *pSib ) +{ + m_pSection->m_Data.SetHiddenFlag(false); + return new SwSectionFrame( *m_pSection, pSib ); +} + +// Creates all Document Views for the preceding Node. +// The created ContentFrames are attached to the corresponding Layout +void SwSectionNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx) +{ + // Take my successive or preceding ContentFrame + SwNodes& rNds = GetNodes(); + if( !(rNds.IsDocNodes() && rNds.GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell()) ) + return; + + if (GetSection().IsHiddenFlag() || IsContentHidden()) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false ); + if( !pCNd ) + { + aIdx = *this; + pCNd = SwNodes::GoPrevSection(&aIdx, true, false); + if (!pCNd) + return; + } + pCNd = aIdx.GetNode().GetContentNode(); + pCNd->MakeFramesForAdjacentContentNode(static_cast<SwContentNode&>(rIdx.GetNode())); + } + else + { + SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() ); + SwFrame *pFrame; + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + OSL_ENSURE( pFrame->IsSctFrame(), "Depend of Section not a Section." ); + if (pFrame->getRootFrame()->HasMergedParas() + && !rIdx.GetNode().IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwFrame *pNew = rIdx.GetNode().GetContentNode()->MakeFrame( pFrame ); + + SwSectionNode* pS = rIdx.GetNode().FindSectionNode(); + + // Assure that node is not inside a table, which is inside the + // found section. + if ( pS ) + { + SwTableNode* pTableNode = rIdx.GetNode().FindTableNode(); + if ( pTableNode && + pTableNode->GetIndex() > pS->GetIndex() ) + { + pS = nullptr; + } + } + + // if the node is in a section, the sectionframe now + // has to be created... + // boolean to control <Init()> of a new section frame. + bool bInitNewSect = false; + if( pS ) + { + SwSectionFrame *pSct = new SwSectionFrame( pS->GetSection(), pFrame ); + // prepare <Init()> of new section frame. + bInitNewSect = true; + SwLayoutFrame* pUp = pSct; + while( pUp->Lower() ) // for columned sections + { + OSL_ENSURE( pUp->Lower()->IsLayoutFrame(),"Who's in there?" ); + pUp = static_cast<SwLayoutFrame*>(pUp->Lower()); + } + pNew->Paste( pUp ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + auto pPrev = pNew->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + pNew = pSct; + } + + // If a Node got Frames attached before or after + if ( rIdx < GetIndex() ) + // the new one precedes me + pNew->Paste( pFrame->GetUpper(), pFrame ); + else + // the new one succeeds me + pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + auto pPrev = pNew->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + if ( bInitNewSect ) + static_cast<SwSectionFrame*>(pNew)->Init(); + } + } +} + +// Create a new SectionFrame for every occurrence in the Layout and insert before +// the corresponding ContentFrame +void SwSectionNode::MakeOwnFrames(SwNodeIndex* pIdxBehind, SwNodeIndex* pEndIdx) +{ + OSL_ENSURE( pIdxBehind, "no Index" ); + SwNodes& rNds = GetNodes(); + SwDoc& rDoc = rNds.GetDoc(); + + *pIdxBehind = *this; + + m_pSection->m_Data.SetHiddenFlag(true); + + if( rNds.IsDocNodes() ) + { + if( pEndIdx ) + ::MakeFrames( &rDoc, pIdxBehind->GetNode(), pEndIdx->GetNode() ); + else + ::MakeFrames( &rDoc, pIdxBehind->GetNode(), SwNodeIndex( *EndOfSectionNode(), 1 ).GetNode() ); + } +} + +void SwSectionNode::DelFrames(SwRootFrame const*const /*FIXME TODO*/, bool const bForce) +{ + SwNodeOffset nStt = GetIndex()+1, nEnd = EndOfSectionIndex(); + if( nStt >= nEnd ) + { + return ; + } + + SwNodes& rNds = GetNodes(); + m_pSection->GetFormat()->DelFrames(); + + // Update our Flag + m_pSection->m_Data.SetHiddenFlag(true); + + // If the Area is within a Fly or TableBox, we can only hide it if + // there is more Content which has Frames. + // Or else the Fly/TableBox Frame does not have a Lower! + if (bForce) + return; + + SwNodeIndex aIdx( *this ); + if( !SwNodes::GoPrevSection( &aIdx, true, false ) || + !CheckNodesRange( *this, aIdx.GetNode(), true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, *this, true )) + { + aIdx = *EndOfSectionNode(); + if( !rNds.GoNextSection( &aIdx, true, false ) || + !CheckNodesRange( *EndOfSectionNode(), aIdx.GetNode(), true ) || + // #i21457# + !lcl_IsInSameTableBox( rNds, *EndOfSectionNode(), false )) + { + m_pSection->m_Data.SetHiddenFlag(false); + } + } +} + +SwSectionNode* SwSectionNode::MakeCopy( SwDoc& rDoc, const SwNodeIndex& rIdx ) const +{ + // In which array am I: Nodes, UndoNodes? + const SwNodes& rNds = GetNodes(); + + // Copy the SectionFrameFormat + SwSectionFormat* pSectFormat = rDoc.MakeSectionFormat(); + pSectFormat->CopyAttrs( *GetSection().GetFormat() ); + + std::unique_ptr<SwTOXBase> pTOXBase; + if (SectionType::ToxContent == GetSection().GetType()) + { + assert( dynamic_cast< const SwTOXBaseSection* >( &GetSection() ) && "no TOXBaseSection!" ); + SwTOXBaseSection const& rTBS( + dynamic_cast<SwTOXBaseSection const&>(GetSection())); + pTOXBase.reset( new SwTOXBase(rTBS, &rDoc) ); + } + + SwSectionNode *const pSectNd = + new SwSectionNode(rIdx.GetNode(), *pSectFormat, pTOXBase.get()); + SwEndNode* pEndNd = new SwEndNode( rIdx.GetNode(), *pSectNd ); + SwNodeIndex aInsPos( *pEndNd ); + + // Take over values + SwSection *const pNewSect = pSectNd->m_pSection.get(); + + if (SectionType::ToxContent != GetSection().GetType()) + { + // Keep the Name for Move + if( &rNds.GetDoc() == &rDoc && rDoc.IsCopyIsMove() ) + { + pNewSect->SetSectionName( GetSection().GetSectionName() ); + } + else + { + const OUString sSectionName(GetSection().GetSectionName()); + pNewSect->SetSectionName(rDoc.GetUniqueSectionName( &sSectionName )); + } + } + + pNewSect->SetType( GetSection().GetType() ); + pNewSect->SetCondition( GetSection().GetCondition() ); + pNewSect->SetCondHidden( GetSection().IsCondHidden() ); + pNewSect->SetLinkFileName( GetSection().GetLinkFileName() ); + if( !pNewSect->IsHiddenFlag() && GetSection().IsHidden() ) + pNewSect->SetHidden(); + if( !pNewSect->IsProtectFlag() && GetSection().IsProtect() ) + pNewSect->SetProtect(); + // edit in readonly sections + if( !pNewSect->IsEditInReadonlyFlag() && GetSection().IsEditInReadonly() ) + pNewSect->SetEditInReadonly(); + + SwNodeRange aRg( *this, SwNodeOffset(+1), *EndOfSectionNode() ); // Where am I? + rNds.Copy_( aRg, aInsPos.GetNode(), false ); + + // Delete all Frames from the copied Area. They are created when creating + // the SectionFrames. + pSectNd->DelFrames(); + + // Copy the Links/Server + if( pNewSect->IsLinkType() ) // Add the Link + pNewSect->CreateLink( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ? LinkCreateType::Connect : LinkCreateType::NONE ); + + // If we copy from the Undo as Server, enter it again + if (m_pSection->IsServer() + && rDoc.GetIDocumentUndoRedo().IsUndoNodes(rNds)) + { + pNewSect->SetRefObject( m_pSection->GetObject() ); + rDoc.getIDocumentLinksAdministration().GetLinkManager().InsertServer( pNewSect->GetObject() ); + } + + // METADATA: copy xml:id; must be done after insertion of node + pSectFormat->RegisterAsCopyOf(*GetSection().GetFormat()); + + return pSectNd; +} + +bool SwSectionNode::IsContentHidden() const +{ + OSL_ENSURE( !m_pSection->IsHidden(), + "That's simple: Hidden Section => Hidden Content" ); + SwNodeIndex aTmp( *this, 1 ); + SwNodeOffset nEnd = EndOfSectionIndex(); + while( aTmp < nEnd ) + { + if( aTmp.GetNode().IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode&>(aTmp.GetNode()).GetSection(); + if( rSect.IsHiddenFlag() ) + // Skip this Section + aTmp = *aTmp.GetNode().EndOfSectionNode(); + } + else + { + if( aTmp.GetNode().IsContentNode() || aTmp.GetNode().IsTableNode() ) + return false; // We found non-hidden content + OSL_ENSURE( aTmp.GetNode().IsEndNode(), "EndNode expected" ); + } + ++aTmp; + } + return true; // Hide everything +} + +void SwSectionNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("section")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("type"), + BAD_CAST(OString::number(static_cast<sal_uInt8>(GetNodeType())).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr())); + + if (m_pSection) + { + m_pSection->dumpAsXml(pWriter); + } + + // (void)xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested +} + +void SwSectionNode::NodesArrChgd() +{ + SwSectionFormat *const pFormat = m_pSection->GetFormat(); + if( !pFormat ) + return; + + SwNodes& rNds = GetNodes(); + SwDoc* pDoc = pFormat->GetDoc(); + + if( !rNds.IsDocNodes() ) + { + pFormat->RemoveAllUnos(); + } + + pFormat->LockModify(); + pFormat->SetFormatAttr( SwFormatContent( this )); + pFormat->UnlockModify(); + + SwSectionNode* pSectNd = StartOfSectionNode()->FindSectionNode(); + // set the correct parent from the new section + pFormat->SetDerivedFrom( pSectNd ? pSectNd->GetSection().GetFormat() + : pDoc->GetDfltFrameFormat() ); + + // Set the right StartNode for all in this Area + SwNodeOffset nStart = GetIndex()+1, nEnd = EndOfSectionIndex(); + for( SwNodeOffset n = nStart; n < nEnd; ++n ) + { + // Make up the Format's nesting + pSectNd = rNds[ n ]->GetSectionNode(); + if( nullptr != pSectNd ) + { + pSectNd->GetSection().GetFormat()->SetDerivedFrom( pFormat ); + n = pSectNd->EndOfSectionIndex(); + } + } + + // Moving Nodes to the UndoNodes array? + if( rNds.IsDocNodes() ) + { + OSL_ENSURE( pDoc == &GetDoc(), + "Moving to different Documents?" ); + if( m_pSection->IsLinkType() ) // Remove the Link + m_pSection->CreateLink( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ? LinkCreateType::Connect : LinkCreateType::NONE ); + + if (m_pSection->IsServer()) + pDoc->getIDocumentLinksAdministration().GetLinkManager().InsertServer( m_pSection->GetObject() ); + } + else + { + if (SectionType::Content != m_pSection->GetType() + && m_pSection->IsConnected()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( &m_pSection->GetBaseLink() ); + } + if (m_pSection->IsServer()) + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_pSection->GetObject() ); + } + +} + +OUString SwDoc::GetUniqueSectionName( const OUString* pChkStr ) const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeSection" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpSectionFormatTable->size() + 1 ); + if( pChkStr ) + newName += *pChkStr; + return newName; + } + + const OUString aName(SwResId(STR_REGION_DEFNAME)); + + SwSectionFormats::size_type nNum = 0; + const SwSectionFormats::size_type nFlagSize = ( mpSectionFormatTable->size() / 8 ) + 2; + std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( auto pFormat : *mpSectionFormatTable ) + { + const SwSectionNode *const pSectNd = pFormat->GetSectionNode(); + if( pSectNd != nullptr ) + { + const OUString& rNm = pSectNd->GetSection().GetSectionName(); + if (rNm.startsWith( aName )) + { + // Calculate the Number and reset the Flag + nNum = o3tl::toInt32(rNm.subView( aName.getLength() )); + if( nNum-- && nNum < mpSectionFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if( pChkStr && *pChkStr==rNm ) + pChkStr = nullptr; + } + } + + if( !pChkStr ) + { + // Flagged all Numbers accordingly, so get the right Number + nNum = mpSectionFormatTable->size(); + for( SwSectionFormats::size_type n = 0; n < nFlagSize; ++n ) + { + auto nTmp = pSetFlags[ n ]; + if( nTmp != 0xFF ) + { + // Calculate the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + } + pSetFlags.reset(); + if( pChkStr ) + return *pChkStr; + return aName + OUString::number( ++nNum ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndsect.hxx b/sw/source/core/docnode/ndsect.hxx new file mode 100644 index 0000000000..57a9f7080c --- /dev/null +++ b/sw/source/core/docnode/ndsect.hxx @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_DOCNODE_NDSECT_HXX +#define INCLUDED_SW_SOURCE_CORE_DOCNODE_NDSECT_HXX + +#include <nodeoffset.hxx> + +class SwSectionNode; + +void sw_DeleteFootnote(SwSectionNode* pNd, SwNodeOffset nStt, SwNodeOffset nEnd); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx new file mode 100644 index 0000000000..a7a2bee478 --- /dev/null +++ b/sw/source/core/docnode/ndtbl.cxx @@ -0,0 +1,4653 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <libxml/xmlwriter.h> +#include <config_wasm_strip.h> +#include <memory> +#include <fesh.hxx> +#include <hintids.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/boxitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/shaditem.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtfordr.hxx> +#include <fmtpdsc.hxx> +#include <fmtanchr.hxx> +#include <fmtlsplt.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <pagefrm.hxx> +#include <tabcol.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <UndoManager.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <swcrsr.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <tblsel.hxx> +#include <poolfmt.hxx> +#include <tabfrm.hxx> +#include <UndoCore.hxx> +#include <UndoRedline.hxx> +#include <UndoDelete.hxx> +#include <UndoNumbering.hxx> +#include <UndoTable.hxx> +#include <hints.hxx> +#include <tblafmt.hxx> +#include <frminf.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <rolbck.hxx> +#include <tblrwcl.hxx> +#include <editsh.hxx> +#include <txtfrm.hxx> +#include <section.hxx> +#include <frmtool.hxx> +#include <node2lay.hxx> +#include <strings.hrc> +#include <docsh.hxx> +#include <unochart.hxx> +#include <node.hxx> +#include <ndtxt.hxx> +#include <cstdlib> +#include <map> +#include <algorithm> +#include <rootfrm.hxx> +#include <fldupde.hxx> +#include <calbck.hxx> +#include <fntcache.hxx> +#include <frameformats.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/string_view.hxx> +#include <svl/numformat.hxx> +#include <tools/datetimeutils.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +const sal_Unicode T2T_PARA = 0x0a; + +static void lcl_SetDfltBoxAttr( SwFrameFormat& rFormat, sal_uInt8 nId ) +{ + bool bTop = false, bBottom = false, bLeft = false, bRight = false; + switch ( nId ) + { + case 0: bTop = bBottom = bLeft = true; break; + case 1: bTop = bBottom = bLeft = bRight = true; break; + case 2: bBottom = bLeft = true; break; + case 3: bBottom = bLeft = bRight = true; break; + } + + const bool bHTML = rFormat.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE); + Color aCol( bHTML ? COL_GRAY : COL_BLACK ); + // Default border in Writer: 0.5pt (matching Word) + SvxBorderLine aLine( &aCol, SvxBorderLineWidth::VeryThin ); + if ( bHTML ) + { + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + } + SvxBoxItem aBox(RES_BOX); + aBox.SetAllDistances(55); + if ( bTop ) + aBox.SetLine( &aLine, SvxBoxItemLine::TOP ); + if ( bBottom ) + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + if ( bLeft ) + aBox.SetLine( &aLine, SvxBoxItemLine::LEFT ); + if ( bRight ) + aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + rFormat.SetFormatAttr( aBox ); +} + +typedef std::map<SwFrameFormat *, SwTableBoxFormat *> DfltBoxAttrMap_t; +typedef std::vector<DfltBoxAttrMap_t *> DfltBoxAttrList_t; + +static void +lcl_SetDfltBoxAttr(SwTableBox& rBox, DfltBoxAttrList_t & rBoxFormatArr, + sal_uInt8 const nId, SwTableAutoFormat const*const pAutoFormat = nullptr) +{ + DfltBoxAttrMap_t * pMap = rBoxFormatArr[ nId ]; + if (!pMap) + { + pMap = new DfltBoxAttrMap_t; + rBoxFormatArr[ nId ] = pMap; + } + + SwTableBoxFormat* pNewTableBoxFormat = nullptr; + SwFrameFormat* pBoxFrameFormat = rBox.GetFrameFormat(); + DfltBoxAttrMap_t::iterator const iter(pMap->find(pBoxFrameFormat)); + if (pMap->end() != iter) + { + pNewTableBoxFormat = iter->second; + } + else + { + SwDoc* pDoc = pBoxFrameFormat->GetDoc(); + // format does not exist, so create it + pNewTableBoxFormat = pDoc->MakeTableBoxFormat(); + pNewTableBoxFormat->SetFormatAttr( pBoxFrameFormat->GetAttrSet().Get( RES_FRM_SIZE ) ); + + if( pAutoFormat ) + pAutoFormat->UpdateToSet( nId, false, false, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pNewTableBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + pDoc->GetNumberFormatter() ); + else + ::lcl_SetDfltBoxAttr( *pNewTableBoxFormat, nId ); + + (*pMap)[pBoxFrameFormat] = pNewTableBoxFormat; + } + rBox.ChgFrameFormat( pNewTableBoxFormat ); +} + +static SwTableBoxFormat *lcl_CreateDfltBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + sal_uInt16 nCols, sal_uInt8 nId ) +{ + if ( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + ::lcl_SetDfltBoxAttr( *pBoxFormat, nId ); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +static SwTableBoxFormat *lcl_CreateAFormatBoxFormat( SwDoc &rDoc, std::vector<SwTableBoxFormat*> &rBoxFormatArr, + const SwTableAutoFormat& rAutoFormat, + const sal_uInt16 nRows, const sal_uInt16 nCols, sal_uInt8 nId ) +{ + if( !rBoxFormatArr[nId] ) + { + SwTableBoxFormat* pBoxFormat = rDoc.MakeTableBoxFormat(); + rAutoFormat.UpdateToSet( nId, nRows==1, nCols==1, + const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pBoxFormat->GetAttrSet())), + SwTableAutoFormatUpdateFlags::Box, + rDoc.GetNumberFormatter( ) ); + if( USHRT_MAX != nCols ) + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + USHRT_MAX / nCols, 0 )); + rBoxFormatArr[ nId ] = pBoxFormat; + } + return rBoxFormatArr[nId]; +} + +SwTableNode* SwDoc::IsIdxInTable( const SwNodeIndex& rIdx ) { return IsInTable(rIdx.GetNode()); } + +SwTableNode* SwDoc::IsInTable(const SwNode& rIdx) +{ + SwNode* pNd = const_cast<SwNode*>(&rIdx); + do { + pNd = pNd->StartOfSectionNode(); + SwTableNode* pTableNd = pNd->GetTableNode(); + if( pTableNd ) + return pTableNd; + } while ( pNd->GetIndex() ); + return nullptr; +} + +/** + * Insert a new Box before the InsPos + */ +bool SwNodes::InsBoxen( SwTableNode* pTableNd, + SwTableLine* pLine, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + const SfxItemSet* pAutoAttr, + sal_uInt16 nInsPos, + sal_uInt16 nCnt ) +{ + if( !nCnt ) + return false; + OSL_ENSURE( pLine, "No valid Line" ); + + // Move Index after the Line's last Box + SwNodeOffset nIdxPos(0); + SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr; + if( !pLine->GetTabBoxes().empty() ) + { + if( nInsPos < pLine->GetTabBoxes().size() ) + { + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable(), + pLine->GetTabBoxes()[ nInsPos ] ); + if( nullptr == pPrvBox ) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + } + else + { + pNxtBox = pLine->FindNextBox( pTableNd->GetTable(), + pLine->GetTabBoxes().back() ); + if( nullptr == pNxtBox ) + pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ); + } + } + else + { + pNxtBox = pLine->FindNextBox( pTableNd->GetTable() ); + if( nullptr == pNxtBox ) + pPrvBox = pLine->FindPreviousBox( pTableNd->GetTable() ); + } + + if( !pPrvBox && !pNxtBox ) + { + bool bSetIdxPos = true; + if( !pTableNd->GetTable().GetTabLines().empty() && !nInsPos ) + { + const SwTableLine* pTableLn = pLine; + while( pTableLn->GetUpper() ) + pTableLn = pTableLn->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().GetTabLines()[ 0 ] == pTableLn ) + { + // Before the Table's first Box + while( !( pNxtBox = pLine->GetTabBoxes()[0])->GetTabLines().empty() ) + pLine = pNxtBox->GetTabLines()[0]; + nIdxPos = pNxtBox->GetSttIdx(); + bSetIdxPos = false; + } + } + if( bSetIdxPos ) + // Tables without content or at the end; move before the End + nIdxPos = pTableNd->EndOfSectionIndex(); + } + else if( pNxtBox ) // There is a successor + nIdxPos = pNxtBox->GetSttIdx(); + else // There is a predecessor + nIdxPos = pPrvBox->GetSttNd()->EndOfSectionIndex() + 1; + + SwNodeIndex aEndIdx( *this, nIdxPos ); + for( sal_uInt16 n = 0; n < nCnt; ++n ) + { + SwStartNode* pSttNd = new SwStartNode( aEndIdx.GetNode(), SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + new SwEndNode( aEndIdx.GetNode(), *pSttNd ); + + pPrvBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + + SwTableBoxes & rTabBoxes = pLine->GetTabBoxes(); + sal_uInt16 nRealInsPos = nInsPos + n; + if (nRealInsPos > rTabBoxes.size()) + nRealInsPos = rTabBoxes.size(); + + rTabBoxes.insert( rTabBoxes.begin() + nRealInsPos, pPrvBox ); + + if( ! pTextColl->IsAssignedToListLevelOfOutlineStyle() + && RES_CONDTXTFMTCOLL != pTextColl->Which() + ) + new SwTextNode( *pSttNd->EndOfSectionNode(), pTextColl, pAutoAttr ); + else + { + // Handle Outline numbering correctly! + SwTextNode* pTNd = new SwTextNode( + *pSttNd->EndOfSectionNode(), + GetDoc().GetDfltTextFormatColl(), + pAutoAttr ); + pTNd->ChgFormatColl( pTextColl ); + } + } + return true; +} + +/** + * Insert a new Table + */ +const SwTable* SwDoc::InsertTable( const SwInsertTableOptions& rInsTableOpts, + const SwPosition& rPos, sal_uInt16 nRows, + sal_uInt16 nCols, sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat, + const std::vector<sal_uInt16> *pColArr, + bool bCalledFromShell, + bool bNewModel, + const OUString& rTableName ) +{ + assert(nRows && "Table without line?"); + assert(nCols && "Table without rows?"); + + { + // Do not copy into Footnotes! + if( rPos.GetNode() < GetNodes().GetEndOfInserts() && + rPos.GetNode().GetIndex() >= GetNodes().GetEndOfInserts().StartOfSectionIndex() ) + return nullptr; + + // If the ColumnArray has a wrong count, ignore it! + if( pColArr && + static_cast<size_t>(nCols + ( text::HoriOrientation::NONE == eAdjust ? 2 : 1 )) != pColArr->size() ) + pColArr = nullptr; + } + + OUString aTableName = rTableName; + if (aTableName.isEmpty() || FindTableFormatByName(aTableName) != nullptr) + aTableName = GetUniqueTableName(); + + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsTable>( rPos, nCols, nRows, o3tl::narrowing<sal_uInt16>(eAdjust), + rInsTableOpts, pTAFormat, pColArr, + aTableName)); + } + + // Start with inserting the Nodes and get the AutoFormat for the Table + SwTextFormatColl *pBodyColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ), + *pHeadColl = pBodyColl; + + bool bDfltBorders( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder ); + + if( (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) && (1 != nRows || !bDfltBorders) ) + pHeadColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + + /* Save content node to extract FRAMEDIR from. */ + const SwContentNode * pContentNd = rPos.GetNode().GetContentNode(); + + /* If we are called from a shell pass the attrset from + pContentNd (aka the node the table is inserted at) thus causing + SwNodes::InsertTable to propagate an adjust item if + necessary. */ + SwTableNode *pTableNd = SwNodes::InsertTable( + rPos.GetNode(), + nCols, + pBodyColl, + nRows, + nRowsToRepeat, + pHeadColl, + bCalledFromShell ? &pContentNd->GetSwAttrSet() : nullptr ); + + // Create the Box/Line/Table construct + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( aTableName, GetDfltFrameFormat() ); + + /* If the node to insert the table at is a context node and has a + non-default FRAMEDIR propagate it to the table. */ + if (pContentNd) + { + const SwAttrSet & aNdSet = pContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR )) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + // Set Orientation at the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + // All lines use the left-to-right Fill-Order! + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + + // Set USHRT_MAX as the Table's default SSize + SwTwips nWidth = USHRT_MAX; + if( pColArr ) + { + sal_uInt16 nSttPos = pColArr->front(); + sal_uInt16 nLastPos = pColArr->back(); + if( text::HoriOrientation::NONE == eAdjust ) + { + sal_uInt16 nFrameWidth = nLastPos; + nLastPos = (*pColArr)[ pColArr->size()-2 ]; + pTableFormat->SetFormatAttr( SvxLRSpaceItem( nSttPos, nFrameWidth - nLastPos, 0, RES_LR_SPACE ) ); + } + nWidth = nLastPos - nSttPos; + } + else + { + nWidth /= nCols; + nWidth *= nCols; // to avoid rounding problems + } + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + // Move the hard PageDesc/PageBreak Attributes if needed + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ] + ->GetContentNode(); + if( pNextNd && pNextNd->HasSwAttrSet() ) + { + const SfxItemSet* pNdSet = pNextNd->GetpSwAttrSet(); + if( const SwFormatPageDesc* pItem = pNdSet->GetItemIfSet( RES_PAGEDESC, false ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_PAGEDESC ); + pNdSet = pNextNd->GetpSwAttrSet(); + } + const SvxFormatBreakItem* pItem; + if( pNdSet && (pItem = pNdSet->GetItemIfSet( RES_BREAK, false )) ) + { + pTableFormat->SetFormatAttr( *pItem ); + pNextNd->ResetAttr( RES_BREAK ); + } + } + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat( *pTableFormat ); + + rNdTable.SetRowsToRepeat( nRowsToRepeat ); + rNdTable.SetTableModel( bNewModel ); + + std::vector<SwTableBoxFormat*> aBoxFormatArr; + SwTableBoxFormat* pBoxFormat = nullptr; + if( !bDfltBorders && !pTAFormat ) + { + pBoxFormat = MakeTableBoxFormat(); + pBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX / nCols, 0 )); + } + else + { + const sal_uInt16 nBoxArrLen = pTAFormat ? 16 : 4; + aBoxFormatArr.resize( nBoxArrLen, nullptr ); + } + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() ); + + SwNodeIndex aNdIdx( *pTableNd, 1 ); // Set to StartNode of first Box + SwTableLines& rLines = rNdTable.GetTabLines(); + for( sal_uInt16 n = 0; n < nRows; ++n ) + { + SwTableLine* pLine = new SwTableLine( pLineFormat, nCols, nullptr ); + rLines.insert( rLines.begin() + n, pLine ); + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( sal_uInt16 i = 0; i < nCols; ++i ) + { + SwTableBoxFormat *pBoxF; + if( pTAFormat ) + { + sal_uInt8 nId = SwTableAutoFormat::CountPos(i, nCols, n, nRows); + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, aBoxFormatArr, *pTAFormat, + nRows, nCols, nId ); + + // Set the Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + GetNodes()[ aNdIdx.GetIndex()+1 ]->GetContentNode()-> + SetAttr( aCharSet ); + } + } + else if( bDfltBorders ) + { + sal_uInt8 nBoxId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + pBoxF = ::lcl_CreateDfltBoxFormat( *this, aBoxFormatArr, nCols, nBoxId); + } + else + pBoxF = pBoxFormat; + + // For AutoFormat on input: the columns are set when inserting the Table + // The Array contains the columns positions and not their widths! + if( pColArr ) + { + nWidth = (*pColArr)[ i + 1 ] - (*pColArr)[ i ]; + if( pBoxF->GetFrameSize().GetWidth() != nWidth ) + { + if( pBoxF->HasWriterListeners() ) // Create new Format + { + SwTableBoxFormat *pNewFormat = MakeTableBoxFormat(); + *pNewFormat = *pBoxF; + pBoxF = pNewFormat; + } + pBoxF->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth )); + } + } + + SwTableBox *pBox = new SwTableBox( pBoxF, aNdIdx, pLine); + rBoxes.insert( rBoxes.begin() + i, pBox ); + aNdIdx += SwNodeOffset(3); // StartNode, TextNode, EndNode == 3 Nodes + } + } + // Insert Frames + pTableNd->MakeOwnFrames(); + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( *pTableNd->EndOfSectionNode(), *pTableNd, SwNodeOffset(1) ); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + getIDocumentState().SetModified(); + CHECK_TABLE(rNdTable); + return &rNdTable; +} + +SwTableNode* SwNodes::InsertTable( SwNode& rNd, + sal_uInt16 nBoxes, + SwTextFormatColl* pContentTextColl, + sal_uInt16 nLines, + sal_uInt16 nRepeat, + SwTextFormatColl* pHeadlineTextColl, + const SwAttrSet * pAttrSet) +{ + if( !nBoxes ) + return nullptr; + + // If Lines is given, create the Matrix from Lines and Boxes + if( !pHeadlineTextColl || !nLines ) + pHeadlineTextColl = pContentTextColl; + + SwTableNode * pTableNd = new SwTableNode( rNd ); + SwEndNode* pEndNd = new SwEndNode( rNd, *pTableNd ); + + if( !nLines ) // For the for loop + ++nLines; + + SwTextFormatColl* pTextColl = pHeadlineTextColl; + for( sal_uInt16 nL = 0; nL < nLines; ++nL ) + { + for( sal_uInt16 nB = 0; nB < nBoxes; ++nB ) + { + SwStartNode* pSttNd = new SwStartNode( *pEndNd, SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + + SwTextNode * pTmpNd = new SwTextNode( *pEndNd, pTextColl ); + + // #i60422# Propagate some more attributes. + const SfxPoolItem* pItem = nullptr; + if ( nullptr != pAttrSet ) + { + static const sal_uInt16 aPropagateItems[] = { + RES_PARATR_ADJUST, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, 0 }; + + const sal_uInt16* pIdx = aPropagateItems; + while ( *pIdx != 0 ) + { + if ( SfxItemState::SET != pTmpNd->GetSwAttrSet().GetItemState( *pIdx ) && + SfxItemState::SET == pAttrSet->GetItemState( *pIdx, true, &pItem ) ) + static_cast<SwContentNode *>(pTmpNd)->SetAttr(*pItem); + ++pIdx; + } + } + + new SwEndNode( *pEndNd, *pSttNd ); + } + if ( nL + 1 >= nRepeat ) + pTextColl = pContentTextColl; + } + return pTableNd; +} + +/** + * Text to Table + */ +const SwTable* SwDoc::TextToTable( const SwInsertTableOptions& rInsTableOpts, + const SwPaM& rRange, sal_Unicode cCh, + sal_Int16 eAdjust, + const SwTableAutoFormat* pTAFormat ) +{ + // See if the selection contains a Table + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + { + SwNodeOffset nCnt = pStt->GetNodeIndex(); + for( ; nCnt <= pEnd->GetNodeIndex(); ++nCnt ) + if( !GetNodes()[ nCnt ]->IsTextNode() ) + return nullptr; + } + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::TEXTTOTABLE, nullptr); + } + + // tdf#153115 first, remove all redlines; splitting them at cell boundaries + // would be tricky to implement, and it's unclear what the value of + // existing redlines is once it's been converted to a table + getIDocumentRedlineAccess().AcceptRedline(rRange, true); + + // Save first node in the selection if it is a context node + SwContentNode * pSttContentNd = pStt->GetNode().GetContentNode(); + + SwPaM aOriginal( *pStt, *pEnd ); + pStt = aOriginal.GetMark(); + pEnd = aOriginal.GetPoint(); + + SwUndoTextToTable* pUndo = nullptr; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + pUndo = new SwUndoTextToTable( aOriginal, rInsTableOpts, cCh, + o3tl::narrowing<sal_uInt16>(eAdjust), pTAFormat ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo( false ); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // Make sure that the range is on Node Edges + SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() ); + if( pStt->GetContentIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->GetContentIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->GetNode().GetContentNode()->Len() != pEnd->GetContentIndex() + || pEnd->GetNodeIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + const_cast<SwPosition*>(pEnd)->Adjust(SwNodeOffset(-1)); + // A Node and at the End? + if( pStt->GetNodeIndex() >= pEnd->GetNodeIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo( nullptr != pUndo ); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + if( !(rInsTableOpts.mnInsMode & SwInsertTableFlags::SplitLayout) ) + pTableFormat->SetFormatAttr( SwFormatLayoutSplit( false )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem *pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + aRg, cCh, pTableFormat, pLineFormat, pBoxFormat, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ), pUndo ); + + SwTable& rNdTable = pTableNd->GetTable(); + + const sal_uInt16 nRowsToRepeat = + SwInsertTableFlags::Headline == (rInsTableOpts.mnInsMode & SwInsertTableFlags::Headline) ? + rInsTableOpts.mnRowsToRepeat : + 0; + rNdTable.SetRowsToRepeat(nRowsToRepeat); + + bool bUseBoxFormat = false; + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + bUseBoxFormat = true; + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + eAdjust = text::HoriOrientation::NONE; + } + + // Set Orientation in the Table's Format + pTableFormat->SetFormatAttr( SwFormatHoriOrient( 0, eAdjust ) ); + rNdTable.RegisterToFormat(*pTableFormat); + + if( pTAFormat || ( rInsTableOpts.mnInsMode & SwInsertTableFlags::DefaultBorder) ) + { + sal_uInt8 nBoxArrLen = pTAFormat ? 16 : 4; + std::unique_ptr< DfltBoxAttrList_t > aBoxFormatArr1; + std::optional< std::vector<SwTableBoxFormat*> > aBoxFormatArr2; + if( bUseBoxFormat ) + { + aBoxFormatArr1.reset(new DfltBoxAttrList_t( nBoxArrLen, nullptr )); + } + else + { + aBoxFormatArr2 = std::vector<SwTableBoxFormat*>( nBoxArrLen, nullptr ); + } + + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet( GetAttrPool() ); + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + + SwTableBoxFormat *pBoxF = nullptr; + SwTableLines& rLines = rNdTable.GetTabLines(); + const SwTableLines::size_type nRows = rLines.size(); + for( SwTableLines::size_type n = 0; n < nRows; ++n ) + { + SwTableBoxes& rBoxes = rLines[ n ]->GetTabBoxes(); + const SwTableBoxes::size_type nCols = rBoxes.size(); + for( SwTableBoxes::size_type i = 0; i < nCols; ++i ) + { + SwTableBox* pBox = rBoxes[ i ]; + bool bChgSz = false; + + if( pTAFormat ) + { + sal_uInt8 nId = static_cast<sal_uInt8>(!n ? 0 : (( n+1 == nRows ) + ? 12 : (4 * (1 + ((n-1) & 1 ))))); + nId = nId + static_cast<sal_uInt8>(!i ? 0 : + ( i+1 == nCols ? 3 : (1 + ((i-1) & 1)))); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId, pTAFormat ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateAFormatBoxFormat( *this, *aBoxFormatArr2, + *pTAFormat, USHRT_MAX, USHRT_MAX, nId ); + } + + // Set Paragraph/Character Attributes if needed + if( pTAFormat->IsFont() || pTAFormat->IsJustify() ) + { + aCharSet.ClearItem(); + pTAFormat->UpdateToSet( nId, nRows==1, nCols==1, aCharSet, + SwTableAutoFormatUpdateFlags::Char, nullptr ); + if( aCharSet.Count() ) + { + SwNodeOffset nSttNd = pBox->GetSttIdx()+1; + SwNodeOffset nEndNd = pBox->GetSttNd()->EndOfSectionIndex(); + for( ; nSttNd < nEndNd; ++nSttNd ) + { + SwContentNode* pNd = GetNodes()[ nSttNd ]->GetContentNode(); + if( pNd ) + { + if( pHistory ) + { + SwRegHistory aReg( pNd, *pNd, pHistory ); + pNd->SetAttr( aCharSet ); + } + else + pNd->SetAttr( aCharSet ); + } + } + } + } + } + else + { + sal_uInt8 nId = (i < nCols - 1 ? 0 : 1) + (n ? 2 : 0 ); + if( bUseBoxFormat ) + ::lcl_SetDfltBoxAttr( *pBox, *aBoxFormatArr1, nId ); + else + { + bChgSz = nullptr == (*aBoxFormatArr2)[ nId ]; + pBoxF = ::lcl_CreateDfltBoxFormat( *this, *aBoxFormatArr2, + USHRT_MAX, nId ); + } + } + + if( !bUseBoxFormat ) + { + if( bChgSz ) + pBoxF->SetFormatAttr( pBox->GetFrameFormat()->GetFrameSize() ); + pBox->ChgFrameFormat( pBoxF ); + } + } + } + + if( bUseBoxFormat ) + { + for( sal_uInt8 i = 0; i < nBoxArrLen; ++i ) + { + delete (*aBoxFormatArr1)[ i ]; + } + } + } + + // Check the boxes for numbers + if( IsInsTableFormatNum() ) + { + for (size_t nBoxes = rNdTable.GetTabSortBoxes().size(); nBoxes; ) + { + ChkBoxNumFormat(*rNdTable.GetTabSortBoxes()[ --nBoxes ], false); + } + } + + SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + { + SwPaM& rTmp = const_cast<SwPaM&>(rRange); // Point always at the Start + rTmp.DeleteMark(); + rTmp.GetPoint()->Assign( *pTableNd ); + GetNodes().GoNext( rTmp.GetPoint() ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::TEXTTOTABLE, nullptr ); + } + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty(true, nullptr, SwNodeOffset(0)); + return &rNdTable; +} + +static void lcl_RemoveBreaks(SwContentNode & rNode, SwTableFormat *const pTableFormat) +{ + // delete old layout frames, new ones need to be created... + rNode.DelFrames(nullptr); + + if (!rNode.IsTextNode()) + { + return; + } + + SwTextNode & rTextNode = *rNode.GetTextNode(); + // remove PageBreaks/PageDesc/ColBreak + SfxItemSet const* pSet = rTextNode.GetpSwAttrSet(); + if (!pSet) + return; + + if (const SvxFormatBreakItem* pItem = pSet->GetItemIfSet(RES_BREAK, false)) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pItem); + } + rTextNode.ResetAttr(RES_BREAK); + pSet = rTextNode.GetpSwAttrSet(); + } + + const SwFormatPageDesc* pPageDescItem; + if (pSet + && (pPageDescItem = pSet->GetItemIfSet(RES_PAGEDESC, false)) + && pPageDescItem->GetPageDesc()) + { + if (pTableFormat) + { + pTableFormat->SetFormatAttr(*pPageDescItem); + } + rTextNode.ResetAttr(RES_PAGEDESC); + } +} + +/** + * balance lines in table, insert empty boxes so all lines have the size + */ +static void +lcl_BalanceTable(SwTable & rTable, size_t const nMaxBoxes, + SwTableNode & rTableNd, SwTableBoxFormat & rBoxFormat, SwTextFormatColl & rTextColl, + SwUndoTextToTable *const pUndo, std::vector<sal_uInt16> *const pPositions) +{ + for (size_t n = 0; n < rTable.GetTabLines().size(); ++n) + { + SwTableLine *const pCurrLine = rTable.GetTabLines()[ n ]; + size_t const nBoxes = pCurrLine->GetTabBoxes().size(); + if (nMaxBoxes != nBoxes) + { + rTableNd.GetNodes().InsBoxen(&rTableNd, pCurrLine, &rBoxFormat, &rTextColl, + nullptr, nBoxes, nMaxBoxes - nBoxes); + + if (pUndo) + { + for (size_t i = nBoxes; i < nMaxBoxes; ++i) + { + pUndo->AddFillBox( *pCurrLine->GetTabBoxes()[i] ); + } + } + + // if the first line is missing boxes, the width array is useless! + if (!n && pPositions) + { + pPositions->clear(); + } + } + } +} + +static void +lcl_SetTableBoxWidths(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc, + std::vector<sal_uInt16> *const pPositions) +{ + if (pPositions && !pPositions->empty()) + { + SwTableLines& rLns = rTable.GetTabLines(); + sal_uInt16 nLastPos = 0; + for (size_t n = 0; n < pPositions->size(); ++n) + { + SwTableBoxFormat *pNewFormat = rDoc.MakeTableBoxFormat(); + pNewFormat->SetFormatAttr( + SwFormatFrameSize(SwFrameSize::Variable, (*pPositions)[n] - nLastPos)); + for (size_t nTmpLine = 0; nTmpLine < rLns.size(); ++nTmpLine) + { + // Have to do an Add here, because the BoxFormat + // is still needed by the caller + pNewFormat->Add( rLns[ nTmpLine ]->GetTabBoxes()[ n ] ); + } + + nLastPos = (*pPositions)[ n ]; + } + + // propagate size upwards from format, so the table gets the right size + SAL_WARN_IF(rBoxFormat.HasWriterListeners(), "sw.core", + "who is still registered in the format?"); + rBoxFormat.SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nLastPos )); + } + else + { + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); + } +} + +SwTableNode* SwNodes::TextToTable( const SwNodeRange& rRange, sal_Unicode cCh, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat, + SwTextFormatColl* pTextColl, + SwUndoTextToTable* pUndo ) +{ + if( rRange.aStart >= rRange.aEnd ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rRange.aStart.GetNode() ); + new SwEndNode( rRange.aEnd.GetNode(), *pTableNd ); + + SwDoc& rDoc = GetDoc(); + std::vector<sal_uInt16> aPosArr; + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nBoxes, nLines, nMaxBoxes = 0; + + SwNodeIndex aSttIdx( *pTableNd, 1 ); + SwNodeIndex aEndIdx( rRange.aEnd, -1 ); + for( nLines = 0, nBoxes = 0; + aSttIdx.GetIndex() < aEndIdx.GetIndex(); + aSttIdx += SwNodeOffset(2), nLines++, nBoxes = 0 ) + { + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "Only add TextNodes to the Table" ); + + if( !nLines && 0x0b == cCh ) + { + cCh = 0x09; + + // Get the separator's position from the first Node, in order for the Boxes to be set accordingly + SwTextFrameInfo aFInfo( static_cast<SwTextFrame*>(pTextNd->getLayoutFrame( pTextNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() )) ); + if( aFInfo.IsOneLine() ) // only makes sense in this case + { + OUString const& rText(pTextNd->GetText()); + for (sal_Int32 nChPos = 0; nChPos < rText.getLength(); ++nChPos) + { + if (rText[nChPos] == cCh) + { + // sw_redlinehide: no idea if this makes any sense... + TextFrameIndex const nPos(aFInfo.GetFrame()->MapModelToView(pTextNd, nChPos)); + aPosArr.push_back( o3tl::narrowing<sal_uInt16>( + aFInfo.GetCharPos(nPos+TextFrameIndex(1), false)) ); + } + } + + aPosArr.push_back( + o3tl::narrowing<sal_uInt16>(aFInfo.GetFrame()->IsVertical() ? + aFInfo.GetFrame()->getFramePrintArea().Bottom() : + aFInfo.GetFrame()->getFramePrintArea().Right()) ); + + } + } + + lcl_RemoveBreaks(*pTextNd, (0 == nLines) ? pTableFormat : nullptr); + + // Set the TableNode as StartNode for all TextNodes in the Table + pTextNd->m_pStartOfSection = pTableNd; + + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + SwStartNode* pSttNd; + SwPosition aCntPos( aSttIdx, pTextNd, 0); + + const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(rDoc, aSttIdx.GetIndex(), SAL_MAX_INT32); + + if( T2T_PARA != cCh ) + { + for (sal_Int32 nChPos = 0; nChPos < pTextNd->GetText().getLength();) + { + if (pTextNd->GetText()[nChPos] == cCh) + { + aCntPos.SetContent(nChPos); + std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc( + [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode, bool) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(*pNewNode, nChPos, nChPos + 1, eMode); + } + }); + SwContentNode *const pNewNd = + pTextNd->SplitContentNode(aCntPos, &restoreFunc); + + // Delete separator and correct search string + pTextNd->EraseText( aCntPos, 1 ); + nChPos = 0; + + // Set the TableNode as StartNode for all TextNodes in the Table + const SwNodeIndex aTmpIdx( aCntPos.GetNode(), -1 ); + pSttNd = new SwStartNode( aTmpIdx.GetNode(), SwNodeType::Start, + SwTableBoxStartNode ); + new SwEndNode( aCntPos.GetNode(), *pSttNd ); + pNewNd->m_pStartOfSection = pSttNd; + + // Assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + else + { + ++nChPos; + } + } + } + + // Now for the last substring + if( !pContentStore->Empty()) + pContentStore->Restore( *pTextNd, pTextNd->GetText().getLength(), pTextNd->GetText().getLength()+1 ); + + pSttNd = new SwStartNode( aCntPos.GetNode(), SwNodeType::Start, SwTableBoxStartNode ); + const SwNodeIndex aTmpIdx( aCntPos.GetNode(), 1 ); + new SwEndNode( aTmpIdx.GetNode(), *pSttNd ); + pTextNd->m_pStartOfSection = pSttNd; + + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + } + + lcl_BalanceTable(rTable, nMaxBoxes, *pTableNd, *pBoxFormat, *pTextColl, + pUndo, &aPosArr); + lcl_SetTableBoxWidths(rTable, nMaxBoxes, *pBoxFormat, rDoc, &aPosArr); + + return pTableNd; +} + +const SwTable* SwDoc::TextToTable( const std::vector< std::vector<SwNodeRange> >& rTableNodes ) +{ + if (rTableNodes.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rFirstRange = *rTableNodes.begin(); + + if (rFirstRange.empty()) + return nullptr; + + const std::vector<SwNodeRange>& rLastRange = *rTableNodes.rbegin(); + + if (rLastRange.empty()) + return nullptr; + + /* Save first node in the selection if it is a content node. */ + SwContentNode * pSttContentNd = rFirstRange.begin()->aStart.GetNode().GetContentNode(); + + const SwNodeRange& rStartRange = *rFirstRange.begin(); + const SwNodeRange& rEndRange = *rLastRange.rbegin(); + + //!!! not necessarily TextNodes !!! + SwPaM aOriginal( rStartRange.aStart, rEndRange.aEnd ); + const SwPosition *pStt = aOriginal.GetMark(); + SwPosition *pEnd = aOriginal.GetPoint(); + + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + // Do not add splitting the TextNode to the Undo history + GetIDocumentUndoRedo().DoUndo(false); + } + + ::PaMCorrAbs( aOriginal, *pEnd ); + + // make sure that the range is on Node Edges + SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() ); + if( pStt->GetContentIndex() ) + getIDocumentContentOperations().SplitNode( *pStt, false ); + + bool bEndContent = 0 != pEnd->GetContentIndex(); + + // Do not split at the End of a Line (except at the End of the Doc) + if( bEndContent ) + { + if( pEnd->GetNode().GetContentNode()->Len() != pEnd->GetContentIndex() + || pEnd->GetNodeIndex() >= GetNodes().GetEndOfContent().GetIndex()-1 ) + { + getIDocumentContentOperations().SplitNode( *pEnd, false ); + pEnd->Adjust(SwNodeOffset(-1)); + // A Node and at the End? + if( pStt->GetNodeIndex() >= pEnd->GetNodeIndex() ) + --aRg.aStart; + } + else + ++aRg.aEnd; + } + + assert(aRg.aEnd.GetNode() == pEnd->GetNode()); + assert(aRg.aStart.GetNode() == pStt->GetNode()); + if( aRg.aEnd.GetIndex() == aRg.aStart.GetIndex() ) + { + OSL_FAIL( "empty range" ); + ++aRg.aEnd; + } + + + { + // TODO: this is not Undo-able - only good enough for file import + IDocumentRedlineAccess & rIDRA(getIDocumentRedlineAccess()); + SwNodeIndex const prev(rTableNodes.begin()->begin()->aStart, -1); + SwNodeIndex const* pPrev(&prev); + // pPrev could point to non-textnode now + for (const auto& rRow : rTableNodes) + { + for (const auto& rCell : rRow) + { + assert(SwNodeIndex(*pPrev, +1) == rCell.aStart); + SwPaM pam(rCell.aStart, 0, *pPrev, + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + pPrev = &rCell.aEnd; + } + } + // another one to break between last cell and node after table + SwPaM pam(pPrev->GetNode(), SwNodeOffset(+1), 0, + pPrev->GetNode(), SwNodeOffset(0), + (pPrev->GetNode().IsContentNode()) + ? pPrev->GetNode().GetContentNode()->Len() : 0); + rIDRA.SplitRedline(pam); + } + + // We always use Upper to insert the Table + SwNode2LayoutSaveUpperFrames aNode2Layout( aRg.aStart.GetNode() ); + + GetIDocumentUndoRedo().DoUndo(bUndo); + + // Create the Box/Line/Table construct + SwTableBoxFormat* pBoxFormat = MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = MakeTableLineFormat(); + SwTableFormat* pTableFormat = MakeTableFrameFormat( GetUniqueTableName(), GetDfltFrameFormat() ); + + // All Lines have a left-to-right Fill Order + pLineFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT )); + // The Table's SSize is USHRT_MAX + pTableFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, USHRT_MAX )); + + /* If the first node in the selection is a context node and if it + has an item FRAMEDIR set (no default) propagate the item to the + replacing table. */ + if (pSttContentNd) + { + const SwAttrSet & aNdSet = pSttContentNd->GetSwAttrSet(); + if (const SvxFrameDirectionItem* pItem = aNdSet.GetItemIfSet( RES_FRAMEDIR )) + { + pTableFormat->SetFormatAttr( *pItem ); + } + } + + //Resolves: tdf#87977, tdf#78599, disable broadcasting modifications + //until after RegisterToFormat is completed + bool bEnableSetModified = getIDocumentState().IsEnableSetModified(); + getIDocumentState().SetEnableSetModified(false); + + SwTableNode* pTableNd = GetNodes().TextToTable( + rTableNodes, pTableFormat, pLineFormat, pBoxFormat ); + + SwTable& rNdTable = pTableNd->GetTable(); + rNdTable.RegisterToFormat(*pTableFormat); + + if( !pBoxFormat->HasWriterListeners() ) + { + // The Box's Formats already have the right size, we must only set + // the right Border/AutoFormat. + pTableFormat->SetFormatAttr( pBoxFormat->GetFrameSize() ); + delete pBoxFormat; + } + + SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + getIDocumentState().SetEnableSetModified(bEnableSetModified); + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + return &rNdTable; +} + +void SwNodes::ExpandRangeForTableBox(const SwNodeRange & rRange, std::optional<SwNodeRange>& rExpandedRange) +{ + bool bChanged = false; + + SwNodeIndex aNewStart = rRange.aStart; + SwNodeIndex aNewEnd = rRange.aEnd; + + SwNodeIndex aEndIndex = rRange.aEnd; + SwNodeIndex aIndex = rRange.aStart; + + while (aIndex < aEndIndex) + { + SwNode& rNode = aIndex.GetNode(); + + if (rNode.IsStartNode()) + { + // advance aIndex to the end node of this start node + SwNode * pEndNode = rNode.EndOfSectionNode(); + aIndex = *pEndNode; + + if (aIndex > aNewEnd) + { + aNewEnd = aIndex; + bChanged = true; + } + } + else if (rNode.IsEndNode()) + { + SwNode * pStartNode = rNode.StartOfSectionNode(); + if (pStartNode->GetIndex() < aNewStart.GetIndex()) + { + aNewStart = *pStartNode; + bChanged = true; + } + } + + if (aIndex < aEndIndex) + ++aIndex; + } + + SwNode * pNode = &aIndex.GetNode(); + while (pNode->IsEndNode() && aIndex < Count() - 1) + { + SwNode * pStartNode = pNode->StartOfSectionNode(); + aNewStart = *pStartNode; + aNewEnd = aIndex; + bChanged = true; + + ++aIndex; + pNode = &aIndex.GetNode(); + } + + if (bChanged) + rExpandedRange.emplace(aNewStart, aNewEnd); +} + +static void +lcl_SetTableBoxWidths2(SwTable & rTable, size_t const nMaxBoxes, + SwTableBoxFormat & rBoxFormat, SwDoc & rDoc) +{ + // rhbz#820283, fdo#55462: set default box widths so table width is covered + SwTableLines & rLines = rTable.GetTabLines(); + for (size_t nTmpLine = 0; nTmpLine < rLines.size(); ++nTmpLine) + { + SwTableBoxes & rBoxes = rLines[nTmpLine]->GetTabBoxes(); + assert(!rBoxes.empty()); // ensured by convertToTable + size_t const nMissing = nMaxBoxes - rBoxes.size(); + if (nMissing) + { + // default width for box at the end of an incomplete line + SwTableBoxFormat *const pNewFormat = rDoc.MakeTableBoxFormat(); + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + pNewFormat->SetFormatAttr( SwFormatFrameSize(SwFrameSize::Variable, + nWidth * (nMissing + 1)) ); + pNewFormat->Add(rBoxes.back()); + } + } + size_t nWidth = nMaxBoxes ? USHRT_MAX / nMaxBoxes : USHRT_MAX; + // default width for all boxes not at the end of an incomplete line + rBoxFormat.SetFormatAttr(SwFormatFrameSize(SwFrameSize::Variable, nWidth)); +} + +SwTableNode* SwNodes::TextToTable( const SwNodes::TableRanges_t & rTableNodes, + SwTableFormat* pTableFormat, + SwTableLineFormat* pLineFormat, + SwTableBoxFormat* pBoxFormat ) +{ + if( rTableNodes.empty() ) + return nullptr; + + SwTableNode * pTableNd = new SwTableNode( rTableNodes.begin()->begin()->aStart.GetNode() ); + //insert the end node after the last text node + SwNodeIndex aInsertIndex( rTableNodes.rbegin()->rbegin()->aEnd ); + ++aInsertIndex; + + //!! ownership will be transferred in c-tor to SwNodes array. + //!! Thus no real problem here... + new SwEndNode( aInsertIndex.GetNode(), *pTableNd ); + + SwDoc& rDoc = GetDoc(); + SwTable& rTable = pTableNd->GetTable(); + SwTableBox* pBox; + sal_uInt16 nLines, nMaxBoxes = 0; + + SwNodeIndex aNodeIndex = rTableNodes.begin()->begin()->aStart; + // delete frames of all contained content nodes + for( nLines = 0; aNodeIndex <= rTableNodes.rbegin()->rbegin()->aEnd; ++aNodeIndex,++nLines ) + { + SwNode& rNode = aNodeIndex.GetNode(); + if( rNode.IsContentNode() ) + { + lcl_RemoveBreaks(static_cast<SwContentNode&>(rNode), + (0 == nLines) ? pTableFormat : nullptr); + } + } + + nLines = 0; + for( const auto& rRow : rTableNodes ) + { + sal_uInt16 nBoxes = 0; + SwTableLine* pLine = new SwTableLine( pLineFormat, 1, nullptr ); + rTable.GetTabLines().insert(rTable.GetTabLines().begin() + nLines, pLine); + + for( const auto& rCell : rRow ) + { + SwNodeIndex aCellEndIdx(rCell.aEnd); + ++aCellEndIdx; + SwStartNode* pSttNd = new SwStartNode( rCell.aStart.GetNode(), SwNodeType::Start, + SwTableBoxStartNode ); + + // Quotation of http://nabble.documentfoundation.org/Some-strange-lines-by-taking-a-look-at-the-bt-of-fdo-51916-tp3994561p3994639.html + // SwNode's constructor adds itself to the same SwNodes array as the other node (pSttNd). + // So this statement is only executed for the side-effect. + new SwEndNode( aCellEndIdx.GetNode(), *pSttNd ); + + //set the start node on all node of the current cell + SwNodeIndex aCellNodeIdx = rCell.aStart; + for(;aCellNodeIdx <= rCell.aEnd; ++aCellNodeIdx ) + { + aCellNodeIdx.GetNode().m_pStartOfSection = pSttNd; + //skip start/end node pairs + if( aCellNodeIdx.GetNode().IsStartNode() ) + aCellNodeIdx.Assign(*aCellNodeIdx.GetNode().EndOfSectionNode()); + } + + // assign Section to the Box + pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin() + nBoxes++, pBox ); + } + if( nMaxBoxes < nBoxes ) + nMaxBoxes = nBoxes; + + nLines++; + } + + lcl_SetTableBoxWidths2(rTable, nMaxBoxes, *pBoxFormat, rDoc); + + return pTableNd; +} + +/** + * Table to Text + */ +bool SwDoc::TableToText( const SwTableNode* pTableNd, sal_Unicode cCh ) +{ + if( !pTableNd ) + return false; + + // #i34471# + // If this is triggered by SwUndoTableToText::Repeat() nobody ever deleted + // the table cursor. + SwEditShell* pESh = GetEditShell(); + if (pESh && pESh->IsTableMode()) + pESh->ClearMark(); + + SwNodeRange aRg( *pTableNd, SwNodeOffset(0), *pTableNd->EndOfSectionNode() ); + std::unique_ptr<SwUndoTableToText> pUndo; + SwNodeRange* pUndoRg = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndoRg = new SwNodeRange( aRg.aStart, SwNodeOffset(-1), aRg.aEnd, SwNodeOffset(+1) ); + pUndo.reset(new SwUndoTableToText( pTableNd->GetTable(), cCh )); + } + + const_cast<SwTable*>(&pTableNd->GetTable())->SwitchFormulasToExternalRepresentation(); + + bool bRet = GetNodes().TableToText( aRg, cCh, pUndo.get() ); + if( pUndoRg ) + { + ++pUndoRg->aStart; + --pUndoRg->aEnd; + pUndo->SetRange( *pUndoRg ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + delete pUndoRg; + } + + if( bRet ) + getIDocumentState().SetModified(); + + return bRet; +} + +namespace { + +/** + * Use the ForEach method from PtrArray to recreate Text from a Table. + * The Boxes can also contain Lines! + */ +struct DelTabPara +{ + SwTextNode* pLastNd; + SwNodes& rNds; + SwUndoTableToText* pUndo; + sal_Unicode cCh; + + DelTabPara( SwNodes& rNodes, sal_Unicode cChar, SwUndoTableToText* pU ) : + pLastNd(nullptr), rNds( rNodes ), pUndo( pU ), cCh( cChar ) {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ); + +static void lcl_DelLine( SwTableLine* pLine, DelTabPara* pPara ) +{ + assert(pPara && "The parameters are missing!"); + DelTabPara aPara( *pPara ); + for( auto& rpBox : pLine->GetTabBoxes() ) + lcl_DelBox(rpBox, &aPara ); + if( pLine->GetUpper() ) // Is there a parent Box? + // Return the last TextNode + pPara->pLastNd = aPara.pLastNd; +} + +static void lcl_DelBox( SwTableBox* pBox, DelTabPara* pDelPara ) +{ + assert(pDelPara && "The parameters are missing"); + + // Delete the Box's Lines + if( !pBox->GetTabLines().empty() ) + { + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_DelLine( pLine, pDelPara ); + } + else + { + SwDoc& rDoc = pDelPara->rNds.GetDoc(); + SwNodeRange aDelRg( *pBox->GetSttNd(), SwNodeOffset(0), + *pBox->GetSttNd()->EndOfSectionNode() ); + // Delete the Section + pDelPara->rNds.SectionUp( &aDelRg ); + const SwTextNode* pCurTextNd = nullptr; + if (T2T_PARA != pDelPara->cCh && pDelPara->pLastNd) + pCurTextNd = aDelRg.aStart.GetNode().GetTextNode(); + if (nullptr != pCurTextNd) + { + // Join the current text node with the last from the previous box if possible + SwNodeOffset nNdIdx = aDelRg.aStart.GetIndex(); + --aDelRg.aStart; + if( pDelPara->pLastNd == &aDelRg.aStart.GetNode() ) + { + // Inserting the separator + SwContentIndex aCntIdx( pDelPara->pLastNd, + pDelPara->pLastNd->GetText().getLength()); + pDelPara->pLastNd->InsertText( OUString(pDelPara->cCh), aCntIdx, + SwInsertFlags::EMPTYEXPAND ); + if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex(), + aCntIdx.GetIndex() ); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + const sal_Int32 nOldTextLen = aCntIdx.GetIndex(); + pContentStore->Save(rDoc, nNdIdx, SAL_MAX_INT32); + + pDelPara->pLastNd->JoinNext(); + + if( !pContentStore->Empty() ) + pContentStore->Restore( rDoc, pDelPara->pLastNd->GetIndex(), nOldTextLen ); + } + else if( pDelPara->pUndo ) + { + ++aDelRg.aStart; + pDelPara->pUndo->AddBoxPos( rDoc, nNdIdx, aDelRg.aEnd.GetIndex() ); + } + } + else if( pDelPara->pUndo ) + pDelPara->pUndo->AddBoxPos( rDoc, aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + --aDelRg.aEnd; + pDelPara->pLastNd = aDelRg.aEnd.GetNode().GetTextNode(); + + // Do not take over the NumberFormatting's adjustment + if( pDelPara->pLastNd && pDelPara->pLastNd->HasSwAttrSet() ) + pDelPara->pLastNd->ResetAttr( RES_PARATR_ADJUST ); + } +} + +bool SwNodes::TableToText( const SwNodeRange& rRange, sal_Unicode cCh, + SwUndoTableToText* pUndo ) +{ + // Is a Table selected? + if (rRange.aStart.GetIndex() >= rRange.aEnd.GetIndex()) + return false; + SwTableNode *const pTableNd(rRange.aStart.GetNode().GetTableNode()); + if (nullptr == pTableNd || + &rRange.aEnd.GetNode() != pTableNd->EndOfSectionNode() ) + return false; + + // If the Table was alone in a Section, create the Frames via the Table's Upper + std::optional<SwNode2LayoutSaveUpperFrames> oNode2Layout; + SwNode* pFrameNd = FindPrvNxtFrameNode( rRange.aStart.GetNode(), &rRange.aEnd.GetNode() ); + SwNodeIndex aFrameIdx( pFrameNd ? *pFrameNd: rRange.aStart.GetNode() ); + if( !pFrameNd ) + // Collect all Uppers + oNode2Layout.emplace(*pTableNd); + + // Delete the Frames + pTableNd->DelFrames(); + + // "Delete" the Table and merge all Lines/Boxes + DelTabPara aDelPara( *this, cCh, pUndo ); + for( SwTableLine *pLine : pTableNd->m_pTable->GetTabLines() ) + lcl_DelLine( pLine, &aDelPara ); + + // We just created a TextNode with fitting separator for every TableLine. + // Now we only need to delete the TableSection and create the Frames for the + // new TextNode. + SwNodeRange aDelRg( rRange.aStart, rRange.aEnd ); + + // If the Table has PageDesc/Break Attributes, carry them over to the + // first Text Node + { + // What about UNDO? + const SfxItemSet& rTableSet = pTableNd->m_pTable->GetFrameFormat()->GetAttrSet(); + const SvxFormatBreakItem* pBreak = rTableSet.GetItemIfSet( RES_BREAK, false ); + const SwFormatPageDesc* pDesc = rTableSet.GetItemIfSet( RES_PAGEDESC, false ); + + if( pBreak || pDesc ) + { + SwNodeIndex aIdx( *pTableNd ); + SwContentNode* pCNd = GoNext( &aIdx ); + if( pBreak ) + pCNd->SetAttr( *pBreak ); + if( pDesc ) + pCNd->SetAttr( *pDesc ); + } + } + + SectionUp( &aDelRg ); // Delete this Section and by that the Table + // #i28006# + SwNodeOffset nStt = aDelRg.aStart.GetIndex(), nEnd = aDelRg.aEnd.GetIndex(); + if( !pFrameNd ) + { + oNode2Layout->RestoreUpperFrames( *this, + aDelRg.aStart.GetIndex(), aDelRg.aEnd.GetIndex() ); + oNode2Layout.reset(); + } + else + { + SwContentNode *pCNd; + SwSectionNode *pSNd; + while( aDelRg.aStart.GetIndex() < nEnd ) + { + pCNd = aDelRg.aStart.GetNode().GetContentNode(); + if( nullptr != pCNd ) + { + if( pFrameNd->IsContentNode() ) + static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); + else if( pFrameNd->IsTableNode() ) + static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + else if( pFrameNd->IsSectionNode() ) + static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aDelRg.aStart); + pFrameNd = pCNd; + } + else + { + pSNd = aDelRg.aStart.GetNode().GetSectionNode(); + if( pSNd ) + { + if( !pSNd->GetSection().IsHidden() && !pSNd->IsContentHidden() ) + { + pSNd->MakeOwnFrames(&aFrameIdx, &aDelRg.aEnd); + break; + } + aDelRg.aStart = *pSNd->EndOfSectionNode(); + } + } + ++aDelRg.aStart; + } + } + + // #i28006# Fly frames have to be restored even if the table was + // #alone in the section + for(sw::SpzFrameFormat* pFly: *GetDoc().GetSpzFrameFormats()) + { + SwFrameFormat *const pFormat = pFly; + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + SwNode const*const pAnchorNode = rAnchor.GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) && + nStt <= pAnchorNode->GetIndex() && + pAnchorNode->GetIndex() < nEnd ) + { + pFormat->MakeFrames(); + } + } + + return true; +} + +/** + * Inserting Columns/Rows + */ +void SwDoc::InsertCol( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + if( !::CheckSplitCells( rCursor, nCnt + 1, SwTableSearchType::Col ) ) + return; + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + ::GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + + if( !aBoxes.empty() ) + InsertCol( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertCol( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind, bool bInsertDummy ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSCOL, rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + rTable.SwitchFormulasToInternalRepresentation(); + bRet = rTable.InsertCol(*this, rBoxes, nCnt, bBehind, bInsertDummy); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; +} + +void SwDoc::InsertRow( const SwCursor& rCursor, sal_uInt16 nCnt, bool bBehind ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + + if( !aBoxes.empty() ) + InsertRow( aBoxes, nCnt, bBehind ); +} + +bool SwDoc::InsertRow( const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind, bool bInsertDummy ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW,rBoxes, *pTableNd, + 0, 0, nCnt, bBehind, false )); + aTmpLst.insert( rTable.GetTabSortBoxes() ); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + rTable.SwitchFormulasToInternalRepresentation(); + + bRet = rTable.InsertRow( this, rBoxes, nCnt, bBehind, bInsertDummy ); + if (bRet) + { + getIDocumentState().SetModified(); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bRet; + +} + +/** + * Deleting Columns/Rows + */ +void SwDoc::DeleteRow( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Row ); + if( ::HasProtectedCells( aBoxes )) + return; + + // Remove the Cursor from the to-be-deleted Section. + // The Cursor is placed after the table, except for + // - when there's another Line, we place it in that one + // - when a Line precedes it, we place it in that one + { + SwTableNode* pTableNd = rCursor.GetPointNode().FindTableNode(); + + if(dynamic_cast<const SwDDETable*>( & pTableNd->GetTable()) != nullptr) + return; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if( aFndBox.GetLines().empty() ) + return; + + if (SwEditShell* pESh = GetEditShell()) + { + pESh->KillPams(); + // FIXME: actually we should be iterating over all Shells! + } + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size() ) + { + FndBox_ *const pTmp = pFndBox->GetLines().front()->GetBoxes()[0].get(); + if( pTmp->GetBox()->GetSttNd() ) + break; // Else it gets too far + pFndBox = pTmp; + } + + SwTableLine* pDelLine = pFndBox->GetLines().back()->GetLine(); + SwTableBox* pDelBox = pDelLine->GetTabBoxes().back(); + while( !pDelBox->GetSttNd() ) + { + SwTableLine* pLn = pDelBox->GetTabLines()[ + pDelBox->GetTabLines().size()-1 ]; + pDelBox = pLn->GetTabBoxes().back(); + } + SwTableBox* pNextBox = pDelLine->FindNextBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindNextBox( pTableNd->GetTable(), pNextBox ); + + if( !pNextBox ) // No succeeding Boxes? Then take the preceding one + { + pDelLine = pFndBox->GetLines().front()->GetLine(); + pDelBox = pDelLine->GetTabBoxes()[ 0 ]; + while( !pDelBox->GetSttNd() ) + pDelBox = pDelBox->GetTabLines()[0]->GetTabBoxes()[0]; + pNextBox = pDelLine->FindPreviousBox( pTableNd->GetTable(), + pDelBox ); + while( pNextBox && + pNextBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + pNextBox = pNextBox->FindPreviousBox( pTableNd->GetTable(), pNextBox ); + } + + SwNodeOffset nIdx; + if( pNextBox ) // Place the Cursor here + nIdx = pNextBox->GetSttIdx() + 1; + else // Else after the Table + nIdx = pTableNd->EndOfSectionIndex() + 1; + + SwNodeIndex aIdx( GetNodes(), nIdx ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + { + // Change the Shell's Cursor or the one passed? + SwPaM* pPam = const_cast<SwPaM*>(static_cast<SwPaM const *>(&rCursor)); + pPam->GetPoint()->Assign(aIdx); + pPam->SetMark(); // Both want a part of it + pPam->DeleteMark(); + } + } + + // Thus delete the Rows + GetIDocumentUndoRedo().StartUndo(SwUndoId::ROW_DELETE, nullptr); + DeleteRowCol( aBoxes ); + GetIDocumentUndoRedo().EndUndo(SwUndoId::ROW_DELETE, nullptr); +} + +void SwDoc::DeleteCol( const SwCursor& rCursor ) +{ + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + GetTableSel( rCursor, aBoxes, SwTableSearchType::Col ); + if( ::HasProtectedCells( aBoxes )) + return; + + // The Cursors need to be removed from the to-be-deleted range. + // Always place them after/on top of the Table; they are always set + // to the old position via the document position. + if (SwEditShell* pESh = GetEditShell()) + { + const SwNode* pNd = rCursor.GetPointNode().FindTableBoxStartNode(); + pESh->ParkCursor( *pNd ); + } + + // Thus delete the Columns + GetIDocumentUndoRedo().StartUndo(SwUndoId::COL_DELETE, nullptr); + DeleteRowCol(aBoxes, SwDoc::RowColMode::DeleteColumn); + GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr); +} + +void SwDoc::DelTable(SwTableNode *const pTableNd) +{ + { + // tdf#156267 remove DdeBookmarks before deleting nodes + SwPaM aTmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + SwDataChanged aTmp(aTmpPaM); + } + + bool bNewTextNd = false; + // Is it alone in a FlyFrame? + SwNodeIndex aIdx( *pTableNd, -1 ); + const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode(); + if (pSttNd) + { + const SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex() + 1; + const SwNodeOffset nSectEnd = pSttNd->EndOfSectionIndex(); + if (nTableEnd == nSectEnd) + { + if (SwFlyStartNode == pSttNd->GetStartNodeType()) + { + SwFrameFormat* pFormat = pSttNd->GetFlyFormat(); + if (pFormat) + { + // That's the FlyFormat we're looking for + getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + return; + } + } + // No Fly? Thus Header or Footer: always leave a TextNode + // We can forget about Undo then! + bNewTextNd = true; + } + } + + // No Fly? Then it is a Header or Footer, so keep always a TextNode + ++aIdx; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() ); + + if (bNewTextNd) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx.GetNode(), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM const* pSavePaM(nullptr); + SwPaM forwardPaM(*pTableNd->EndOfSectionNode()); + if (forwardPaM.Move(fnMoveForward, GoInNode)) + { + pSavePaM = &forwardPaM; + } + SwPaM backwardPaM(*pTableNd); + if (backwardPaM.Move(fnMoveBackward, GoInNode)) + { + if (pSavePaM == nullptr + // try to stay in the same outer table cell + || (forwardPaM.GetPoint()->GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode() + && forwardPaM.GetPoint()->GetNode().StartOfSectionIndex() + < backwardPaM.GetPoint()->GetNode().StartOfSectionIndex())) + { + pSavePaM = &backwardPaM; + } + } + assert(pSavePaM); // due to bNewTextNd this must succeed + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint()); + } + + // Move hard PageBreaks to the succeeding Node + bool bSavePageBreak = false, bSavePageDesc = false; + SwNodeOffset nNextNd = pTableNd->EndOfSectionIndex()+1; + SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode(); + if (pNextNd) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC, + false, &pItem)) + { + pNextNd->SetAttr( *pItem ); + bSavePageDesc = true; + } + + if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK, + false, &pItem)) + { + pNextNd->SetAttr( *pItem ); + bSavePageBreak = true; + } + } + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default)); + if (bNewTextNd) + pUndo->SetTableDelLastNd(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName()); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + if (bNewTextNd) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx.GetNode(), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM const* pSavePaM(nullptr); + SwPaM forwardPaM(*pTableNd->EndOfSectionNode()); + if (forwardPaM.Move(fnMoveForward, GoInNode)) + { + pSavePaM = &forwardPaM; + } + SwPaM backwardPaM(*pTableNd); + if (backwardPaM.Move(fnMoveBackward, GoInNode)) + { + if (pSavePaM == nullptr + // try to stay in the same outer table cell + || (forwardPaM.GetPoint()->GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode() + && forwardPaM.GetPoint()->GetNode().StartOfSectionIndex() + < backwardPaM.GetPoint()->GetNode().StartOfSectionIndex())) + { + pSavePaM = &backwardPaM; + } + } + assert(pSavePaM); // due to bNewTextNd this must succeed + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint()); + } + + // Move hard PageBreaks to the succeeding Node + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if (pNextNd) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if (SfxItemState::SET == pTableFormat->GetItemState(RES_PAGEDESC, + false, &pItem)) + { + pNextNd->SetAttr( *pItem ); + } + + if (SfxItemState::SET == pTableFormat->GetItemState(RES_BREAK, + false, &pItem)) + { + pNextNd->SetAttr( *pItem ); + } + } + + pTableNd->DelFrames(); + getIDocumentContentOperations().DeleteSection( pTableNd ); + } + + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); +} + +bool SwDoc::DeleteRowCol(const SwSelBoxes& rBoxes, RowColMode const eMode) +{ + if (!(eMode & SwDoc::RowColMode::DeleteProtected) + && ::HasProtectedCells(rBoxes)) + { + return false; + } + + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + if (!(eMode & SwDoc::RowColMode::DeleteProtected) + && dynamic_cast<const SwDDETable*>(&pTableNd->GetTable()) != nullptr) + { + return false; + } + + ::ClearFEShellTabCols(*this, nullptr); + SwSelBoxes aSelBoxes( rBoxes ); + SwTable &rTable = pTableNd->GetTable(); + tools::Long nMin = 0; + tools::Long nMax = 0; + if( rTable.IsNewModel() ) + { + if (eMode & SwDoc::RowColMode::DeleteColumn) + rTable.ExpandColumnSelection( aSelBoxes, nMin, nMax ); + else + rTable.FindSuperfluousRows( aSelBoxes ); + } + + // Are we deleting the whole Table? + const SwNodeOffset nTmpIdx1 = pTableNd->GetIndex(); + const SwNodeOffset nTmpIdx2 = aSelBoxes.back()->GetSttNd()->EndOfSectionIndex() + 1; + if( pTableNd->GetTable().GetTabSortBoxes().size() == aSelBoxes.size() && + aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 && + nTmpIdx2 == pTableNd->EndOfSectionIndex() ) + { + DelTable(pTableNd); + return true; + } + + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_DELBOX, aSelBoxes, *pTableNd, + nMin, nMax, 0, false, false )); + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + rTable.SwitchFormulasToInternalRepresentation(); + + if (rTable.IsNewModel()) + { + if (eMode & SwDoc::RowColMode::DeleteColumn) + rTable.PrepareDeleteCol( nMin, nMax ); + rTable.FindSuperfluousRows( aSelBoxes ); + if (pUndo) + pUndo->ReNewBoxes( aSelBoxes ); + } + bRet = rTable.DeleteSel( this, aSelBoxes, nullptr, pUndo.get(), true, true ); + if (bRet) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +/** + * Split up/merge Boxes in the Table + */ +bool SwDoc::SplitTable( const SwSelBoxes& rBoxes, bool bVert, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr) + return false; + + std::vector<SwNodeOffset> aNdsCnts; + SwTableSortBoxes aTmpLst; + std::unique_ptr<SwUndoTableNdsChg> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoTableNdsChg( SwUndoId::TABLE_SPLIT, rBoxes, *pTableNd, 0, 0, + nCnt, bVert, bSameHeight )); + + aTmpLst.insert( rTable.GetTabSortBoxes() ); + if( !bVert ) + { + for (size_t n = 0; n < rBoxes.size(); ++n) + { + const SwStartNode* pSttNd = rBoxes[ n ]->GetSttNd(); + aNdsCnts.push_back( pSttNd->EndOfSectionIndex() - + pSttNd->GetIndex() ); + } + } + } + + bool bRet(false); + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + rTable.SwitchFormulasToInternalRepresentation(); + + if (bVert) + bRet = rTable.SplitCol(*this, rBoxes, nCnt); + else + bRet = rTable.SplitRow(*this, rBoxes, nCnt, bSameHeight); + + if (bRet) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + } + + if( pUndo && bRet ) + { + if( bVert ) + pUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + else + pUndo->SaveNewBoxes( *pTableNd, aTmpLst, rBoxes, aNdsCnts ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + return bRet; +} + +TableMergeErr SwDoc::MergeTable( SwPaM& rPam ) +{ + // Check if the current cursor's Point/Mark are inside a Table + SwTableNode* pTableNd = rPam.GetPointNode().FindTableNode(); + if( !pTableNd ) + return TableMergeErr::NoSelection; + SwTable& rTable = pTableNd->GetTable(); + if( dynamic_cast<const SwDDETable*>( &rTable) != nullptr ) + return TableMergeErr::NoSelection; + TableMergeErr nRet = TableMergeErr::NoSelection; + if( !rTable.IsNewModel() ) + { + nRet =::CheckMergeSel( rPam ); + if( TableMergeErr::Ok != nRet ) + return nRet; + nRet = TableMergeErr::NoSelection; + } + + // #i33394# + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_MERGE, nullptr ); + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + std::unique_ptr<SwUndoTableMerge> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTableMerge( rPam )); + + // Find the Boxes via the Layout + SwSelBoxes aBoxes; + SwSelBoxes aMerged; + SwTableBox* pMergeBox; + + if( !rTable.PrepareMerge( rPam, aBoxes, aMerged, &pMergeBox, pUndo.get() ) ) + { // No cells found to merge + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pUndo ) + { + pUndo.reset(); + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId) + && (SwUndoId::REDLINE == nLastUndoId)) + { + // FIXME: why is this horrible cleanup necessary? + SwUndoRedline *const pU = dynamic_cast<SwUndoRedline*>( + GetUndoManager().RemoveLastUndo()); + if (pU && pU->GetRedlSaveCount()) + { + SwEditShell *const pEditShell(GetEditShell()); + assert(pEditShell); + ::sw::UndoRedoContext context(*this, *pEditShell); + static_cast<SfxUndoAction *>(pU)->UndoWithContext(context); + } + delete pU; + } + } + } + else + { + // The PaMs need to be removed from the to-be-deleted range. Thus always place + // them at the end of/on top of the Table; it's always set to the old position via + // the Document Position. + // For a start remember an index for the temporary position, because we cannot + // access it after GetMergeSel + { + rPam.DeleteMark(); + rPam.GetPoint()->Assign(*pMergeBox->GetSttNd()); + rPam.SetMark(); + rPam.DeleteMark(); + + SwPaM* pTmp = &rPam; + while( &rPam != ( pTmp = pTmp->GetNext() )) + for( int i = 0; i < 2; ++i ) + pTmp->GetBound( static_cast<bool>(i) ) = *rPam.GetPoint(); + + if (SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(&rPam)) + { + // tdf#135098 update selection so rPam's m_SelectedBoxes is updated + // to not contain the soon to-be-deleted SwTableBox so if the rPam + // is queried via a11y it doesn't claim the deleted cell still + // exists + pTableCursor->NewTableSelection(); + } + } + + // Merge them + pTableNd->GetTable().SwitchFormulasToInternalRepresentation(); + + if( pTableNd->GetTable().Merge( this, aBoxes, aMerged, pMergeBox, pUndo.get() )) + { + nRet = TableMergeErr::Ok; + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + + rPam.GetPoint()->Assign( *pMergeBox->GetSttNd() ); + rPam.Move(); + + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_MERGE, nullptr ); + return nRet; +} + +SwTableNode::SwTableNode( const SwNode& rWhere ) + : SwStartNode( rWhere, SwNodeType::Table ) +{ + m_pTable.reset(new SwTable); +} + +SwTableNode::~SwTableNode() +{ + // Notify UNO wrappers + GetTable().GetFrameFormat()->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); + DelFrames(); + m_pTable->SetTableNode(this); // set this so that ~SwDDETable can read it! + m_pTable.reset(); +} + +SwTabFrame *SwTableNode::MakeFrame( SwFrame* pSib ) +{ + return new SwTabFrame( *m_pTable, pSib ); +} + +/** + * Creates all Views from the Document for the preceding Node. The resulting ContentFrames + * are added to the corresponding Layout. + */ +void SwTableNode::MakeFramesForAdjacentContentNode(const SwNodeIndex & rIdx) +{ + if( !GetTable().GetFrameFormat()->HasWriterListeners()) // Do we actually have Frame? + return; + + SwFrame *pFrame; + SwContentNode * pNode = rIdx.GetNode().GetContentNode(); + + OSL_ENSURE( pNode, "No ContentNode or CopyNode and new Node is identical"); + + bool bBefore = rIdx < GetIndex(); + + SwNode2Layout aNode2Layout( *this, rIdx.GetIndex() ); + + while( nullptr != (pFrame = aNode2Layout.NextFrame()) ) + { + if ( ( pFrame->getRootFrame()->HasMergedParas() && + !pNode->IsCreateFrameWhenHidingRedlines() ) || + // tdf#153819 table deletion with change tracking: + // table node without frames in Hide Changes mode + !pFrame->GetUpper() ) + { + continue; + } + SwFrame *pNew = pNode->MakeFrame( pFrame ); + // Will the Node receive Frames before or after? + if ( bBefore ) + // The new one precedes me + pNew->Paste( pFrame->GetUpper(), pFrame ); + else + // The new one succeeds me + pNew->Paste( pFrame->GetUpper(), pFrame->GetNext() ); + } +} + +/** + * Create a TableFrame for every Shell and insert before the corresponding ContentFrame. + */ +void SwTableNode::MakeOwnFrames(SwPosition* pIdxBehind) +{ + SwNode *pNd = GetNodes().FindPrvNxtFrameNode( *this, EndOfSectionNode() ); + if( !pNd ) + { + if (pIdxBehind) + pIdxBehind->Assign(*this); + return; + } + if (pIdxBehind) + pIdxBehind->Assign(*pNd); + + SwFrame *pFrame( nullptr ); + SwLayoutFrame *pUpper( nullptr ); + SwNode2Layout aNode2Layout( *pNd, GetIndex() ); + while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, *this )) ) + { + if (pUpper->getRootFrame()->HasMergedParas() + && !IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwTabFrame* pNew = MakeFrame( pUpper ); + pNew->Paste( pUpper, pFrame ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + auto pPrev = pNew->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + pNew->RegistFlys(); + } +} + +void SwTableNode::DelFrames(SwRootFrame const*const pLayout) +{ + /* For a start, cut out and delete the TabFrames (which will also delete the Columns and Rows) + The TabFrames are attached to the FrameFormat of the SwTable. + We need to delete them in a more cumbersome way, for the Master to also delete the Follows. */ + + SwIterator<SwTabFrame,SwFormat> aIter( *(m_pTable->GetFrameFormat()) ); + SwTabFrame *pFrame = aIter.First(); + while ( pFrame ) + { + bool bAgain = false; + { + if (!pFrame->IsFollow() && (!pLayout || pLayout == pFrame->getRootFrame())) + { + while ( pFrame->HasFollow() ) + pFrame->JoinAndDelFollows(); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (!GetDoc().IsInDtor()) + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + if (pFrame->GetUpper()) + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + bAgain = true; + } + } + pFrame = bAgain ? aIter.First() : aIter.Next(); + } +} + +void SwTableNode::SetNewTable( std::unique_ptr<SwTable> pNewTable, bool bNewFrames ) +{ + DelFrames(); + m_pTable->SetTableNode(this); + m_pTable = std::move(pNewTable); + if( bNewFrames ) + { + MakeOwnFrames(); + } +} + +void SwTableNode::RemoveRedlines() +{ + SwDoc& rDoc = GetDoc(); + SwTable& rTable = GetTable(); + rDoc.getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAllTableRedlines(rDoc, rTable, true, RedlineType::Any); +} + +void SwTableNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTableNode")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr())); + + if (m_pTable) + { + m_pTable->dumpAsXml(pWriter); + } + + // (void)xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested +} + +void SwDoc::GetTabCols( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "pBoxFrame needs to be specified!" ); + if( !pBoxFrame ) + return; + + SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + const SwTableBox* pBox = pBoxFrame->GetTabBox(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + rFill.SetLeftMin ( nLeftMin ); + rFill.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + rFill.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + rFill.SetRightMax( nRightMax - nLeftMin ); + + pTab->GetTable()->GetTabCols( rFill, pBox ); +} + +// Here are some little helpers used in SwDoc::GetTabRows + +#define ROWFUZZY 25 + +namespace { + +struct FuzzyCompare +{ + bool operator() ( tools::Long s1, tools::Long s2 ) const; +}; + +} + +bool FuzzyCompare::operator() ( tools::Long s1, tools::Long s2 ) const +{ + return ( s1 < s2 && std::abs( s1 - s2 ) > ROWFUZZY ); +} + +static bool lcl_IsFrameInColumn( const SwCellFrame& rFrame, SwSelBoxes const & rBoxes ) +{ + for (size_t i = 0; i < rBoxes.size(); ++i) + { + if ( rFrame.GetTabBox() == rBoxes[ i ] ) + return true; + } + + return false; +} + +void SwDoc::GetTabRows( SwTabCols &rFill, const SwCellFrame* pBoxFrame ) +{ + OSL_ENSURE( pBoxFrame, "GetTabRows called without pBoxFrame" ); + + // Make code robust: + if ( !pBoxFrame ) + return; + + // #i39552# Collection of the boxes of the current + // column has to be done at the beginning of this function, because + // the table may be formatted in ::GetTableSel. + SwDeletionChecker aDelCheck( pBoxFrame ); + + SwSelBoxes aBoxes; + const SwContentFrame* pContent = ::GetCellContent( *pBoxFrame ); + if ( pContent && pContent->IsTextFrame() ) + { + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + ::GetTableSel( aTmpCursor, aBoxes, SwTableSearchType::Col ); + } + + // Make code robust: + if ( aDelCheck.HasBeenDeleted() ) + { + OSL_FAIL( "Current box has been deleted during GetTabRows()" ); + return; + } + + // Make code robust: + const SwTabFrame* pTab = pBoxFrame->FindTabFrame(); + OSL_ENSURE( pTab, "GetTabRows called without a table" ); + if ( !pTab ) + return; + + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + + // Set fixed points, LeftMin in Document coordinates, all others relative + SwRectFnSet aRectFnSet(pTab); + const SwPageFrame* pPage = pTab->FindPageFrame(); + const tools::Long nLeftMin = ( aRectFnSet.IsVert() ? + pTab->GetPrtLeft() - pPage->getFrameArea().Left() : + pTab->GetPrtTop() - pPage->getFrameArea().Top() ); + const tools::Long nLeft = aRectFnSet.IsVert() ? LONG_MAX : 0; + const tools::Long nRight = aRectFnSet.GetHeight(pTab->getFramePrintArea()); + const tools::Long nRightMax = aRectFnSet.IsVert() ? nRight : LONG_MAX; + + rFill.SetLeftMin( nLeftMin ); + rFill.SetLeft( nLeft ); + rFill.SetRight( nRight ); + rFill.SetRightMax( nRightMax ); + + typedef std::map< tools::Long, std::pair< tools::Long, long >, FuzzyCompare > BoundaryMap; + BoundaryMap aBoundaries; + BoundaryMap::iterator aIter; + std::pair< tools::Long, long > aPair; + + typedef std::map< tools::Long, bool > HiddenMap; + HiddenMap aHidden; + HiddenMap::iterator aHiddenIter; + + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + // upper and lower borders of current cell frame: + tools::Long nUpperBorder = aRectFnSet.GetTop(pFrame->getFrameArea()); + tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + + // get boundaries for nUpperBorder: + aIter = aBoundaries.find( nUpperBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + aBoundaries[ nUpperBorder ] = aPair; + } + + // get boundaries for nLowerBorder: + aIter = aBoundaries.find( nLowerBorder ); + if ( aIter == aBoundaries.end() ) + { + aPair.first = nUpperBorder; aPair.second = LONG_MAX; + } + else + { + nLowerBorder = (*aIter).first; + tools::Long nNewLowerBorderUpperBoundary = std::max( (*aIter).second.first, nUpperBorder ); + aPair.first = nNewLowerBorderUpperBoundary; aPair.second = LONG_MAX; + } + aBoundaries[ nLowerBorder ] = aPair; + + // calculate hidden flags for entry nUpperBorder/nLowerBorder: + tools::Long nTmpVal = nUpperBorder; + for ( sal_uInt8 i = 0; i < 2; ++i ) + { + aHiddenIter = aHidden.find( nTmpVal ); + if ( aHiddenIter == aHidden.end() ) + aHidden[ nTmpVal ] = !lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ); + else + { + if ( aHidden[ nTmpVal ] && + lcl_IsFrameInColumn( *static_cast<const SwCellFrame*>(pFrame), aBoxes ) ) + aHidden[ nTmpVal ] = false; + } + nTmpVal = nLowerBorder; + } + } + + pFrame = pFrame->GetNextLayoutLeaf(); + } + + // transfer calculated values from BoundaryMap and HiddenMap into rFill: + size_t nIdx = 0; + for ( const auto& rEntry : aBoundaries ) + { + const tools::Long nTabTop = aRectFnSet.GetPrtTop(*pTab); + const tools::Long nKey = aRectFnSet.YDiff( rEntry.first, nTabTop ); + const std::pair< tools::Long, long > aTmpPair = rEntry.second; + const tools::Long nFirst = aRectFnSet.YDiff( aTmpPair.first, nTabTop ); + const tools::Long nSecond = aTmpPair.second; + + aHiddenIter = aHidden.find( rEntry.first ); + const bool bHidden = aHiddenIter != aHidden.end() && (*aHiddenIter).second; + rFill.Insert( nKey, nFirst, nSecond, bHidden, nIdx++ ); + } + + // delete first and last entry + OSL_ENSURE( rFill.Count(), "Deleting from empty vector. Fasten your seatbelts!" ); + // #i60818# There may be only one entry in rFill. Make + // code robust by checking count of rFill. + if ( rFill.Count() ) rFill.Remove( 0 ); + if ( rFill.Count() ) rFill.Remove( rFill.Count() - 1 ); + rFill.SetLastRowAllowedToChange( !pTab->HasFollowFlowLine() ); +} + +void SwDoc::SetTabCols( const SwTabCols &rNew, bool bCurRowOnly, + const SwCellFrame* pBoxFrame ) +{ + const SwTableBox* pBox = nullptr; + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + pBox = pBoxFrame->GetTabBox(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwTable& rTab = *pTab->GetTable(); + const SwFormatFrameSize& rTableFrameSz = rTab.GetFrameFormat()->GetFrameSize(); + SwRectFnSet aRectFnSet(pTab); + // #i17174# - With fix for #i9040# the shadow size is taken + // from the table width. Thus, add its left and right size to current table + // printing area width in order to get the correct table size attribute. + SwTwips nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + { + SvxShadowItem aShadow( rTab.GetFrameFormat()->GetShadow() ); + nPrtWidth += aShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ) + + aShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + } + if( nPrtWidth != rTableFrameSz.GetWidth() ) + { + SwFormatFrameSize aSz( rTableFrameSz ); + aSz.SetWidth( nPrtWidth ); + rTab.GetFrameFormat()->SetFormatAttr( aSz ); + } + + SwTabCols aOld( rNew.Count() ); + + const SwPageFrame* pPage = pTab->FindPageFrame(); + const sal_uLong nLeftMin = aRectFnSet.GetLeft(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + const sal_uLong nRightMax = aRectFnSet.GetRight(pTab->getFrameArea()) - + aRectFnSet.GetLeft(pPage->getFrameArea()); + + // Set fixed points, LeftMin in Document coordinates, all others relative + aOld.SetLeftMin ( nLeftMin ); + aOld.SetLeft ( aRectFnSet.GetLeft(pTab->getFramePrintArea()) ); + aOld.SetRight ( aRectFnSet.GetRight(pTab->getFramePrintArea())); + aOld.SetRightMax( nRightMax - nLeftMin ); + + rTab.GetTabCols( aOld, pBox ); + SetTabCols(rTab, rNew, aOld, pBox, bCurRowOnly ); +} + +void SwDoc::SetTabRows( const SwTabCols &rNew, bool bCurColOnly, + const SwCellFrame* pBoxFrame ) +{ + SwTabFrame *pTab = nullptr; + + if( pBoxFrame ) + { + pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoxFrame))->ImplFindTabFrame(); + } + else + { + OSL_ENSURE( false, "must specify pBoxFrame" ); + return ; + } + + // If the Table is still using relative values (USHRT_MAX) + // we need to switch to absolute ones. + SwRectFnSet aRectFnSet(pTab); + SwTabCols aOld( rNew.Count() ); + + // Set fixed points, LeftMin in Document coordinates, all others relative + const SwPageFrame* pPage = pTab->FindPageFrame(); + + aOld.SetRight( aRectFnSet.GetHeight(pTab->getFramePrintArea()) ); + tools::Long nLeftMin; + if ( aRectFnSet.IsVert() ) + { + nLeftMin = pTab->GetPrtLeft() - pPage->getFrameArea().Left(); + aOld.SetLeft ( LONG_MAX ); + aOld.SetRightMax( aOld.GetRight() ); + + } + else + { + nLeftMin = pTab->GetPrtTop() - pPage->getFrameArea().Top(); + aOld.SetLeft ( 0 ); + aOld.SetRightMax( LONG_MAX ); + } + aOld.SetLeftMin ( nLeftMin ); + + GetTabRows( aOld, pBoxFrame ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_ATTR, nullptr ); + + // check for differences between aOld and rNew: + const size_t nCount = rNew.Count(); + const SwTable* pTable = pTab->GetTable(); + OSL_ENSURE( pTable, "My colleague told me, this couldn't happen" ); + + for ( size_t i = 0; i <= nCount; ++i ) + { + const size_t nIdxStt = aRectFnSet.IsVert() ? nCount - i : i - 1; + const size_t nIdxEnd = aRectFnSet.IsVert() ? nCount - i - 1 : i; + + const tools::Long nOldRowStart = i == 0 ? 0 : aOld[ nIdxStt ]; + const tools::Long nOldRowEnd = i == nCount ? aOld.GetRight() : aOld[ nIdxEnd ]; + const tools::Long nOldRowHeight = nOldRowEnd - nOldRowStart; + + const tools::Long nNewRowStart = i == 0 ? 0 : rNew[ nIdxStt ]; + const tools::Long nNewRowEnd = i == nCount ? rNew.GetRight() : rNew[ nIdxEnd ]; + const tools::Long nNewRowHeight = nNewRowEnd - nNewRowStart; + + const tools::Long nDiff = nNewRowHeight - nOldRowHeight; + if ( std::abs( nDiff ) >= ROWFUZZY ) + { + // For the old table model pTextFrame and pLine will be set for every box. + // For the new table model pTextFrame will be set if the box is not covered, + // but the pLine will be set if the box is not an overlapping box + // In the new table model the row height can be adjusted, + // when both variables are set. + const SwTextFrame* pTextFrame = nullptr; + const SwTableLine* pLine = nullptr; + + // Iterate over all SwCellFrames with Bottom = nOldPos + const SwFrame* pFrame = pTab->GetNextLayoutLeaf(); + while ( pFrame && pTab->IsAnLower( pFrame ) ) + { + if ( pFrame->IsCellFrame() && pFrame->FindTabFrame() == pTab ) + { + const tools::Long nLowerBorder = aRectFnSet.GetBottom(pFrame->getFrameArea()); + const sal_uLong nTabTop = aRectFnSet.GetPrtTop(*pTab); + if ( std::abs( aRectFnSet.YInc( nTabTop, nOldRowEnd ) - nLowerBorder ) <= ROWFUZZY ) + { + if ( !bCurColOnly || pFrame == pBoxFrame ) + { + const SwFrame* pContent = ::GetCellContent( static_cast<const SwCellFrame&>(*pFrame) ); + + if ( pContent && pContent->IsTextFrame() ) + { + const SwTableBox* pBox = static_cast<const SwCellFrame*>(pFrame)->GetTabBox(); + const sal_Int32 nRowSpan = pBox->getRowSpan(); + if( nRowSpan > 0 ) // Not overlapped + pTextFrame = static_cast<const SwTextFrame*>(pContent); + if( nRowSpan < 2 ) // Not overlapping for row height + pLine = pBox->GetUpper(); + if( pLine && pTextFrame ) // always for old table model + { + // The new row height must not to be calculated from an overlapping box + SwFormatFrameSize aNew( pLine->GetFrameFormat()->GetFrameSize() ); + const tools::Long nNewSize = aRectFnSet.GetHeight(pFrame->getFrameArea()) + nDiff; + if( nNewSize != aNew.GetHeight() ) + { + aNew.SetHeight( nNewSize ); + if ( SwFrameSize::Variable == aNew.GetHeightSizeType() ) + aNew.SetHeightSizeType( SwFrameSize::Minimum ); + // This position must not be in an overlapped box + const SwPosition aPos(*static_cast<const SwTextFrame*>(pContent)->GetTextNodeFirst()); + const SwCursor aTmpCursor( aPos, nullptr ); + SetRowHeight( aTmpCursor, aNew ); + // For the new table model we're done, for the old one + // there might be another (sub)row to adjust... + if( pTable->IsNewModel() ) + break; + } + pLine = nullptr; + } + } + } + } + } + pFrame = pFrame->GetNextLayoutLeaf(); + } + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::TABLE_ATTR, nullptr ); + + ::ClearFEShellTabCols(*this, nullptr); +} + +/** + * Direct access for UNO + */ +void SwDoc::SetTabCols(SwTable& rTab, const SwTabCols &rNew, const SwTabCols &rOld, + const SwTableBox *pStart, bool bCurRowOnly ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAttrTable>( *rTab.GetTableNode(), true )); + } + rTab.SetTabCols( rNew, rOld, pStart, bCurRowOnly ); + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); +} + +void SwDoc::SetRowsToRepeat( SwTable &rTable, sal_uInt16 nSet ) +{ + if( nSet == rTable.GetRowsToRepeat() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableHeadline>(rTable, rTable.GetRowsToRepeat(), nSet) ); + } + + rTable.SetRowsToRepeat(nSet); + rTable.GetFrameFormat()->CallSwClientNotify(sw::TableHeadingChange()); + getIDocumentState().SetModified(); +} + +void SwCollectTableLineBoxes::AddToUndoHistory( const SwContentNode& rNd ) +{ + if( m_pHistory ) + m_pHistory->AddColl(rNd.GetFormatColl(), rNd.GetIndex(), SwNodeType::Text); +} + +void SwCollectTableLineBoxes::AddBox( const SwTableBox& rBox ) +{ + m_aPositionArr.push_back(m_nWidth); + SwTableBox* p = const_cast<SwTableBox*>(&rBox); + m_Boxes.push_back(p); + m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); +} + +const SwTableBox* SwCollectTableLineBoxes::GetBoxOfPos( const SwTableBox& rBox ) +{ + const SwTableBox* pRet = nullptr; + + if( !m_aPositionArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < m_aPositionArr.size(); ++n ) + if( m_aPositionArr[ n ] == m_nWidth ) + break; + else if( m_aPositionArr[ n ] > m_nWidth ) + { + if( n ) + --n; + break; + } + + if( n >= m_aPositionArr.size() ) + --n; + + m_nWidth = m_nWidth + o3tl::narrowing<sal_uInt16>(rBox.GetFrameFormat()->GetFrameSize().GetWidth()); + pRet = m_Boxes[ n ]; + } + return pRet; +} + +bool SwCollectTableLineBoxes::Resize( sal_uInt16 nOffset, sal_uInt16 nOldWidth ) +{ + if( !m_aPositionArr.empty() ) + { + std::vector<sal_uInt16>::size_type n; + for( n = 0; n < m_aPositionArr.size(); ++n ) + { + if( m_aPositionArr[ n ] == nOffset ) + break; + else if( m_aPositionArr[ n ] > nOffset ) + { + if( n ) + --n; + break; + } + } + + m_aPositionArr.erase( m_aPositionArr.begin(), m_aPositionArr.begin() + n ); + m_Boxes.erase(m_Boxes.begin(), m_Boxes.begin() + n); + + size_t nArrSize = m_aPositionArr.size(); + if (nArrSize) + { + if (nOldWidth == 0) + throw o3tl::divide_by_zero(); + + // Adapt the positions to the new Size + for( n = 0; n < nArrSize; ++n ) + { + sal_uLong nSize = m_nWidth; + nSize *= ( m_aPositionArr[ n ] - nOffset ); + nSize /= nOldWidth; + m_aPositionArr[ n ] = sal_uInt16( nSize ); + } + } + } + return !m_aPositionArr.empty(); +} + +bool sw_Line_CollectBox( const SwTableLine*& rpLine, void* pPara ) +{ + SwCollectTableLineBoxes* pSplPara = static_cast<SwCollectTableLineBoxes*>(pPara); + if( pSplPara->IsGetValues() ) + for( const auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, pSplPara ); + else + for( auto& rpBox : const_cast<SwTableLine*>(rpLine)->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, pSplPara ); + return true; +} + +void sw_Box_CollectBox( const SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + pSplPara->AddBox( *pBox ); +} + +void sw_BoxSetSplitBoxFormats( SwTableBox* pBox, SwCollectTableLineBoxes* pSplPara ) +{ + auto nLen = pBox->GetTabLines().size(); + if( nLen ) + { + // Continue with the actual Line + if( pSplPara->IsGetFromTop() ) + nLen = 0; + else + --nLen; + + const SwTableLine* pLn = pBox->GetTabLines()[ nLen ]; + sw_Line_CollectBox( pLn, pSplPara ); + } + else + { + const SwTableBox* pSrcBox = pSplPara->GetBoxOfPos( *pBox ); + SwFrameFormat* pFormat = pSrcBox->GetFrameFormat(); + + if( SplitTable_HeadlineOption::BorderCopy == pSplPara->GetMode() ) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + if( !rBoxItem.GetTop() ) + { + SvxBoxItem aNew( rBoxItem ); + aNew.SetLine( pFormat->GetBox().GetBottom(), SvxBoxItemLine::TOP ); + if( aNew != rBoxItem ) + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + } + } + else + { + SfxItemSetFixed<RES_LR_SPACE, RES_UL_SPACE, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BACKGROUND, RES_SHADOW> + aTmpSet( pFormat->GetDoc()->GetAttrPool() ); + aTmpSet.Put( pFormat->GetAttrSet() ); + if( aTmpSet.Count() ) + pBox->ClaimFrameFormat()->SetFormatAttr( aTmpSet ); + + if( SplitTable_HeadlineOption::BoxAttrAllCopy == pSplPara->GetMode() ) + { + SwNodeIndex aIdx( *pSrcBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + aIdx = *pBox->GetSttNd(); + SwContentNode* pDNd = aIdx.GetNodes().GoNext( &aIdx ); + + // If the Node is alone in the Section + if( SwNodeOffset(2) == pDNd->EndOfSectionIndex() - + pDNd->StartOfSectionIndex() ) + { + pSplPara->AddToUndoHistory( *pDNd ); + pDNd->ChgFormatColl( pCNd->GetFormatColl() ); + } + } + + // note conditional template + pBox->GetSttNd()->CheckSectionCondColl(); + } + } +} + +/** + * Splits a Table in the top-level Line which contains the Index. + * All succeeding top-level Lines go into a new Table/Node. + * + * @param bCalcNewSize true + * Calculate the new Size for both from the + * Boxes' Max; but only if Size is using absolute + * values (USHRT_MAX) + */ +void SwDoc::SplitTable( const SwPosition& rPos, SplitTable_HeadlineOption eHdlnMode, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos.GetNode(); + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return; + + if( dynamic_cast<const SwDDETable*>( &pTNd->GetTable() ) != nullptr) + return; + + SwTable& rTable = pTNd->GetTable(); + rTable.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + SwHistory aHistory; + { + SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + // Find top-level Line + SwTableBox* pBox = rTable.GetTableBox(nSttIdx); + sal_uInt16 nSplitLine = 0; + if(pBox) + { + SwTableLine* pLine = pBox->GetUpper(); + while(pLine->GetUpper()) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine contains the top-level Line now + nSplitLine = rTable.GetTabLines().GetPos(pLine); + } + rTable.Split(GetUniqueTableName(), nSplitLine, GetIDocumentUndoRedo().DoesUndo() ? &aHistory : nullptr); + } + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + SwTableNode* pNew = GetNodes().SplitTable( rPos.GetNode(), false, bCalcNewSize ); + + if( pNew ) + { + std::unique_ptr<SwSaveRowSpan> pSaveRowSp = pNew->GetTable().CleanUpTopRowSpan( rTable.GetTabLines().size() ); + SwUndoSplitTable* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoSplitTable( + *pNew, std::move(pSaveRowSp), eHdlnMode, bCalcNewSize); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + if( aHistory.Count() ) + pUndo->SaveFormula( aHistory ); + } + + switch( eHdlnMode ) + { + // Set the lower Border of the preceding Line to + // the upper Border of the current one + case SplitTable_HeadlineOption::BorderCopy: + { + SwCollectTableLineBoxes aPara( false, eHdlnMode ); + SwTableLine* pLn = rTable.GetTabLines()[ + rTable.GetTabLines().size() - 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + + // Switch off repeating Header + pNew->GetTable().SetRowsToRepeat( 0 ); + } + break; + + // Take over the Attributes of the first Line to the new one + case SplitTable_HeadlineOption::BoxAttrCopy: + case SplitTable_HeadlineOption::BoxAttrAllCopy: + { + SwHistory* pHst = nullptr; + if( SplitTable_HeadlineOption::BoxAttrAllCopy == eHdlnMode && pUndo ) + pHst = pUndo->GetHistory(); + + SwCollectTableLineBoxes aPara( true, eHdlnMode, pHst ); + SwTableLine* pLn = rTable.GetTabLines()[ 0 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox(rpBox, &aPara ); + + aPara.SetValues( true ); + pLn = pNew->GetTable().GetTabLines()[ 0 ]; + for( auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aPara ); + } + break; + + case SplitTable_HeadlineOption::ContentCopy: + rTable.CopyHeadlineIntoTable( *pNew ); + if( pUndo ) + pUndo->SetTableNodeOffset( pNew->GetIndex() ); + break; + + case SplitTable_HeadlineOption::NONE: + // Switch off repeating the Header + pNew->GetTable().SetRowsToRepeat( 0 ); + break; + } + + // And insert Frames + pNew->MakeOwnFrames(); + + // Insert a paragraph between the Table + GetNodes().MakeTextNode( *pNew, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( rTable.GetFrameFormat()->GetName() ); + + // update table style formatting of both the tables + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + { + pFEShell->UpdateTableStyleFormatting(pTNd); + pFEShell->UpdateTableStyleFormatting(pNew); + } + + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); +} + +static bool lcl_ChgTableSize( SwTable& rTable ) +{ + // The Attribute must not be set via the Modify or else all Boxes are + // set back to 0. + // So lock the Format. + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SwFormatFrameSize aTableMaxSz( pFormat->GetFrameSize() ); + + if( USHRT_MAX == aTableMaxSz.GetWidth() ) + return false; + + bool bLocked = pFormat->IsModifyLocked(); + pFormat->LockModify(); + + aTableMaxSz.SetWidth( 0 ); + + SwTableLines& rLns = rTable.GetTabLines(); + for( auto pLn : rLns ) + { + SwTwips nMaxLnWidth = 0; + SwTableBoxes& rBoxes = pLn->GetTabBoxes(); + for( auto pBox : rBoxes ) + nMaxLnWidth += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + + if( nMaxLnWidth > aTableMaxSz.GetWidth() ) + aTableMaxSz.SetWidth( nMaxLnWidth ); + } + pFormat->SetFormatAttr( aTableMaxSz ); + if( !bLocked ) // Release the Lock if appropriate + pFormat->UnlockModify(); + + return true; +} + +namespace { + +class SplitTable_Para +{ + std::map<SwFrameFormat const*, SwFrameFormat*> m_aSrcDestMap; + SwTableNode* m_pNewTableNode; + SwTable& m_rOldTable; + +public: + SplitTable_Para(SwTableNode* pNew, SwTable& rOld) + : m_pNewTableNode(pNew) + , m_rOldTable(rOld) + {} + SwFrameFormat* GetDestFormat( SwFrameFormat* pSrcFormat ) const + { + auto it = m_aSrcDestMap.find(pSrcFormat); + return it == m_aSrcDestMap.end() ? nullptr : it->second; + } + + void InsertSrcDest( SwFrameFormat const * pSrcFormat, SwFrameFormat* pDestFormat ) + { + m_aSrcDestMap[pSrcFormat] = pDestFormat; + } + + void ChgBox( SwTableBox* pBox ) + { + m_rOldTable.GetTabSortBoxes().erase(pBox); + m_pNewTableNode->GetTable().GetTabSortBoxes().insert(pBox); + } +}; + +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ); + +static void lcl_SplitTable_CpyLine( SwTableLine* pLn, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pLn->GetFrameFormat(); + SwTableLineFormat* pDestFormat = static_cast<SwTableLineFormat*>( pPara->GetDestFormat( pSrcFormat ) ); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pLn->ClaimFrameFormat() ); + } + else + pLn->ChgFrameFormat( pDestFormat ); + + for( auto& rpBox : pLn->GetTabBoxes() ) + lcl_SplitTable_CpyBox(rpBox, pPara ); +} + +static void lcl_SplitTable_CpyBox( SwTableBox* pBox, SplitTable_Para* pPara ) +{ + SwFrameFormat *pSrcFormat = pBox->GetFrameFormat(); + SwTableBoxFormat* pDestFormat = static_cast<SwTableBoxFormat*>(pPara->GetDestFormat( pSrcFormat )); + if( pDestFormat == nullptr ) + { + pPara->InsertSrcDest( pSrcFormat, pBox->ClaimFrameFormat() ); + } + else + pBox->ChgFrameFormat( pDestFormat ); + + if( pBox->GetSttNd() ) + pPara->ChgBox( pBox ); + else + for( SwTableLine* pLine : pBox->GetTabLines() ) + lcl_SplitTable_CpyLine( pLine, pPara ); +} + +SwTableNode* SwNodes::SplitTable( SwNode& rPos, bool bAfter, + bool bCalcNewSize ) +{ + SwNode* pNd = &rPos; + SwTableNode* pTNd = pNd->FindTableNode(); + if( !pTNd || pNd->IsTableNode() ) + return nullptr; + + SwNodeOffset nSttIdx = pNd->FindTableBoxStartNode()->GetIndex(); + + // Find this Box/top-level line + SwTable& rTable = pTNd->GetTable(); + SwTableBox* pBox = rTable.GetTableBox( nSttIdx ); + if( !pBox ) + return nullptr; + + SwTableLine* pLine = pBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // pLine now contains the top-level line + sal_uInt16 nLinePos = rTable.GetTabLines().GetPos( pLine ); + if( USHRT_MAX == nLinePos || + ( bAfter ? ++nLinePos >= rTable.GetTabLines().size() : !nLinePos )) + return nullptr; // Not found or last Line! + + // Find the first Box of the succeeding Line + SwTableLine* pNextLine = rTable.GetTabLines()[ nLinePos ]; + pBox = pNextLine->GetTabBoxes()[0]; + while( !pBox->GetSttNd() ) + pBox = pBox->GetTabLines()[0]->GetTabBoxes()[0]; + + // Insert an EndNode and TableNode into the Nodes Array + SwTableNode * pNewTableNd; + { + SwEndNode* pOldTableEndNd = pTNd->EndOfSectionNode()->GetEndNode(); + assert(pOldTableEndNd && "Where is the EndNode?"); + + new SwEndNode( *pBox->GetSttNd(), *pTNd ); + pNewTableNd = new SwTableNode( *pBox->GetSttNd() ); + pNewTableNd->GetTable().SetTableModel( rTable.IsNewModel() ); + + pOldTableEndNd->m_pStartOfSection = pNewTableNd; + pNewTableNd->m_pEndOfSection = pOldTableEndNd; + + SwNode* pBoxNd = const_cast<SwStartNode*>(pBox->GetSttNd()->GetStartNode()); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pNewTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pOldTableEndNd ); + } + + { + // Move the Lines + SwTable& rNewTable = pNewTableNd->GetTable(); + rNewTable.GetTabLines().insert( rNewTable.GetTabLines().begin(), + rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + /* From the back (bottom right) to the front (top left) deregister all Boxes from the + Chart Data Provider. The Modify event is triggered in the calling function. + TL_CHART2: */ + SwChartDataProvider *pPCD = rTable.GetFrameFormat()->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if( pPCD ) + { + for (SwTableLines::size_type k = nLinePos; k < rTable.GetTabLines().size(); ++k) + { + const SwTableLines::size_type nLineIdx = (rTable.GetTabLines().size() - 1) - k + nLinePos; + const SwTableBoxes::size_type nBoxCnt = rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes().size(); + for (SwTableBoxes::size_type j = 0; j < nBoxCnt; ++j) + { + const SwTableBoxes::size_type nIdx = nBoxCnt - 1 - j; + pPCD->DeleteBox( &rTable, *rTable.GetTabLines()[ nLineIdx ]->GetTabBoxes()[nIdx] ); + } + } + } + + // Delete + sal_uInt16 nDeleted = rTable.GetTabLines().size() - nLinePos; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nLinePos, rTable.GetTabLines().end() ); + + // Move the affected Boxes. Make the Formats unique and correct the StartNodes + SplitTable_Para aPara( pNewTableNd, rTable ); + for( SwTableLine* pNewLine : rNewTable.GetTabLines() ) + lcl_SplitTable_CpyLine( pNewLine, &aPara ); + rTable.CleanUpBottomRowSpan( nDeleted ); + } + + { + // Copy the Table FrameFormat + SwFrameFormat* pOldTableFormat = rTable.GetFrameFormat(); + SwFrameFormat* pNewTableFormat = pOldTableFormat->GetDoc()->MakeTableFrameFormat( + pOldTableFormat->GetDoc()->GetUniqueTableName(), + pOldTableFormat->GetDoc()->GetDfltFrameFormat() ); + + *pNewTableFormat = *pOldTableFormat; + pNewTableNd->GetTable().RegisterToFormat( *pNewTableFormat ); + + pNewTableNd->GetTable().SetTableStyleName(rTable.GetTableStyleName()); + + // Calculate a new Size? + // lcl_ChgTableSize: Only execute the second call if the first call was + // successful, thus has an absolute Size + if( bCalcNewSize && lcl_ChgTableSize( rTable ) ) + lcl_ChgTableSize( pNewTableNd->GetTable() ); + } + + // TL_CHART2: need to inform chart of probably changed cell names + rTable.UpdateCharts(); + + return pNewTableNd; // That's it! +} + +/** + * rPos needs to be in the Table that remains + * + * @param bWithPrev merge the current Table with the preceding + * or succeeding one + */ +bool SwDoc::MergeTable( const SwPosition& rPos, bool bWithPrev ) +{ + SwTableNode* pTableNd = rPos.GetNode().FindTableNode(), *pDelTableNd; + if( !pTableNd ) + return false; + + SwNodes& rNds = GetNodes(); + if( bWithPrev ) + pDelTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + else + pDelTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + if( !pDelTableNd ) + return false; + + if( dynamic_cast<const SwDDETable*>( &pTableNd->GetTable() ) != nullptr || + dynamic_cast<const SwDDETable*>( &pDelTableNd->GetTable() ) != nullptr) + return false; + + // Delete HTML Layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + pDelTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // Both Tables are present; we can start + SwUndoMergeTable* pUndo = nullptr; + std::unique_ptr<SwHistory> pHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoMergeTable( *pTableNd, *pDelTableNd, bWithPrev ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + pHistory.reset(new SwHistory); + } + + // Adapt all "TableFormulas" + pTableNd->GetTable().Merge(pDelTableNd->GetTable(), pHistory.get()); + + // The actual merge + bool bRet = rNds.MergeTable( bWithPrev ? *pTableNd : *pDelTableNd, !bWithPrev ); + + if( pHistory ) + { + if( pHistory->Count() ) + pUndo->SaveFormula( *pHistory ); + pHistory.reset(); + } + if( bRet ) + { + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + return bRet; +} + +bool SwNodes::MergeTable( SwNode& rPos, bool bWithPrev ) +{ + SwTableNode* pDelTableNd = rPos.GetTableNode(); + OSL_ENSURE( pDelTableNd, "Where did the TableNode go?" ); + + SwTableNode* pTableNd = (*this)[ rPos.GetIndex() - 1]->FindTableNode(); + OSL_ENSURE( pTableNd, "Where did the TableNode go?" ); + + if( !pDelTableNd || !pTableNd ) + return false; + + pDelTableNd->DelFrames(); + + SwTable& rDelTable = pDelTableNd->GetTable(); + SwTable& rTable = pTableNd->GetTable(); + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rTable ); + aFndBox.DelFrames( rTable ); + + // TL_CHART2: + // tell the charts about the table to be deleted and have them use their own data + GetDoc().getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &rDelTable ); + + // Sync the TableFormat's Width + { + const SwFormatFrameSize& rTableSz = rTable.GetFrameFormat()->GetFrameSize(); + const SwFormatFrameSize& rDelTableSz = rDelTable.GetFrameFormat()->GetFrameSize(); + if( rTableSz != rDelTableSz ) + { + // The needs correction + if( bWithPrev ) + rDelTable.GetFrameFormat()->SetFormatAttr( rTableSz ); + else + rTable.GetFrameFormat()->SetFormatAttr( rDelTableSz ); + } + } + + if( !bWithPrev ) + { + // Transfer all Attributes of the succeeding Table to the preceding one + // We do this, because the succeeding one is deleted when deleting the Node + rTable.SetRowsToRepeat( rDelTable.GetRowsToRepeat() ); + rTable.SetTableChgMode( rDelTable.GetTableChgMode() ); + + rTable.GetFrameFormat()->LockModify(); + *rTable.GetFrameFormat() = *rDelTable.GetFrameFormat(); + // Also switch the Name + rTable.GetFrameFormat()->SetFormatName( rDelTable.GetFrameFormat()->GetName() ); + rTable.GetFrameFormat()->UnlockModify(); + } + + // Move the Lines and Boxes + SwTableLines::size_type nOldSize = rTable.GetTabLines().size(); + rTable.GetTabLines().insert( rTable.GetTabLines().begin() + nOldSize, + rDelTable.GetTabLines().begin(), rDelTable.GetTabLines().end() ); + rDelTable.GetTabLines().clear(); + + rTable.GetTabSortBoxes().insert( rDelTable.GetTabSortBoxes() ); + rDelTable.GetTabSortBoxes().clear(); + + // The preceding Table always remains, while the succeeding one is deleted + SwEndNode* pTableEndNd = pDelTableNd->EndOfSectionNode(); + pTableNd->m_pEndOfSection = pTableEndNd; + + SwNodeIndex aIdx( *pDelTableNd, 1 ); + + SwNode* pBoxNd = aIdx.GetNode().GetStartNode(); + do { + OSL_ENSURE( pBoxNd->IsStartNode(), "This needs to be a StartNode!" ); + pBoxNd->m_pStartOfSection = pTableNd; + pBoxNd = (*this)[ pBoxNd->EndOfSectionIndex() + 1 ]; + } while( pBoxNd != pTableEndNd ); + pBoxNd->m_pStartOfSection = pTableNd; + + aIdx -= SwNodeOffset(2); + DelNodes( aIdx, SwNodeOffset(2) ); + + // tweak the conditional styles at the first inserted Line + const SwTableLine* pFirstLn = rTable.GetTabLines()[ nOldSize ]; + sw_LineSetHeadCondColl( pFirstLn ); + + // Clean up the Borders + if( nOldSize ) + { + SwGCLineBorder aPara( rTable ); + aPara.nLinePos = --nOldSize; + pFirstLn = rTable.GetTabLines()[ nOldSize ]; + sw_GC_Line_Border( pFirstLn, &aPara ); + } + + // Update Layout + aFndBox.MakeFrames( rTable ); + + return true; +} + +namespace { + +// Use the PtrArray's ForEach method +struct SetAFormatTabPara +{ + SwTableAutoFormat& rTableFormat; + SwUndoTableAutoFormat* pUndo; + sal_uInt16 nEndBox, nCurBox; + sal_uInt8 nAFormatLine, nAFormatBox; + bool bSingleRowTable; + + explicit SetAFormatTabPara( const SwTableAutoFormat& rNew ) + : rTableFormat( const_cast<SwTableAutoFormat&>(rNew) ), pUndo( nullptr ), + nEndBox( 0 ), nCurBox( 0 ), nAFormatLine( 0 ), nAFormatBox( 0 ), bSingleRowTable(false) + {} +}; + +} + +// Forward declare so that the Lines and Boxes can use recursion +static bool lcl_SetAFormatBox(FndBox_ &, SetAFormatTabPara *pSetPara, bool bResetDirect); +static bool lcl_SetAFormatLine(FndLine_ &, SetAFormatTabPara *pPara, bool bResetDirect); + +static bool lcl_SetAFormatLine(FndLine_ & rLine, SetAFormatTabPara *pPara, bool bResetDirect) +{ + for (auto const& it : rLine.GetBoxes()) + { + lcl_SetAFormatBox(*it, pPara, bResetDirect); + } + return true; +} + +static bool lcl_SetAFormatBox(FndBox_ & rBox, SetAFormatTabPara *pSetPara, bool bResetDirect) +{ + if (!rBox.GetUpper()->GetUpper()) // Box on first level? + { + if( !pSetPara->nCurBox ) + pSetPara->nAFormatBox = 0; + else if( pSetPara->nCurBox == pSetPara->nEndBox ) + pSetPara->nAFormatBox = 3; + else //Even column(1) or Odd column(2) + pSetPara->nAFormatBox = static_cast<sal_uInt8>(1 + ((pSetPara->nCurBox-1) & 1)); + } + + if (rBox.GetBox()->GetSttNd()) + { + SwTableBox* pSetBox = rBox.GetBox(); + if (!pSetBox->HasDirectFormatting() || bResetDirect) + { + if (bResetDirect) + pSetBox->SetDirectFormatting(false); + + SwDoc* pDoc = pSetBox->GetFrameFormat()->GetDoc(); + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_END-1> aCharSet(pDoc->GetAttrPool()); + SfxItemSet aBoxSet(pDoc->GetAttrPool(), aTableBoxSetRange); + sal_uInt8 nPos = pSetPara->nAFormatLine * 4 + pSetPara->nAFormatBox; + const bool bSingleRowTable = pSetPara->bSingleRowTable; + const bool bSingleColTable = pSetPara->nEndBox == 0; + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aCharSet, SwTableAutoFormatUpdateFlags::Char, nullptr); + pSetPara->rTableFormat.UpdateToSet(nPos, bSingleRowTable, bSingleColTable, aBoxSet, SwTableAutoFormatUpdateFlags::Box, pDoc->GetNumberFormatter()); + + if (aCharSet.Count()) + { + SwNodeOffset nSttNd = pSetBox->GetSttIdx()+1; + SwNodeOffset nEndNd = pSetBox->GetSttNd()->EndOfSectionIndex(); + for (; nSttNd < nEndNd; ++nSttNd) + { + SwContentNode* pNd = pDoc->GetNodes()[ nSttNd ]->GetContentNode(); + if (pNd) + pNd->SetAttr(aCharSet); + } + } + + if (aBoxSet.Count()) + { + if (pSetPara->pUndo && SfxItemState::SET == aBoxSet.GetItemState(RES_BOXATR_FORMAT)) + pSetPara->pUndo->SaveBoxContent( *pSetBox ); + + pSetBox->ClaimFrameFormat()->SetFormatAttr(aBoxSet); + } + } + } + else + { + // Not sure how this situation can occur, but apparently we have some kind of table in table. + // I am guessing at how to best handle singlerow in this situation. + const bool bOrigSingleRowTable = pSetPara->bSingleRowTable; + pSetPara->bSingleRowTable = rBox.GetLines().size() == 1; + for (auto const& rpFndLine : rBox.GetLines()) + { + lcl_SetAFormatLine(*rpFndLine, pSetPara, bResetDirect); + } + pSetPara->bSingleRowTable = bOrigSingleRowTable; + } + + if (!rBox.GetUpper()->GetUpper()) // a BaseLine + ++pSetPara->nCurBox; + return true; +} + +bool SwDoc::SetTableAutoFormat(const SwSelBoxes& rBoxes, const SwTableAutoFormat& rNew, bool bResetDirect, bool const isSetStyleName) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SwTable &table = pTableNd->GetTable(); + table.SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + // Disable Undo, but first store parameters + SwUndoTableAutoFormat* pUndo = nullptr; + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + if (bUndo) + { + pUndo = new SwUndoTableAutoFormat( *pTableNd, rNew ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + GetIDocumentUndoRedo().DoUndo(false); + } + + if (isSetStyleName) + { // tdf#98226 do this here where undo can record it + pTableNd->GetTable().SetTableStyleName(rNew.GetName()); + } + + rNew.RestoreTableProperties(table); + + SetAFormatTabPara aPara( rNew ); + FndLines_t& rFLns = pFndBox->GetLines(); + aPara.bSingleRowTable = rFLns.size() == 1; + + for (FndLines_t::size_type n = 0; n < rFLns.size(); ++n) + { + FndLine_* pLine = rFLns[n].get(); + + // Set Upper to 0 (thus simulate BaseLine) + FndBox_* pSaveBox = pLine->GetUpper(); + pLine->SetUpper( nullptr ); + + if( !n ) + aPara.nAFormatLine = 0; + else if (static_cast<size_t>(n+1) == rFLns.size()) + aPara.nAFormatLine = 3; + else + aPara.nAFormatLine = static_cast<sal_uInt8>(1 + ((n-1) & 1 )); + + aPara.nAFormatBox = 0; + aPara.nCurBox = 0; + aPara.nEndBox = pLine->GetBoxes().size()-1; + aPara.pUndo = pUndo; + for (auto const& it : pLine->GetBoxes()) + { + lcl_SetAFormatBox(*it, &aPara, bResetDirect); + } + + pLine->SetUpper( pSaveBox ); + } + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + + return true; +} + +/** + * Find out who has the Attributes + */ +bool SwDoc::GetTableAutoFormat( const SwSelBoxes& rBoxes, SwTableAutoFormat& rGet ) +{ + OSL_ENSURE( !rBoxes.empty(), "No valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // Store table properties + SwTable &table = pTableNd->GetTable(); + rGet.StoreTableProperties(table); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size()) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes()[0].get(); + } + + if( pFndBox->GetLines().empty() ) // One too far? (only one sel. Box) + pFndBox = pFndBox->GetUpper()->GetUpper(); + + FndLines_t& rFLns = pFndBox->GetLines(); + + sal_uInt16 aLnArr[4]; + aLnArr[0] = 0; + aLnArr[1] = 1 < rFLns.size() ? 1 : 0; + aLnArr[2] = 2 < rFLns.size() ? 2 : aLnArr[1]; + aLnArr[3] = rFLns.size() - 1; + + for( sal_uInt8 nLine = 0; nLine < 4; ++nLine ) + { + FndLine_& rLine = *rFLns[ aLnArr[ nLine ] ]; + + sal_uInt16 aBoxArr[4]; + aBoxArr[0] = 0; + aBoxArr[1] = 1 < rLine.GetBoxes().size() ? 1 : 0; + aBoxArr[2] = 2 < rLine.GetBoxes().size() ? 2 : aBoxArr[1]; + aBoxArr[3] = rLine.GetBoxes().size() - 1; + + for( sal_uInt8 nBox = 0; nBox < 4; ++nBox ) + { + SwTableBox* pFBox = rLine.GetBoxes()[ aBoxArr[ nBox ] ]->GetBox(); + // Always apply to the first ones + while( !pFBox->GetSttNd() ) + pFBox = pFBox->GetTabLines()[0]->GetTabBoxes()[0]; + + sal_uInt8 nPos = nLine * 4 + nBox; + SwNodeIndex aIdx( *pFBox->GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetNodes().GoNext( &aIdx ); + + if( pCNd ) + rGet.UpdateFromSet( nPos, pCNd->GetSwAttrSet(), + SwTableAutoFormatUpdateFlags::Char, nullptr ); + rGet.UpdateFromSet( nPos, pFBox->GetFrameFormat()->GetAttrSet(), + SwTableAutoFormatUpdateFlags::Box, + GetNumberFormatter() ); + } + } + + return true; +} + +SwTableAutoFormatTable& SwDoc::GetTableStyles() +{ + if (!m_pTableStyles) + { + m_pTableStyles.reset(new SwTableAutoFormatTable); + m_pTableStyles->Load(); + } + return *m_pTableStyles; +} + +OUString SwDoc::GetUniqueTableName() const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeTable" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpTableFrameFormatTable->size() + 1 ); + return newName; + } + + const OUString aName(SwResId(STR_TABLE_DEFNAME)); + + const size_t nFlagSize = ( mpTableFrameFormatTable->size() / 8 ) + 2; + + std::unique_ptr<sal_uInt8[]> pSetFlags( new sal_uInt8[ nFlagSize ] ); + memset( pSetFlags.get(), 0, nFlagSize ); + + for( size_t n = 0; n < mpTableFrameFormatTable->size(); ++n ) + { + const SwTableFormat* pFormat = (*mpTableFrameFormatTable)[ n ]; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName().startsWith( aName ) ) + { + // Get number and set the Flag + const sal_Int32 nNmLen = aName.getLength(); + size_t nNum = o3tl::toInt32(pFormat->GetName().subView( nNmLen )); + if( nNum-- && nNum < mpTableFrameFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + } + + // All numbers are flagged properly, thus calculate the right number + size_t nNum = mpTableFrameFormatTable->size(); + for( size_t n = 0; n < nFlagSize; ++n ) + { + auto nTmp = pSetFlags[ n ]; + if( nTmp != 0xFF ) + { + // Calculate the number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + + return aName + OUString::number( ++nNum ); +} + +SwTableFormat* SwDoc::FindTableFormatByName( const OUString& rName, bool bAll ) const +{ + const SwFormat* pRet = nullptr; + if( bAll ) + pRet = mpTableFrameFormatTable->FindFormatByName( rName ); + else + { + auto [it, itEnd] = mpTableFrameFormatTable->findRangeByName(rName); + // Only the ones set in the Doc + for( ; it != itEnd; ++it ) + { + const SwFrameFormat* pFormat = *it; + if( !pFormat->IsDefault() && IsUsed( *pFormat ) && + pFormat->GetName() == rName ) + { + pRet = pFormat; + break; + } + } + } + return const_cast<SwTableFormat*>(static_cast<const SwTableFormat*>(pRet)); +} + +void SwDoc::SetColRowWidthHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff ) +{ + SwTableNode* pTableNd = const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()); + std::unique_ptr<SwUndo> pUndo; + + pTableNd->GetTable().SwitchFormulasToInternalRepresentation(); + bool const bUndo(GetIDocumentUndoRedo().DoesUndo()); + bool bRet = false; + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::ColLeft: + case TableChgWidthHeightType::ColRight: + case TableChgWidthHeightType::CellLeft: + case TableChgWidthHeightType::CellRight: + { + bRet = pTableNd->GetTable().SetColWidth( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + } + break; + case TableChgWidthHeightType::RowBottom: + case TableChgWidthHeightType::CellTop: + case TableChgWidthHeightType::CellBottom: + bRet = pTableNd->GetTable().SetRowHeight( rCurrentBox, + eType, nAbsDiff, nRelDiff, + bUndo ? &pUndo : nullptr ); + break; + default: break; + } + + GetIDocumentUndoRedo().DoUndo(bUndo); // SetColWidth can turn it off + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + } +} + +bool SwDoc::IsNumberFormat( const OUString& aString, sal_uInt32& F_Index, double& fOutNumber ) +{ + if( aString.getLength() > 308 ) // optimization matches svl:IsNumberFormat arbitrary value + return false; + + // remove any comment anchor marks + return GetNumberFormatter()->IsNumberFormat( + aString.replaceAll(OUStringChar(CH_TXTATR_INWORD), u""), F_Index, fOutNumber); +} + +void SwDoc::ChkBoxNumFormat( SwTableBox& rBox, bool bCallUpdate ) +{ + // Optimization: If the Box says it's Text, it remains Text + const SwTableBoxNumFormat* pNumFormatItem = rBox.GetFrameFormat()->GetItemIfSet( RES_BOXATR_FORMAT, + false ); + if( pNumFormatItem && GetNumberFormatter()->IsTextFormat(pNumFormatItem->GetValue()) ) + return ; + + std::unique_ptr<SwUndoTableNumFormat> pUndo; + + bool bIsEmptyTextNd; + bool bChgd = true; + sal_uInt32 nFormatIdx; + double fNumber; + if( rBox.HasNumContent( fNumber, nFormatIdx, bIsEmptyTextNd ) ) + { + if( !rBox.IsNumberChanged() ) + bChgd = false; + else + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + pUndo->SetNumFormat( nFormatIdx, fNumber ); + } + + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aBoxSet( GetAttrPool() ); + + bool bLockModify = true; + bool bSetNumberFormat = IsInsTableFormatNum(); + const bool bForceNumberFormat = IsInsTableFormatNum() && IsInsTableChangeNumFormat(); + + // if the user forced a number format in this cell previously, + // keep it, unless the user set that she wants the full number + // format recognition + if( pNumFormatItem && !bForceNumberFormat ) + { + sal_uLong nOldNumFormat = pNumFormatItem->GetValue(); + SvNumberFormatter* pNumFormatr = GetNumberFormatter(); + + SvNumFormatType nFormatType = pNumFormatr->GetType( nFormatIdx ); + if( nFormatType == pNumFormatr->GetType( nOldNumFormat ) || SvNumFormatType::NUMBER == nFormatType ) + { + // Current and specified NumFormat match + // -> keep old Format + nFormatIdx = nOldNumFormat; + bSetNumberFormat = true; + } + else + { + // Current and specified NumFormat do not match + // -> insert as Text + bLockModify = bSetNumberFormat = false; + } + } + + if( bSetNumberFormat || bForceNumberFormat ) + { + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + aBoxSet.Put( SwTableBoxValue( fNumber )); + aBoxSet.Put( SwTableBoxNumFormat( nFormatIdx )); + } + + // It's not enough to only reset the Formula. + // Make sure that the Text is formatted accordingly + if( !bSetNumberFormat && !bIsEmptyTextNd && pNumFormatItem ) + { + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + } + + if( bLockModify ) pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + if( bLockModify ) pBoxFormat->UnlockModify(); + + if( bSetNumberFormat ) + pBoxFormat->SetFormatAttr( aBoxSet ); + } + } + else + { + // It's not a number + SwTableBoxFormat* pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.GetFrameFormat()); + if( SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_FORMAT, false ) || + SfxItemState::SET == pBoxFormat->GetItemState( RES_BOXATR_VALUE, false ) ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::TABLE_AUTOFMT, nullptr ); + pUndo.reset(new SwUndoTableNumFormat( rBox )); + } + + pBoxFormat = static_cast<SwTableBoxFormat*>(rBox.ClaimFrameFormat()); + + // Remove all number formats + sal_uInt16 nWhich1 = RES_BOXATR_FORMULA; + if( !bIsEmptyTextNd ) + { + nWhich1 = RES_BOXATR_FORMAT; + + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( nWhich1 )); + } + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + } + else + bChgd = false; + } + + if( !bChgd ) + return; + + if( pUndo ) + { + pUndo->SetBox( rBox ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + const SwTableNode* pTableNd = rBox.GetSttNd()->FindTableNode(); + if( bCallUpdate ) + { + getIDocumentFieldsAccess().UpdateTableFields(&pTableNd->GetTable()); + + // TL_CHART2: update charts (when cursor leaves cell and + // automatic update is enabled) + if (AUTOUPD_FIELD_AND_CHARTS == GetDocumentSettingManager().getFieldUpdateFlags(true)) + pTableNd->GetTable().UpdateCharts(); + } + getIDocumentState().SetModified(); +} + +void SwDoc::SetTableBoxFormulaAttrs( SwTableBox& rBox, const SfxItemSet& rSet ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoTableNumFormat>(rBox, &rSet) ); + } + + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + } + else if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE )) + { + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->UnlockModify(); + } + pBoxFormat->SetFormatAttr( rSet ); + getIDocumentState().SetModified(); +} + +void SwDoc::ClearLineNumAttrs( SwPosition const & rPos ) +{ + SwPaM aPam(rPos); + aPam.Move(fnMoveBackward); + SwContentNode *pNode = aPam.GetPointContentNode(); + if ( nullptr == pNode ) + return ; + if( !pNode->IsTextNode() ) + return; + + SwTextNode * pTextNode = pNode->GetTextNode(); + if (!(pTextNode && pTextNode->IsNumbered() + && pTextNode->GetText().isEmpty())) + return; + + SfxItemSetFixed<RES_PARATR_BEGIN, RES_PARATR_END - 1> + rSet( pTextNode->GetDoc().GetAttrPool() ); + pTextNode->SwContentNode::GetAttr( rSet ); + const SfxStringItem* pFormatItem = rSet.GetItemIfSet( RES_PARATR_NUMRULE, false ); + if ( !pFormatItem ) + return; + + SwUndoDelNum * pUndo; + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo = new SwUndoDelNum( aPam ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + else + pUndo = nullptr; + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + aRegH.RegisterInModify( pTextNode , *pTextNode ); + if ( pUndo ) + pUndo->AddNode( *pTextNode ); + std::unique_ptr<SfxStringItem> pNewItem(pFormatItem->Clone()); + pNewItem->SetValue(OUString()); + rSet.Put( std::move(pNewItem) ); + pTextNode->SetAttr( rSet ); +} + +void SwDoc::ClearBoxNumAttrs( SwNode& rNode ) +{ + SwStartNode* pSttNd = rNode.FindSttNodeByType( SwTableBoxStartNode ); + if( nullptr == pSttNd || + SwNodeOffset(2) != pSttNd->EndOfSectionIndex() - pSttNd->GetIndex()) + return; + + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + + const SfxItemSet& rSet = pBox->GetFrameFormat()->GetAttrSet(); + const SwTableBoxNumFormat* pFormatItem = rSet.GetItemIfSet( RES_BOXATR_FORMAT, false ); + if( !pFormatItem || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA, false ) || + SfxItemState::SET == rSet.GetItemState( RES_BOXATR_VALUE, false )) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoTableNumFormat>(*pBox)); + } + + SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat(); + + // Keep TextFormats! + sal_uInt16 nWhich1 = RES_BOXATR_FORMAT; + if( pFormatItem && GetNumberFormatter()->IsTextFormat( + pFormatItem->GetValue() )) + nWhich1 = RES_BOXATR_FORMULA; + else + // Just resetting Attributes is not enough + // Make sure that the Text is formatted accordingly + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + + pBoxFormat->ResetFormatAttr( nWhich1, RES_BOXATR_VALUE ); + getIDocumentState().SetModified(); +} + +/** + * Copies a Table from the same or another Doc into itself + * We create a new Table or an existing one is filled with the Content. + * We either fill in the Content from a certain Box or a certain TableSelection + * + * This method is called by edglss.cxx/fecopy.cxx + */ +bool SwDoc::InsCopyOfTable( SwPosition& rInsPos, const SwSelBoxes& rBoxes, + const SwTable* pCpyTable, bool bCpyName, bool bCorrPos, const OUString& rStyleName ) +{ + bool bRet; + + const SwTableNode* pSrcTableNd = pCpyTable + ? pCpyTable->GetTableNode() + : rBoxes[ 0 ]->GetSttNd()->FindTableNode(); + + SwTableNode * pInsTableNd = rInsPos.GetNode().FindTableNode(); + + bool const bUndo( GetIDocumentUndoRedo().DoesUndo() ); + if( !pCpyTable && !pInsTableNd ) + { + std::unique_ptr<SwUndoCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoCpyTable(*this)); + } + + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + bRet = pSrcTableNd->GetTable().MakeCopy( *this, rInsPos, rBoxes, + bCpyName, rStyleName ); + } + + if( pUndo && bRet ) + { + pInsTableNd = GetNodes()[ rInsPos.GetNodeIndex() - 1 ]->FindTableNode(); + + pUndo->SetTableSttIdx( pInsTableNd->GetIndex() ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else + { + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + if( getIDocumentRedlineAccess().IsRedlineOn() ) + getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On | + RedlineFlags::ShowInsert | + RedlineFlags::ShowDelete ); + + std::unique_ptr<SwUndoTableCpyTable> pUndo; + if (bUndo) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoTableCpyTable(*this)); + GetIDocumentUndoRedo().DoUndo(false); + } + + rtl::Reference<SwDoc> xCpyDoc(&const_cast<SwDoc&>(pSrcTableNd->GetDoc())); + bool bDelCpyDoc = xCpyDoc == this; + + if( bDelCpyDoc ) + { + // Copy the Table into a temporary Doc + xCpyDoc = new SwDoc; + + SwPosition aPos( xCpyDoc->GetNodes().GetEndOfContent() ); + if( !pSrcTableNd->GetTable().MakeCopy( *xCpyDoc, aPos, rBoxes, true )) + { + xCpyDoc.clear(); + + if( pUndo ) + { + GetIDocumentUndoRedo().DoUndo(bUndo); + } + return false; + } + aPos.Adjust(SwNodeOffset(-1)); // Set to the Table's EndNode + pSrcTableNd = aPos.GetNode().FindTableNode(); + } + + const SwStartNode* pSttNd = rInsPos.GetNode().FindTableBoxStartNode(); + + rInsPos.nContent.Assign( nullptr, 0 ); + + // no complex into complex, but copy into or from new model is welcome + if( ( !pSrcTableNd->GetTable().IsTableComplex() || pInsTableNd->GetTable().IsNewModel() ) + && ( bDelCpyDoc || !rBoxes.empty() ) ) + { + // Copy the Table "relatively" + const SwSelBoxes* pBoxes; + SwSelBoxes aBoxes; + + if( bDelCpyDoc ) + { + SwTableBox* pBox = pInsTableNd->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "Box is not in this Table" ); + aBoxes.insert( pBox ); + pBoxes = &aBoxes; + } + else + pBoxes = &rBoxes; + + // Copy Table to the selected Lines + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + *pBoxes, pUndo.get() ); + } + else + { + SwNodeIndex aNdIdx( *pSttNd, 1 ); + bRet = pInsTableNd->GetTable().InsTable( pSrcTableNd->GetTable(), + aNdIdx, pUndo.get() ); + } + + xCpyDoc.clear(); + + if( pUndo ) + { + // If the Table could not be copied, delete the Undo object + GetIDocumentUndoRedo().DoUndo(bUndo); + if( bRet || !pUndo->IsEmpty() ) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + if( bCorrPos ) + { + rInsPos.Assign( *pSttNd ); + GetNodes().GoNext( &rInsPos ); + } + getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if( bRet ) + { + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + } + return bRet; +} + +bool SwDoc::UnProtectTableCells( SwTable& rTable ) +{ + bool bChgd = false; + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rTable.GetTableNode() )); + + SwTableSortBoxes& rSrtBox = rTable.GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + pBoxFormat->ResetFormatAttr( RES_PROTECT ); + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + return bChgd; +} + +void SwDoc::UnProtectCells( const OUString& rName ) +{ + SwTableFormat* pFormat = FindTableFormatByName( rName ); + if( pFormat ) + { + bool bChgd = UnProtectTableCells( *SwTable::FindTable( pFormat ) ); + if( bChgd ) + getIDocumentState().SetModified(); + } +} + +bool SwDoc::UnProtectCells( const SwSelBoxes& rBoxes ) +{ + bool bChgd = false; + if( !rBoxes.empty() ) + { + std::unique_ptr<SwUndoAttrTable> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoAttrTable( *rBoxes[0]->GetSttNd()->FindTableNode() )); + + std::map<SwFrameFormat*, SwTableBoxFormat*> aFormatsMap; + for (size_t i = rBoxes.size(); i; ) + { + SwTableBox* pBox = rBoxes[ --i ]; + SwFrameFormat* pBoxFormat = pBox->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + std::map<SwFrameFormat*, SwTableBoxFormat*>::const_iterator const it = + aFormatsMap.find(pBoxFormat); + if (aFormatsMap.end() != it) + pBox->ChgFrameFormat(it->second); + else + { + SwTableBoxFormat *const pNewBoxFormat( + static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat())); + pNewBoxFormat->ResetFormatAttr( RES_PROTECT ); + aFormatsMap.insert(std::make_pair(pBoxFormat, pNewBoxFormat)); + } + bChgd = true; + } + } + + if( pUndo && bChgd ) + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + return bChgd; +} + +void SwDoc::UnProtectTables( const SwPaM& rPam ) +{ + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + bool bChgd = false, bHasSel = rPam.HasMark() || + rPam.GetNext() != &rPam; + sw::TableFrameFormats& rFormats = *GetTableFrameFormats(); + SwTable* pTable; + const SwTableNode* pTableNd; + for( auto n = rFormats.size(); n ; ) + if( nullptr != (pTable = SwTable::FindTable( rFormats[ --n ])) && + nullptr != (pTableNd = pTable->GetTableNode() ) && + pTableNd->GetNodes().IsDocNodes() ) + { + SwNodeOffset nTableIdx = pTableNd->GetIndex(); + + // Check whether the Table is within the Selection + if( bHasSel ) + { + bool bFound = false; + SwPaM* pTmp = const_cast<SwPaM*>(&rPam); + do { + auto [pStt, pEnd] = pTmp->StartEnd(); // SwPosition* + bFound = pStt->GetNodeIndex() < nTableIdx && + nTableIdx < pEnd->GetNodeIndex(); + + } while( !bFound && &rPam != ( pTmp = pTmp->GetNext() ) ); + if( !bFound ) + continue; // Continue searching + } + + // Lift the protection + bChgd |= UnProtectTableCells( *pTable ); + } + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + if( bChgd ) + getIDocumentState().SetModified(); +} + +bool SwDoc::HasTableAnyProtection( const SwPosition* pPos, + const OUString* pTableName, + bool* pFullTableProtection ) +{ + bool bHasProtection = false; + SwTable* pTable = nullptr; + if( pTableName ) + pTable = SwTable::FindTable( FindTableFormatByName( *pTableName ) ); + else if( pPos ) + { + SwTableNode* pTableNd = pPos->GetNode().FindTableNode(); + if( pTableNd ) + pTable = &pTableNd->GetTable(); + } + + if( pTable ) + { + SwTableSortBoxes& rSrtBox = pTable->GetTabSortBoxes(); + for (size_t i = rSrtBox.size(); i; ) + { + SwFrameFormat *pBoxFormat = rSrtBox[ --i ]->GetFrameFormat(); + if( pBoxFormat->GetProtect().IsContentProtected() ) + { + if( !bHasProtection ) + { + bHasProtection = true; + if( !pFullTableProtection ) + break; + *pFullTableProtection = true; + } + } + else if( bHasProtection && pFullTableProtection ) + { + *pFullTableProtection = false; + break; + } + } + } + return bHasProtection; +} + +SwTableAutoFormat* SwDoc::MakeTableStyle(const OUString& rName, bool bBroadcast) +{ + SwTableAutoFormat aTableFormat(rName); + GetTableStyles().AddAutoFormat(aTableFormat); + SwTableAutoFormat* pTableFormat = GetTableStyles().FindAutoFormat(rName); + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleMake>(rName, *this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetCreated); + + return pTableFormat; +} + +std::unique_ptr<SwTableAutoFormat> SwDoc::DelTableStyle(const OUString& rName, bool bBroadcast) +{ + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Table, SfxHintId::StyleSheetErased); + + std::unique_ptr<SwTableAutoFormat> pReleasedFormat = GetTableStyles().ReleaseAutoFormat(rName); + + std::vector<SwTable*> vAffectedTables; + if (pReleasedFormat) + { + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == pReleasedFormat->GetName()) + { + pTable->SetTableStyleName(""); + vAffectedTables.push_back(pTable); + } + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleDelete>(std::move(pReleasedFormat), std::move(vAffectedTables), *this)); + } + } + + return pReleasedFormat; +} + +void SwDoc::ChgTableStyle(const OUString& rName, const SwTableAutoFormat& rNewFormat) +{ + SwTableAutoFormat* pFormat = GetTableStyles().FindAutoFormat(rName); + if (!pFormat) + return; + + SwTableAutoFormat aOldFormat = *pFormat; + *pFormat = rNewFormat; + pFormat->SetName(rName); + + size_t nTableCount = GetTableFrameFormatCount(true); + for (size_t i=0; i < nTableCount; ++i) + { + SwFrameFormat* pFrameFormat = &GetTableFrameFormat(i, true); + SwTable* pTable = SwTable::FindTable(pFrameFormat); + if (pTable->GetTableStyleName() == rName) + if (SwFEShell* pFEShell = GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(pTable->GetTableNode()); + } + + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTableStyleUpdate>(*pFormat, aOldFormat, *this)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/ndtbl1.cxx b/sw/source/core/docnode/ndtbl1.cxx new file mode 100644 index 0000000000..49b93463a4 --- /dev/null +++ b/sw/source/core/docnode/ndtbl1.cxx @@ -0,0 +1,1768 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <fesh.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtrowsplt.hxx> +#include <tabcol.hxx> +#include <frmatr.hxx> +#include <cellfrm.hxx> +#include <tabfrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <svx/svxids.hrc> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <swcrsr.hxx> +#include <viscrs.hxx> +#include <swtable.hxx> +#include <htmltbl.hxx> +#include <tblsel.hxx> +#include <swtblfmt.hxx> +#include <ndindex.hxx> +#include <undobj.hxx> +#include <calbck.hxx> +#include <UndoTable.hxx> +#include <o3tl/enumrange.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <redline.hxx> + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +// See swtable.cxx too +#define COLFUZZY 20L + +static bool IsSame( tools::Long nA, tools::Long nB ) { return std::abs(nA-nB) <= COLFUZZY; } + +namespace { + +// SwTableLine::ChgFrameFormat may delete old format which doesn't have writer listeners anymore. +// This may invalidate my pointers, and lead to use-after-free. For this reason, I register myself +// as a writer listener for the old format here, and take care to delete formats without listeners +// in my own dtor. +class SwTableFormatCmp : public SwClient +{ +public: + SwTableFormatCmp( SwFrameFormat *pOld, SwFrameFormat *pNew, sal_Int16 nType ); + ~SwTableFormatCmp() override; + + static SwFrameFormat* FindNewFormat(std::vector<std::unique_ptr<SwTableFormatCmp>>& rArr, + SwFrameFormat const* pOld, sal_Int16 nType); + +private: + SwFrameFormat *m_pOld, *m_pNew; + sal_Int16 m_nType; +}; + +} + +SwTableFormatCmp::SwTableFormatCmp(SwFrameFormat* pO, SwFrameFormat* pN, sal_Int16 nT) + : m_pOld(pO) + , m_pNew(pN) + , m_nType(nT) +{ + if (m_pOld) + m_pOld->Add(this); +} + +SwTableFormatCmp::~SwTableFormatCmp() +{ + if (m_pOld) + { + m_pOld->Remove(this); + if (!m_pOld->HasWriterListeners()) + delete m_pOld; + } +} + +// static +SwFrameFormat* SwTableFormatCmp::FindNewFormat(std::vector<std::unique_ptr<SwTableFormatCmp>>& rArr, + SwFrameFormat const* pOld, sal_Int16 nType) +{ + for (const auto& pCmp : rArr) + { + if (pCmp->m_pOld == pOld && pCmp->m_nType == nType) + return pCmp->m_pNew; + } + return nullptr; +} + +static void lcl_GetStartEndCell( const SwCursor& rCursor, + SwLayoutFrame *&prStart, SwLayoutFrame *&prEnd ) +{ + OSL_ENSURE( rCursor.GetPointContentNode() && rCursor.GetMarkContentNode(), + "Tab selection not at ContentNode" ); + + Point aPtPos, aMkPos; + const SwShellCursor* pShCursor = dynamic_cast<const SwShellCursor*>(&rCursor); + if( pShCursor ) + { + aPtPos = pShCursor->GetPtPos(); + aMkPos = pShCursor->GetMkPos(); + } + + // Robust: + SwContentNode* pPointNd = rCursor.GetPointContentNode(); + SwContentNode* pMarkNd = rCursor.GetMarkContentNode(); + + std::pair<Point, bool> tmp(aPtPos, true); + SwFrame *const pPointFrame = pPointNd ? pPointNd->getLayoutFrame(pPointNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + tmp.first = aMkPos; + SwFrame *const pMarkFrame = pMarkNd ? pMarkNd->getLayoutFrame(pMarkNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + + prStart = pPointFrame ? pPointFrame->GetUpper() : nullptr; + prEnd = pMarkFrame ? pMarkFrame->GetUpper() : nullptr; +} + +static bool lcl_GetBoxSel( const SwCursor& rCursor, SwSelBoxes& rBoxes, + bool bAllCursor = false ) +{ + const SwTableCursor* pTableCursor = + dynamic_cast<const SwTableCursor*>(&rCursor); + if( pTableCursor ) + ::GetTableSelCrs( *pTableCursor, rBoxes ); + else + { + const SwPaM *pCurPam = &rCursor, *pSttPam = pCurPam; + do { + const SwNode* pNd = pCurPam->GetPointNode().FindTableBoxStartNode(); + if( pNd ) + { + SwTableBox* pBox = const_cast<SwTableBox*>(pNd->FindTableNode()->GetTable(). + GetTableBox( pNd->GetIndex() )); + rBoxes.insert( pBox ); + } + } while( bAllCursor && + pSttPam != ( pCurPam = pCurPam->GetNext()) ); + } + return !rBoxes.empty(); +} + +static void InsertLine( std::vector<SwTableLine*>& rLineArr, SwTableLine* pLine ) +{ + if( rLineArr.end() == std::find( rLineArr.begin(), rLineArr.end(), pLine ) ) + rLineArr.push_back( pLine ); +} + +static bool lcl_IsAnLower( const SwTableLine *pLine, const SwTableLine *pAssumed ) +{ + const SwTableLine *pTmp = pAssumed->GetUpper() ? + pAssumed->GetUpper()->GetUpper() : nullptr; + while ( pTmp ) + { + if ( pTmp == pLine ) + return true; + pTmp = pTmp->GetUpper() ? pTmp->GetUpper()->GetUpper() : nullptr; + } + return false; +} + +namespace { + +struct LinesAndTable +{ + std::vector<SwTableLine*> &m_rLines; + const SwTable &m_rTable; + bool m_bInsertLines; + + LinesAndTable(std::vector<SwTableLine*> &rL, const SwTable &rTable) : + m_rLines(rL), m_rTable(rTable), m_bInsertLines(true) {} +}; + +} + +static bool FindLine_( FndLine_ & rLine, LinesAndTable* pPara ); + +static bool FindBox_( FndBox_ & rBox, LinesAndTable* pPara ) +{ + if (!rBox.GetLines().empty()) + { + pPara->m_bInsertLines = true; + for (auto const& rpFndLine : rBox.GetLines()) + { + FindLine_(*rpFndLine, pPara); + } + + if (pPara->m_bInsertLines) + { + const SwTableLines &rLines = (rBox.GetBox()) + ? rBox.GetBox()->GetTabLines() + : pPara->m_rTable.GetTabLines(); + if (rBox.GetLines().size() == rLines.size()) + { + for ( auto pLine : rLines ) + ::InsertLine(pPara->m_rLines, pLine); + } + else + pPara->m_bInsertLines = false; + } + } + else if (rBox.GetBox()) + { + ::InsertLine(pPara->m_rLines, rBox.GetBox()->GetUpper()); + } + return true; +} + +bool FindLine_( FndLine_& rLine, LinesAndTable* pPara ) +{ + for (auto const& it : rLine.GetBoxes()) + { + FindBox_(*it, pPara); + } + return true; +} + +static void lcl_CollectLines( std::vector<SwTableLine*> &rArr, const SwCursor& rCursor, bool bRemoveLines ) +{ + // Collect the selected Boxes first + SwSelBoxes aBoxes; + if( !::lcl_GetBoxSel( rCursor, aBoxes )) + return ; + + // Copy the selected structure + const SwTable &rTable = aBoxes[0]->GetSttNd()->FindTableNode()->GetTable(); + LinesAndTable aPara( rArr, rTable ); + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aTmpPara( aBoxes, &aFndBox ); + ForEach_FndLineCopyCol( const_cast<SwTableLines&>(rTable.GetTabLines()), &aTmpPara ); + } + + // Collect the Lines which only contain selected Boxes + ::FindBox_(aFndBox, &aPara); + + // Remove lines, that have a common superordinate row. + // (Not for row split) + if ( !bRemoveLines ) + return; + + for ( std::vector<SwTableLine*>::size_type i = 0; i < rArr.size(); ++i ) + { + SwTableLine *pUpLine = rArr[i]; + for ( std::vector<SwTableLine*>::size_type k = 0; k < rArr.size(); ++k ) + { + if ( k != i && ::lcl_IsAnLower( pUpLine, rArr[k] ) ) + { + rArr.erase( rArr.begin() + k ); + if ( k <= i ) + --i; + --k; + } + } + } +} + +static void lcl_ProcessRowAttr(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableLine* pLine, const SfxPoolItem& rNew) +{ + SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( rFormatCmp, pLine->GetFrameFormat(), 0 ); + if ( nullptr != pNewFormat ) + pLine->ChgFrameFormat( static_cast<SwTableLineFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pLine->GetFrameFormat(); + SwFrameFormat *pNew = pLine->ClaimFrameFormat(); + pNew->SetFormatAttr( rNew ); + rFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, 0)); + } +} + +static void lcl_ProcessBoxSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableBox* pBox, const SwFormatFrameSize& rNew); + +static void lcl_ProcessRowSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableLine* pLine, const SwFormatFrameSize& rNew) +{ + lcl_ProcessRowAttr( rFormatCmp, pLine, rNew ); + SwTableBoxes &rBoxes = pLine->GetTabBoxes(); + for ( auto pBox : rBoxes ) + ::lcl_ProcessBoxSize( rFormatCmp, pBox, rNew ); +} + +static void lcl_ProcessBoxSize(std::vector<std::unique_ptr<SwTableFormatCmp>>& rFormatCmp, + SwTableBox* pBox, const SwFormatFrameSize& rNew) +{ + SwTableLines &rLines = pBox->GetTabLines(); + if ( !rLines.empty() ) + { + SwFormatFrameSize aSz( rNew ); + aSz.SetHeight( rNew.GetHeight() ? rNew.GetHeight() / rLines.size() : 0 ); + for ( auto pLine : rLines ) + ::lcl_ProcessRowSize( rFormatCmp, pLine, aSz ); + } +} + +void SwDoc::SetRowSplit( const SwCursor& rCursor, const SwFormatRowSplit &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, false ); + + if( aRowArr.empty() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + + for( auto pLn : aRowArr ) + ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); +} + +std::unique_ptr<SwFormatRowSplit> SwDoc::GetRowSplit( const SwCursor& rCursor ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return nullptr; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, false ); + + if( aRowArr.empty() ) + return nullptr; + + SwFormatRowSplit* pSz = &const_cast<SwFormatRowSplit&>(aRowArr[0]->GetFrameFormat()->GetRowSplit()); + + for ( auto pLn : aRowArr ) + { + if ( pSz->GetValue() != pLn->GetFrameFormat()->GetRowSplit().GetValue() ) + { + return nullptr; + } + } + return std::make_unique<SwFormatRowSplit>( *pSz ); +} + +/* Class: SwDoc + * Methods: SetRowHeight(), GetRowHeight() + * + * The line height is calculated from the Selection. + * Starting with every Cell within the Selection, all Cells are iterated + * through in an upwards fashion. + * + * The topmost Line gets the requested value, all Lines below it get + * a respective value that is calculated from the relation of the old and + * new size of the topmost Line in the lower line's own size. + * + * All changed Lines may get an own FrameFormat. + * Of course we can only touch every Line once. + */ + +void SwDoc::SetRowHeight( const SwCursor& rCursor, const SwFormatFrameSize &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + for ( auto pLn : aRowArr ) + ::lcl_ProcessRowSize( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); +} + +std::unique_ptr<SwFormatFrameSize> SwDoc::GetRowHeight( const SwCursor& rCursor ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return nullptr; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return nullptr; + + SwFormatFrameSize* pSz = &const_cast<SwFormatFrameSize&>(aRowArr[0]->GetFrameFormat()->GetFrameSize()); + + for ( auto pLn : aRowArr ) + { + if ( *pSz != pLn->GetFrameFormat()->GetFrameSize() ) + return nullptr; + } + return std::make_unique<SwFormatFrameSize>( *pSz ); +} + +bool SwDoc::BalanceRowHeight( const SwCursor& rCursor, bool bTstOnly, const bool bOptimize ) +{ + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( 1 < aRowArr.size() ) + { + if( !bTstOnly ) + { + tools::Long nHeight = 0; + sal_Int32 nTotalHeight = 0; + for ( auto pLn : aRowArr ) + { + if (bOptimize) + nHeight = 0; + SwIterator<SwFrame,SwFormat> aIter( *pLn->GetFrameFormat() ); + SwFrame* pFrame = aIter.First(); + while ( pFrame ) + { + nHeight = std::max( nHeight, pFrame->getFrameArea().Height() ); + pFrame = aIter.Next(); + } + nTotalHeight += nHeight; + } + + if ( bOptimize ) + nHeight = nTotalHeight / aRowArr.size(); + + SwFormatFrameSize aNew( SwFrameSize::Minimum, 0, nHeight ); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + for( auto pLn : aRowArr ) + ::lcl_ProcessRowSize( aFormatCmp, pLn, aNew ); + + getIDocumentState().SetModified(); + } + bRet = true; + } + } + return bRet; +} + +void SwDoc::SetRowBackground( const SwCursor& rCursor, const SvxBrushItem &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + + for( auto pLn : aRowArr ) + ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); + + getIDocumentState().SetModified(); +} + +bool SwDoc::GetRowBackground( const SwCursor& rCursor, std::unique_ptr<SvxBrushItem>& rToFill ) +{ + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( pTableNd ) + { + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( !aRowArr.empty() ) + { + rToFill = aRowArr[0]->GetFrameFormat()->makeBackgroundBrushItem(); + + bRet = true; + for ( std::vector<SwTableLine*>::size_type i = 1; i < aRowArr.size(); ++i ) + { + std::unique_ptr<SvxBrushItem> aAlternative(aRowArr[i]->GetFrameFormat()->makeBackgroundBrushItem()); + + if ( *rToFill != *aAlternative ) + { + bRet = false; + break; + } + } + } + } + return bRet; +} + +// has a table row, which is not a tracked deletion +bool SwDoc::HasRowNotTracked( const SwCursor& rCursor ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return false; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return false; + + SwRedlineTable::size_type nRedlinePos = 0; + SwDoc* pDoc = aRowArr[0]->GetFrameFormat()->GetDoc(); + const IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess(); + + for( auto pLn : aRowArr ) + { + auto pHasTextChangesOnlyProp = pLn->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + if ( !pHasTextChangesOnlyProp || pHasTextChangesOnlyProp->GetValue() ) + // there is a not tracked row in the table selection + return true; + + // tdf#150666 examine tracked row: it's possible to delete a tracked insertion + SwRedlineTable::size_type nPos = pLn->UpdateTextChangesOnly(nRedlinePos); + if ( nPos != SwRedlineTable::npos ) + { + const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable(); + SwRangeRedline* pTmp = aRedlineTable[ nPos ]; + if ( RedlineType::Insert == pTmp->GetType() ) + return true; + } + } + return false; +} + +void SwDoc::SetRowNotTracked( const SwCursor& rCursor, + const SvxPrintItem &rNew, bool bAll, bool bIns ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + if( !pTableNd ) + return; + + std::vector<SwTableLine*> aRowArr; // For Lines collecting + if ( bAll ) + { + const SwTableLines &rLines = pTableNd->GetTable().GetTabLines(); + aRowArr.insert(aRowArr.end(), rLines.begin(), rLines.end()); + } + else + ::lcl_CollectLines( aRowArr, rCursor, true ); + + if( aRowArr.empty() ) + return; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + bool bInsertDummy = !bAll && !bIns && + // HasTextChangesOnly == false, i.e. a tracked row change (deletion, if bIns == false) + !rNew.GetValue(); + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( std::max( 255, static_cast<int>(aRowArr.size()) ) ); + + SwRedlineTable::size_type nRedlinePos = 0; + for( auto pLn : aRowArr ) + { + // tdf#150666 deleting row insertion from the same author needs special handling, + // because removing redlines of the author can result an empty line, + // which doesn't contain any redline for the tracked row + bool bDeletionOfOwnRowInsertion = false; + if ( bInsertDummy ) + { + SwRedlineTable::size_type nPos = pLn->UpdateTextChangesOnly(nRedlinePos); + if ( nPos != SwRedlineTable::npos ) + { + SwDoc* pDoc = pLn->GetFrameFormat()->GetDoc(); + IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess(); + const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable(); + SwRangeRedline* pTmp = aRedlineTable[ nPos ]; + if ( RedlineType::Insert == pTmp->GetType() && + rIDRA.GetRedlineAuthor() == pTmp->GetRedlineData().GetAuthor() && + pTmp->GetText()[0] == CH_TXT_TRACKED_DUMMY_CHAR ) + { + bDeletionOfOwnRowInsertion = true; + } + } + } + + ::lcl_ProcessRowAttr( aFormatCmp, pLn, rNew ); + // as a workaround for the rows without text content, + // add a redline with invisible text CH_TXT_TRACKED_DUMMY_CHAR + // (unless the table is part of a bigger deletion, where the + // new redline can cause a problem) + if ( bInsertDummy && (pLn->IsEmpty() || bDeletionOfOwnRowInsertion ) ) + { + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + SwNodeIndex aInsPos( *(pLn->GetTabBoxes()[0]->GetSttNd()), 1 ); + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + getIDocumentRedlineAccess().SetRedlineFlags_intern(RedlineFlags::NONE); + SwPaM aPaM(aInsPos); + getIDocumentContentOperations().InsertString( aPaM, + OUStringChar(CH_TXT_TRACKED_DUMMY_CHAR) ); + aPaM.SetMark(); + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + getIDocumentContentOperations().DeleteAndJoin( aPaM ); + } + } + + getIDocumentState().SetModified(); +} + +static void InsertCell( std::vector<SwCellFrame*>& rCellArr, SwCellFrame* pCellFrame ) +{ + if( rCellArr.end() == std::find( rCellArr.begin(), rCellArr.end(), pCellFrame ) ) + rCellArr.push_back( pCellFrame ); +} + +static void lcl_CollectCells( std::vector<SwCellFrame*> &rArr, const SwRect &rUnion, + SwTabFrame *pTab ) +{ + SwLayoutFrame *pCell = pTab->FirstCell(); + do + { + // If the Cell contains a CellFrame, we need to use it + // in order to get to the Cell + while ( !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "Frame is not a Cell" ); + if ( rUnion.Overlaps( pCell->getFrameArea() ) ) + ::InsertCell( rArr, static_cast<SwCellFrame*>(pCell) ); + + // Make sure the Cell is left (Areas) + SwLayoutFrame *pTmp = pCell; + do + { pTmp = pTmp->GetNextLayoutLeaf(); + } while ( pCell->IsAnLower( pTmp ) ); + pCell = pTmp; + } while( pCell && pTab->IsAnLower( pCell ) ); +} + +void SwDoc::SetTabBorders( const SwCursor& rCursor, const SfxItemSet& rSet ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( aUnions.empty() ) + return; + + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoAttrTable>(*pTableNd) ); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve( 255 ); + const SvxBoxItem* pSetBox; + const SvxBoxInfoItem *pSetBoxInfo; + + const SvxBorderLine* pLeft = nullptr; + const SvxBorderLine* pRight = nullptr; + const SvxBorderLine* pTop = nullptr; + const SvxBorderLine* pBottom = nullptr; + const SvxBorderLine* pHori = nullptr; + const SvxBorderLine* pVert = nullptr; + bool bHoriValid = true, bVertValid = true, + bTopValid = true, bBottomValid = true, + bLeftValid = true, bRightValid = true; + + // The Flags in the BoxInfo Item decide whether a BorderLine is valid! + pSetBoxInfo = rSet.GetItemIfSet( SID_ATTR_BORDER_INNER, false ); + if( pSetBoxInfo ) + { + pHori = pSetBoxInfo->GetHori(); + pVert = pSetBoxInfo->GetVert(); + + bHoriValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::HORI); + bVertValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::VERT); + + // Do we want to evaluate these? + bTopValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::TOP); + bBottomValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::BOTTOM); + bLeftValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::LEFT); + bRightValid = pSetBoxInfo->IsValid(SvxBoxInfoItemValidFlags::RIGHT); + } + + pSetBox = rSet.GetItemIfSet( RES_BOX, false ); + if( pSetBox ) + { + pLeft = pSetBox->GetLeft(); + pRight = pSetBox->GetRight(); + pTop = pSetBox->GetTop(); + pBottom = pSetBox->GetBottom(); + } + else + { + // Not set, thus not valid values + bTopValid = bBottomValid = bLeftValid = bRightValid = false; + pSetBox = nullptr; + } + + bool bFirst = true; + for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + SwTabFrame *pTab = pUnion->GetTable(); + const SwRect &rUnion = pUnion->GetUnion(); + const bool bLast = (i == aUnions.size() - 1); + + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve( 255 ); + ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); + + // All Cell Borders that match the UnionRect or extend it are + // Outer Borders. All others are Inner Borders. + + // New: The Outer Borders can, depending on whether it's a + // Start/Middle/Follow Table (for Selection via FollowTabs), + // also not be Outer Borders. + // Outer Borders are set on the left, right, at the top and at the bottom. + // Inner Borders are only set at the top and on the left. + for ( auto pCell : aCellArr ) + { + const bool bVert = pTab->IsVertical(); + const bool bRTL = pTab->IsRightToLeft(); + bool bTopOver, bLeftOver, bRightOver, bBottomOver; + if ( bVert ) + { + bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); + } + else + { + bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); + bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + } + + if ( bRTL ) + std::swap( bLeftOver, bRightOver ); + + // Do not set anything by default in HeadlineRepeats + if ( pTab->IsFollow() && + ( pTab->IsInHeadline( *pCell ) || + // Same holds for follow flow rows + pCell->IsInFollowFlowRow() ) ) + continue; + + SvxBoxItem aBox( pCell->GetFormat()->GetBox() ); + + sal_Int16 nType = 0; + + // Top Border + if( bTopValid ) + { + if ( bFirst && bTopOver ) + { + aBox.SetLine( pTop, SvxBoxItemLine::TOP ); + nType |= 0x0001; + } + else if ( bHoriValid ) + { + aBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + nType |= 0x0002; + } + } + + // Fix fdo#62470 correct the input for RTL table + if (bRTL) + { + if( bLeftOver && bRightOver) + { + if ( bLeftValid ) + { + aBox.SetLine( pLeft, SvxBoxItemLine::RIGHT ); + nType |= 0x0010; + } + if ( bRightValid ) + { + aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else + { + if ( bLeftValid ) + { + aBox.SetLine( bRightOver ? pLeft : nullptr, SvxBoxItemLine::RIGHT ); + if (bVertValid) + nType |= 0x0020; + else + nType |= 0x0010; + } + if ( bLeftOver ) + { + if ( bRightValid ) + { + aBox.SetLine( pRight, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else if ( bVertValid ) + { + aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); + nType |= 0x0008; + } + } + } + else + { + // Left Border + if ( bLeftOver ) + { + if( bLeftValid ) + { + aBox.SetLine( pLeft, SvxBoxItemLine::LEFT ); + nType |= 0x0004; + } + } + else if( bVertValid ) + { + aBox.SetLine( pVert, SvxBoxItemLine::LEFT ); + nType |= 0x0008; + } + + // Right Border + if( bRightValid ) + { + if ( bRightOver ) + { + aBox.SetLine( pRight, SvxBoxItemLine::RIGHT ); + nType |= 0x0010; + } + else if ( bVertValid ) + { + aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + nType |= 0x0020; + } + } + } + + // Bottom Border + if ( bLast && bBottomOver ) + { + if( bBottomValid ) + { + aBox.SetLine( pBottom, SvxBoxItemLine::BOTTOM ); + nType |= 0x0040; + } + } + else if( bHoriValid ) + { + aBox.SetLine( pHori, SvxBoxItemLine::BOTTOM ); + nType |= 0x0080; + } + + if( pSetBox ) + { + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + aBox.SetDistance( pSetBox->GetDistance( k ), k ); + } + + SwTableBox *pBox = const_cast<SwTableBox*>(pCell->GetTabBox()); + SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), nType ); + if ( nullptr != pNewFormat ) + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pBox->GetFrameFormat(); + SwFrameFormat *pNew = pBox->ClaimFrameFormat(); + pNew->SetFormatAttr( aBox ); + aFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, nType)); + } + } + + bFirst = false; + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->BordersChanged( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); + } + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); +} + +static void lcl_SetLineStyle( SvxBorderLine *pToSet, + const Color *pColor, const SvxBorderLine *pBorderLine) +{ + if ( pBorderLine ) + { + if ( !pColor ) + { + Color aTmp( pToSet->GetColor() ); + *pToSet = *pBorderLine; + pToSet->SetColor( aTmp ); + } + else + *pToSet = *pBorderLine; + } + if ( pColor ) + pToSet->SetColor( *pColor ); +} + +void SwDoc::SetTabLineStyle( const SwCursor& rCursor, + const Color* pColor, bool bSetLine, + const SvxBorderLine* pBorderLine ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( aUnions.empty() ) + return; + + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoAttrTable>(*pTableNd)); + } + + SvxBorderLine aDefaultBorder(pBorderLine ? *pBorderLine + : SvxBorderLine(pColor, SvxBorderLineWidth::VeryThin)); + if (pColor && pBorderLine) + aDefaultBorder.SetColor(*pColor); + + for( auto &rU : aUnions ) + { + SwSelUnion *pUnion = &rU; + SwTabFrame *pTab = pUnion->GetTable(); + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve( 255 ); + ::lcl_CollectCells( aCellArr, pUnion->GetUnion(), pTab ); + + for ( auto pCell : aCellArr ) + { + // Do not set anything by default in HeadlineRepeats + if ( pTab->IsFollow() && pTab->IsInHeadline( *pCell ) ) + continue; + + const_cast<SwTableBox*>(pCell->GetTabBox())->ClaimFrameFormat(); + SwFrameFormat *pFormat = pCell->GetFormat(); + std::unique_ptr<SvxBoxItem> aBox(pFormat->GetBox().Clone()); + + SvxBorderLine* pTop = aBox->GetTop(); + SvxBorderLine* pBot = aBox->GetBottom(); + SvxBorderLine* pLeft = aBox->GetLeft(); + SvxBorderLine* pRight = aBox->GetRight(); + + if ( !pBorderLine && bSetLine ) + { + aBox.reset(::GetDfltAttr(RES_BOX)->Clone()); + } + else if ((pColor || pBorderLine) && !pTop && !pBot && !pLeft && !pRight) + { + aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::TOP); + aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::BOTTOM); + aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::LEFT); + aBox->SetLine(&aDefaultBorder, SvxBoxItemLine::RIGHT); + } + else + { + if (pTop) + ::lcl_SetLineStyle(pTop, pColor, pBorderLine); + if (pBot) + ::lcl_SetLineStyle(pBot, pColor, pBorderLine); + if (pLeft) + ::lcl_SetLineStyle(pLeft, pColor, pBorderLine); + if (pRight) + ::lcl_SetLineStyle(pRight, pColor, pBorderLine); + } + pFormat->SetFormatAttr( *aBox ); + } + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->BordersChanged( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ) ); + } + ::ClearFEShellTabCols(*this, nullptr); + getIDocumentState().SetModified(); +} + +void SwDoc::GetTabBorders( const SwCursor& rCursor, SfxItemSet& rSet ) +{ + SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + if( aUnions.empty() ) + return; + + SvxBoxItem aSetBox ( rSet.Get(RES_BOX ) ); + SvxBoxInfoItem aSetBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); + + bool bTopSet = false, + bBottomSet = false, + bLeftSet = false, + bRightSet = false, + bHoriSet = false, + bVertSet = false, + bDistanceSet = false, + bRTLTab = false; + + aSetBoxInfo.ResetFlags(); + + for ( SwSelUnions::size_type i = 0; i < aUnions.size(); ++i ) + { + SwSelUnion *pUnion = &aUnions[i]; + const SwTabFrame *pTab = pUnion->GetTable(); + const SwRect &rUnion = pUnion->GetUnion(); + const bool bFirst = i == 0; + const bool bLast = (i == aUnions.size() - 1); + + std::vector<SwCellFrame*> aCellArr; + aCellArr.reserve(255); + ::lcl_CollectCells( aCellArr, rUnion, const_cast<SwTabFrame*>(pTab) ); + + for ( auto pCell : aCellArr ) + { + const bool bVert = pTab->IsVertical(); + const bool bRTL = bRTLTab = pTab->IsRightToLeft(); + bool bTopOver, bLeftOver, bRightOver, bBottomOver; + if ( bVert ) + { + bTopOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bLeftOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bRightOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + bBottomOver = pCell->getFrameArea().Left() <= rUnion.Left(); + } + else + { + bTopOver = pCell->getFrameArea().Top() <= rUnion.Top(); + bLeftOver = pCell->getFrameArea().Left() <= rUnion.Left(); + bRightOver = pCell->getFrameArea().Right() >= rUnion.Right(); + bBottomOver = pCell->getFrameArea().Bottom() >= rUnion.Bottom(); + } + + if ( bRTL ) + std::swap( bLeftOver, bRightOver ); + + const SwFrameFormat *pFormat = pCell->GetFormat(); + const SvxBoxItem &rBox = pFormat->GetBox(); + + // Top Border + if ( bFirst && bTopOver ) + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::TOP)) + { + if ( !bTopSet ) + { bTopSet = true; + aSetBox.SetLine( rBox.GetTop(), SvxBoxItemLine::TOP ); + } + else if ((aSetBox.GetTop() && rBox.GetTop() && + (*aSetBox.GetTop() != *rBox.GetTop())) || + ((!aSetBox.GetTop()) != (!rBox.GetTop()))) // != expression is true, if one and only one of the two pointers is !0 + { + aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::TOP, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + } + } + } + + // Left Border + if ( bLeftOver ) + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT)) + { + if ( !bLeftSet ) + { bLeftSet = true; + aSetBox.SetLine( rBox.GetLeft(), SvxBoxItemLine::LEFT ); + } + else if ((aSetBox.GetLeft() && rBox.GetLeft() && + (*aSetBox.GetLeft() != *rBox.GetLeft())) || + ((!aSetBox.GetLeft()) != (!rBox.GetLeft()))) + { + aSetBoxInfo.SetValid(SvxBoxInfoItemValidFlags::LEFT, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::LEFT ); + } + } + } + else + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::VERT)) + { + if ( !bVertSet ) + { bVertSet = true; + aSetBoxInfo.SetLine( rBox.GetLeft(), SvxBoxInfoItemLine::VERT ); + } + else if ((aSetBoxInfo.GetVert() && rBox.GetLeft() && + (*aSetBoxInfo.GetVert() != *rBox.GetLeft())) || + ((!aSetBoxInfo.GetVert()) != (!rBox.GetLeft()))) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::VERT, false ); + aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); + } + } + } + + // Right Border + if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) && bRightOver ) + { + if ( !bRightSet ) + { bRightSet = true; + aSetBox.SetLine( rBox.GetRight(), SvxBoxItemLine::RIGHT ); + } + else if ((aSetBox.GetRight() && rBox.GetRight() && + (*aSetBox.GetRight() != *rBox.GetRight())) || + (!aSetBox.GetRight() != !rBox.GetRight())) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + } + + // Bottom Border + if ( bLast && bBottomOver ) + { + if ( aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::BOTTOM) ) + { + if ( !bBottomSet ) + { bBottomSet = true; + aSetBox.SetLine( rBox.GetBottom(), SvxBoxItemLine::BOTTOM ); + } + else if ((aSetBox.GetBottom() && rBox.GetBottom() && + (*aSetBox.GetBottom() != *rBox.GetBottom())) || + (!aSetBox.GetBottom() != !rBox.GetBottom())) + { aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, false ); + aSetBox.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + } + } + } + // In all Lines, except for the last one, the horizontal Line + // is taken from the Bottom Line. + else + { + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::HORI)) + { + if ( !bHoriSet ) + { bHoriSet = true; + aSetBoxInfo.SetLine( rBox.GetBottom(), SvxBoxInfoItemLine::HORI ); + } + else if ((aSetBoxInfo.GetHori() && rBox.GetBottom() && + (*aSetBoxInfo.GetHori() != *rBox.GetBottom())) || + ((!aSetBoxInfo.GetHori()) != (!rBox.GetBottom()))) + { + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::HORI, false ); + aSetBoxInfo.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); + } + } + } + + // Distance to text + if (aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::DISTANCE)) + { + if( !bDistanceSet ) // Set on first iteration + { + bDistanceSet = true; + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + aSetBox.SetDistance( rBox.GetDistance( k ), k ); + } + else + { + for( SvxBoxItemLine k : o3tl::enumrange<SvxBoxItemLine>() ) + if( aSetBox.GetDistance( k ) != + rBox.GetDistance( k ) ) + { + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::DISTANCE, false ); + aSetBox.SetAllDistances(0); + break; + } + } + } + } + } + + // fdo#62470 fix the reading for table format. + if ( bRTLTab ) + { + SvxBoxItem aTempBox ( rSet.Get(RES_BOX ) ); + SvxBoxInfoItem aTempBoxInfo( rSet.Get(SID_ATTR_BORDER_INNER) ); + + aTempBox.SetLine( aSetBox.GetRight(), SvxBoxItemLine::RIGHT); + aSetBox.SetLine( aSetBox.GetLeft(), SvxBoxItemLine::RIGHT); + aSetBox.SetLine( aTempBox.GetRight(), SvxBoxItemLine::LEFT); + + aTempBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT, aSetBoxInfo.IsValid(SvxBoxInfoItemValidFlags::RIGHT) ); + aSetBoxInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT, aTempBoxInfo.IsValid(SvxBoxInfoItemValidFlags::LEFT) ); + } + + rSet.Put( aSetBox ); + rSet.Put( aSetBoxInfo ); +} + +void SwDoc::SetBoxAttr( const SwCursor& rCursor, const SfxPoolItem &rNew ) +{ + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( !(pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes, true )) ) + return; + + SwTable& rTable = pTableNd->GetTable(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoAttrTable>(*pTableNd) ); + } + + std::vector<std::unique_ptr<SwTableFormatCmp>> aFormatCmp; + aFormatCmp.reserve(std::max<size_t>(255, aBoxes.size())); + for (size_t i = 0; i < aBoxes.size(); ++i) + { + SwTableBox *pBox = aBoxes[i]; + + SwFrameFormat *pNewFormat = SwTableFormatCmp::FindNewFormat( aFormatCmp, pBox->GetFrameFormat(), 0 ); + if ( nullptr != pNewFormat ) + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pNewFormat) ); + else + { + SwFrameFormat *pOld = pBox->GetFrameFormat(); + SwFrameFormat *pNew = pBox->ClaimFrameFormat(); + pNew->SetFormatAttr( rNew ); + aFormatCmp.push_back(std::make_unique<SwTableFormatCmp>(pOld, pNew, 0)); + } + + pBox->SetDirectFormatting(true); + } + + SwHTMLTableLayout *pTableLayout = rTable.GetHTMLTableLayout(); + if( pTableLayout ) + { + SwContentFrame* pFrame = rCursor.GetPointContentNode()->getLayoutFrame( rCursor.GetPointContentNode()->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ); + SwTabFrame* pTabFrame = pFrame->ImplFindTabFrame(); + + pTableLayout->Resize( + pTableLayout->GetBrowseWidthByTabFrame( *pTabFrame ), true ); + } + getIDocumentState().SetModified(); +} + +bool SwDoc::GetBoxAttr( const SwCursor& rCursor, std::unique_ptr<SfxPoolItem>& rToFill ) +{ + // tdf#144843 calling GetBoxAttr *requires* object + assert(rToFill && "requires object here"); + bool bRet = false; + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( pTableNd && lcl_GetBoxSel( rCursor, aBoxes )) + { + bRet = true; + bool bOneFound = false; + const sal_uInt16 nWhich = rToFill->Which(); + for (size_t i = 0; i < aBoxes.size(); ++i) + { + switch ( nWhich ) + { + case RES_BACKGROUND: + { + std::unique_ptr<SvxBrushItem> xBack = + aBoxes[i]->GetFrameFormat()->makeBackgroundBrushItem(); + if( !bOneFound ) + { + rToFill = std::move(xBack); + bOneFound = true; + } + else if( *rToFill != *xBack ) + bRet = false; + } + break; + + case RES_FRAMEDIR: + { + const SvxFrameDirectionItem& rDir = + aBoxes[i]->GetFrameFormat()->GetFrameDir(); + if( !bOneFound ) + { + rToFill.reset(rDir.Clone()); + bOneFound = true; + } + else if( rToFill && *rToFill != rDir ) + bRet = false; + } + break; + case RES_VERT_ORIENT: + { + const SwFormatVertOrient& rOrient = + aBoxes[i]->GetFrameFormat()->GetVertOrient(); + if( !bOneFound ) + { + rToFill.reset(rOrient.Clone()); + bOneFound = true; + } + else if( rToFill && *rToFill != rOrient ) + bRet = false; + } + break; + } + + if ( !bRet ) + break; + } + } + return bRet; +} + +void SwDoc::SetBoxAlign( const SwCursor& rCursor, sal_uInt16 nAlign ) +{ + OSL_ENSURE( nAlign == text::VertOrientation::NONE || + nAlign == text::VertOrientation::CENTER || + nAlign == text::VertOrientation::BOTTOM, "Wrong alignment" ); + SwFormatVertOrient aVertOri( 0, nAlign ); + SetBoxAttr( rCursor, aVertOri ); +} + +sal_uInt16 SwDoc::GetBoxAlign( const SwCursor& rCursor ) +{ + sal_uInt16 nAlign = USHRT_MAX; + SwTableNode* pTableNd = rCursor.GetPoint()->GetNode().FindTableNode(); + SwSelBoxes aBoxes; + if( pTableNd && ::lcl_GetBoxSel( rCursor, aBoxes )) + { + for (size_t i = 0; i < aBoxes.size(); ++i) + { + const SwFormatVertOrient &rOri = + aBoxes[i]->GetFrameFormat()->GetVertOrient(); + if( USHRT_MAX == nAlign ) + nAlign = o3tl::narrowing<sal_uInt16>(rOri.GetVertOrient()); + else if( rOri.GetVertOrient() != nAlign ) + { + nAlign = USHRT_MAX; + break; + } + } + } + return nAlign; +} + +static sal_uInt16 lcl_CalcCellFit( const SwLayoutFrame *pCell ) +{ + SwTwips nRet = 0; + const SwFrame *pFrame = pCell->Lower(); // The whole Line + SwRectFnSet aRectFnSet(pCell); + while ( pFrame ) + { + const SwTwips nAdd = aRectFnSet.GetWidth(pFrame->getFrameArea()) - + aRectFnSet.GetWidth(pFrame->getFramePrintArea()); + + // pFrame does not necessarily have to be a SwTextFrame! + const SwTwips nCalcFitToContent = pFrame->IsTextFrame() ? + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pFrame))->CalcFitToContent() : + aRectFnSet.GetWidth(pFrame->getFramePrintArea()); + + nRet = std::max( nRet, nCalcFitToContent + nAdd ); + pFrame = pFrame->GetNext(); + } + // Surrounding border as well as left and Right Border also need to be respected + nRet += aRectFnSet.GetWidth(pCell->getFrameArea()) - + aRectFnSet.GetWidth(pCell->getFramePrintArea()); + + // To compensate for the accuracy of calculation later on in SwTable::SetTabCols + // we keep adding up a little. + nRet += COLFUZZY; + return o3tl::narrowing<sal_uInt16>(std::max( SwTwips(MINLAY), nRet )); +} + +/* The Line is within the Selection but not outlined by the TabCols. + * + * That means that the Line has been "split" by other Cells due to the + * two-dimensional representation used. Thus, we have to distribute the cell's + * default or minimum value amongst the Cell it has been split by. + * + * First, we collect the Columns (not the Column separators) which overlap + * with the Cell. We then distribute the desired value according to the + * amount of overlapping amongst the Cells. + * + * A Cell's default value stays the same if it already has a larger value than + * the desired one. It's overwritten if it's smaller. + */ +static void lcl_CalcSubColValues( std::vector<sal_uInt16> &rToFill, const SwTabCols &rCols, + const SwLayoutFrame *pCell, const SwLayoutFrame *pTab, + bool bWishValues ) +{ + const sal_uInt16 nWish = bWishValues ? + ::lcl_CalcCellFit( pCell ) : + MINLAY + sal_uInt16(pCell->getFrameArea().Width() - pCell->getFramePrintArea().Width()); + + SwRectFnSet aRectFnSet(pTab); + + for ( size_t i = 0 ; i <= rCols.Count(); ++i ) + { + tools::Long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; + tools::Long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; + nColLeft += rCols.GetLeftMin(); + nColRight += rCols.GetLeftMin(); + + // Adapt values to the proportions of the Table (Follows) + if ( rCols.GetLeftMin() != aRectFnSet.GetLeft(pTab->getFrameArea()) ) + { + const tools::Long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); + nColLeft += nDiff; + nColRight += nDiff; + } + const tools::Long nCellLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); + const tools::Long nCellRight = aRectFnSet.GetRight(pCell->getFrameArea()); + + // Calculate overlapping value + tools::Long nWidth = 0; + if ( nColLeft <= nCellLeft && nColRight >= (nCellLeft+COLFUZZY) ) + nWidth = nColRight - nCellLeft; + else if ( nColLeft <= (nCellRight-COLFUZZY) && nColRight >= nCellRight ) + nWidth = nCellRight - nColLeft; + else if ( nColLeft >= nCellLeft && nColRight <= nCellRight ) + nWidth = nColRight - nColLeft; + if ( nWidth && pCell->getFrameArea().Width() ) + { + tools::Long nTmp = nWidth * nWish / pCell->getFrameArea().Width(); + if ( o3tl::make_unsigned(nTmp) > rToFill[i] ) + rToFill[i] = sal_uInt16(nTmp); + } + } +} + +/** + * Retrieves new values to set the TabCols. + * + * We do not iterate over the TabCols' entries, but over the gaps that describe Cells. + * We set TabCol entries for which we did not calculate Cells to 0. + * + * @param bWishValues == true: We calculate the desired value of all affected + * Cells for the current Selection/current Cell. + * If more Cells are within a Column, the highest + * desired value is returned. + * We set TabCol entries for which we did not calculate + * Cells to 0. + * + * @param bWishValues == false: The Selection is expanded vertically. + * We calculate the minimum value for every + * Column in the TabCols that intersects with the + * Selection. + */ +static void lcl_CalcColValues( std::vector<sal_uInt16> &rToFill, const SwTabCols &rCols, + const SwLayoutFrame *pStart, const SwLayoutFrame *pEnd, + bool bWishValues ) +{ + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd, + bWishValues ? SwTableSearchType::NONE : SwTableSearchType::Col ); + + for ( auto &rU : aUnions ) + { + SwSelUnion *pSelUnion = &rU; + const SwTabFrame *pTab = pSelUnion->GetTable(); + const SwRect &rUnion = pSelUnion->GetUnion(); + + SwRectFnSet aRectFnSet(pTab); + bool bRTL = pTab->IsRightToLeft(); + + const SwLayoutFrame *pCell = pTab->FirstCell(); + if (!pCell) + continue; + do + { + if ( pCell->IsCellFrame() && pCell->FindTabFrame() == pTab && ::IsFrameInTableSel( rUnion, pCell ) ) + { + const tools::Long nCLeft = aRectFnSet.GetLeft(pCell->getFrameArea()); + const tools::Long nCRight = aRectFnSet.GetRight(pCell->getFrameArea()); + + bool bNotInCols = true; + + for ( size_t i = 0; i <= rCols.Count(); ++i ) + { + sal_uInt16 nFit = rToFill[i]; + tools::Long nColLeft = i == 0 ? rCols.GetLeft() : rCols[i-1]; + tools::Long nColRight = i == rCols.Count() ? rCols.GetRight() : rCols[i]; + + if ( bRTL ) + { + tools::Long nTmpRight = nColRight; + nColRight = rCols.GetRight() - nColLeft; + nColLeft = rCols.GetRight() - nTmpRight; + } + + nColLeft += rCols.GetLeftMin(); + nColRight += rCols.GetLeftMin(); + + // Adapt values to the proportions of the Table (Follows) + tools::Long nLeftA = nColLeft; + tools::Long nRightA = nColRight; + if ( rCols.GetLeftMin() != sal_uInt16(aRectFnSet.GetLeft(pTab->getFrameArea())) ) + { + const tools::Long nDiff = aRectFnSet.GetLeft(pTab->getFrameArea()) - rCols.GetLeftMin(); + nLeftA += nDiff; + nRightA += nDiff; + } + + // We don't want to take a too close look + if ( ::IsSame(nCLeft, nLeftA) && ::IsSame(nCRight, nRightA)) + { + bNotInCols = false; + if ( bWishValues ) + { + const sal_uInt16 nWish = ::lcl_CalcCellFit( pCell ); + if ( nWish > nFit ) + nFit = nWish; + } + else + { const sal_uInt16 nMin = MINLAY + sal_uInt16(pCell->getFrameArea().Width() - + pCell->getFramePrintArea().Width()); + if ( !nFit || nMin < nFit ) + nFit = nMin; + } + if ( rToFill[i] < nFit ) + rToFill[i] = nFit; + } + } + if ( bNotInCols ) + ::lcl_CalcSubColValues( rToFill, rCols, pCell, pTab, bWishValues ); + } + do { + pCell = pCell->GetNextLayoutLeaf(); + } while( pCell && pCell->getFrameArea().Width() == 0 ); + } while ( pCell && pTab->IsAnLower( pCell ) ); + } +} + +void SwDoc::AdjustCellWidth( const SwCursor& rCursor, + const bool bBalance, + const bool bNoShrink ) +{ + // Check whether the current Cursor has it's Point/Mark in a Table + SwContentNode* pCntNd = rCursor.GetPoint()->GetNode().GetContentNode(); + SwTableNode* pTableNd = pCntNd ? pCntNd->FindTableNode() : nullptr; + if( !pTableNd ) + return ; + + SwLayoutFrame *pStart, *pEnd; + ::lcl_GetStartEndCell( rCursor, pStart, pEnd ); + + // Collect TabCols; we reset the Table with them + SwFrame* pBoxFrame = pStart; + while( pBoxFrame && !pBoxFrame->IsCellFrame() ) + pBoxFrame = pBoxFrame->GetUpper(); + + if ( !pBoxFrame ) + return; // Robust + + SwTabCols aTabCols; + GetTabCols( aTabCols, static_cast<SwCellFrame*>(pBoxFrame) ); + + if ( ! aTabCols.Count() ) + return; + + std::vector<sal_uInt16> aWish(aTabCols.Count() + 1); + std::vector<sal_uInt16> aMins(aTabCols.Count() + 1); + + ::lcl_CalcColValues( aWish, aTabCols, pStart, pEnd, /*bWishValues=*/true ); + + // It's more robust if we calculate the minimum values for the whole Table + const SwTabFrame *pTab = pStart->ImplFindTabFrame(); + pStart = const_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame const *>(pTab->FirstCell())); + pEnd = const_cast<SwLayoutFrame*>(pTab->FindLastContentOrTable()->GetUpper()); + while( !pEnd->IsCellFrame() ) + pEnd = pEnd->GetUpper(); + ::lcl_CalcColValues( aMins, aTabCols, pStart, pEnd, /*bWishValues=*/false ); + + sal_uInt16 nSelectedWidth = 0, nCols = 0; + float fTotalWish = 0; + if ( bBalance || bNoShrink ) + { + // Find the combined size of the selected columns + for ( size_t i = 0; i <= aTabCols.Count(); ++i ) + { + if ( aWish[i] ) + { + if ( i == 0 ) + nSelectedWidth += aTabCols[i] - aTabCols.GetLeft(); + else if ( i == aTabCols.Count() ) + nSelectedWidth += aTabCols.GetRight() - aTabCols[i-1]; + else + nSelectedWidth += aTabCols[i] - aTabCols[i-1]; + ++nCols; + } + fTotalWish += aWish[i]; + } + // bBalance: Distribute the width evenly + if (bBalance) + { + assert(nCols); + const sal_uInt16 nEqualWidth = nCols ? nSelectedWidth / nCols : 0; + for (sal_uInt16 & rn : aWish) + if (rn) + rn = nEqualWidth; + } + } + + const tools::Long nOldRight = aTabCols.GetRight(); + + // In order to make the implementation easier, but still use the available + // space properly, we do this twice. + + // The problem: The first column is getting wider, the others get slimmer + // only afterwards. + // The first column's desired width would be discarded as it would cause + // the Table's width to exceed the maximum width. + const tools::Long nMaxRight = std::max(aTabCols.GetRightMax(), nOldRight); + const sal_uInt16 nEqualWidth = (nMaxRight - aTabCols.GetLeft()) / (aTabCols.Count() + 1); + const sal_Int16 nTablePadding = nSelectedWidth - fTotalWish; + for ( int k = 0; k < 2; ++k ) + { + for ( size_t i = 0; i <= aTabCols.Count(); ++i ) + { + // bNoShrink: distribute excess space proportionately on pass 2. + if ( bNoShrink && k && nTablePadding > 0 && fTotalWish > 0 ) + aWish[i] += round( aWish[i] / fTotalWish * nTablePadding ); + + // First pass is primarily a shrink pass. Give all columns a chance + // to grow by requesting the maximum width as "balanced". + // Second pass is a first-come, first-served chance to max out. + int nDiff = k ? aWish[i] : std::min(aWish[i], nEqualWidth); + if ( nDiff ) + { + int nMin = aMins[i]; + if ( nMin > nDiff ) + nDiff = nMin; + + if ( i == 0 ) + { + if( aTabCols.Count() ) + nDiff -= aTabCols[0] - aTabCols.GetLeft(); + else + nDiff -= aTabCols.GetRight() - aTabCols.GetLeft(); + } + else if ( i == aTabCols.Count() ) + nDiff -= aTabCols.GetRight() - aTabCols[i-1]; + else + nDiff -= aTabCols[i] - aTabCols[i-1]; + + tools::Long nTabRight = aTabCols.GetRight() + nDiff; + + // If the Table would become (or is already) too wide, + // restrict the column growth to the allowed maximum. + if (!bBalance && nTabRight > nMaxRight) + { + const tools::Long nTmpD = nTabRight - nMaxRight; + nDiff -= nTmpD; + nTabRight -= nTmpD; + } + + // all the remaining columns need to be shifted by the same amount + for ( size_t i2 = i; i2 < aTabCols.Count(); ++i2 ) + aTabCols[i2] += nDiff; + aTabCols.SetRight( nTabRight ); + } + } + } + + const tools::Long nNewRight = aTabCols.GetRight(); + + SwFrameFormat *pFormat = pTableNd->GetTable().GetFrameFormat(); + const sal_Int16 nOriHori = pFormat->GetHoriOrient().GetHoriOrient(); + + // We can leave the "real" work to the SwTable now + SetTabCols( aTabCols, false, static_cast<SwCellFrame*>(pBoxFrame) ); + + // Alignment might have been changed in SetTabCols; restore old value + const SwFormatHoriOrient &rHori = pFormat->GetHoriOrient(); + SwFormatHoriOrient aHori( rHori ); + if ( aHori.GetHoriOrient() != nOriHori ) + { + aHori.SetHoriOrient( nOriHori ); + pFormat->SetFormatAttr( aHori ); + } + + // We switch to left-adjusted for automatic width + // We adjust the right border for Border attributes + if( !bBalance && nNewRight < nOldRight ) + { + if( aHori.GetHoriOrient() == text::HoriOrientation::FULL ) + { + aHori.SetHoriOrient( text::HoriOrientation::LEFT ); + pFormat->SetFormatAttr( aHori ); + } + } + + getIDocumentState().SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/node.cxx b/sw/source/core/docnode/node.cxx new file mode 100644 index 0000000000..b2419bd132 --- /dev/null +++ b/sw/source/core/docnode/node.cxx @@ -0,0 +1,2187 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_wasm_strip.h> +#include <hintids.hxx> +#include <editeng/protitem.hxx> +#include <osl/diagnose.h> +#include <tools/gen.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <txtftn.hxx> +#include <ftnfrm.hxx> +#include <doc.hxx> +#include <node.hxx> +#include <ndindex.hxx> +#include <numrule.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <section.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <viewsh.hxx> +#include <paratr.hxx> +#include <ftnidx.hxx> +#include <fmtftn.hxx> +#include <fmthdft.hxx> +#include <frmatr.hxx> +#include <fmtautofmt.hxx> +#include <frmtool.hxx> +#include <pagefrm.hxx> +#include <node2lay.hxx> +#include <pagedesc.hxx> +#include <fmtpdsc.hxx> +#include <breakit.hxx> +#include <SwStyleNameMapper.hxx> +#include <scriptinfo.hxx> +#include <rootfrm.hxx> +#include <istyleaccess.hxx> +#include <IDocumentListItems.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <calbck.hxx> +#include <ndole.hxx> +#include <memory> +#include <swcrsr.hxx> +#include <hints.hxx> +#include <frameformats.hxx> +#include <OnlineAccessibilityCheck.hxx> +#ifdef DBG_UTIL +#include <sal/backtrace.hxx> +#endif + +using namespace ::com::sun::star::i18n; + +namespace sw +{ + +void AccessibilityCheckStatus::reset() +{ + pCollection.reset(); +} + +} + +/* + * Some local helper functions for the attribute set handle of a content node. + * Since the attribute set of a content node may not be modified directly, + * we always have to create a new SwAttrSet, do the modifications, and get + * a new handle from the style access + */ + +namespace AttrSetHandleHelper +{ + +static void GetNewAutoStyle( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, + SwAttrSet const & rNewAttrSet ) +{ + if( rNode.GetModifyAtAttr() ) + const_cast<SwAttrSet*>(rpAttrSet.get())->SetModifyAtAttr( nullptr ); + IStyleAccess& rSA = rpAttrSet->GetPool()->GetDoc()->GetIStyleAccess(); + rpAttrSet = rSA.getAutomaticStyle( rNewAttrSet, rNode.IsTextNode() ? + IStyleAccess::AUTO_STYLE_PARA : + IStyleAccess::AUTO_STYLE_NOTXT ); + const bool bSetModifyAtAttr = const_cast<SwAttrSet*>(rpAttrSet.get())->SetModifyAtAttr( &rNode ); + rNode.SetModifyAtAttr( bSetModifyAtAttr ); +} + +static void SetParent( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, + const SwFormat* pParentFormat, + const SwFormat* pConditionalFormat ) +{ + OSL_ENSURE( rpAttrSet, "no SwAttrSet" ); + OSL_ENSURE( pParentFormat || !pConditionalFormat, "ConditionalFormat without ParentFormat?" ); + + const SwAttrSet* pParentSet = pParentFormat ? &pParentFormat->GetAttrSet() : nullptr; + + if ( pParentSet == rpAttrSet->GetParent() ) + return; + + SwAttrSet aNewSet( *rpAttrSet ); + aNewSet.SetParent( pParentSet ); + aNewSet.ClearItem( RES_FRMATR_STYLE_NAME ); + aNewSet.ClearItem( RES_FRMATR_CONDITIONAL_STYLE_NAME ); + + if ( pParentFormat ) + { + OUString sVal; + SwStyleNameMapper::FillProgName( pParentFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + const SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal ); + aNewSet.Put( aAnyFormatColl ); + + if ( pConditionalFormat != pParentFormat ) + SwStyleNameMapper::FillProgName( pConditionalFormat->GetName(), sVal, SwGetPoolIdFromName::TxtColl ); + + const SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal ); + aNewSet.Put( aFormatColl ); + } + + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); +} + +static const SfxPoolItem* Put( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, + const SfxPoolItem& rAttr ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + const SfxPoolItem* pRet = aNewSet.Put( rAttr ); + if ( pRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return pRet; +} + +static bool Put( std::shared_ptr<const SwAttrSet>& rpAttrSet, const SwContentNode& rNode, + const SfxItemSet& rSet ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + + // #i76273# Robust + std::optional<SfxItemSetFixed<RES_FRMATR_STYLE_NAME, RES_FRMATR_CONDITIONAL_STYLE_NAME>> pStyleNames; + if ( SfxItemState::SET == rSet.GetItemState( RES_FRMATR_STYLE_NAME, false ) ) + { + pStyleNames.emplace( *aNewSet.GetPool() ); + pStyleNames->Put( aNewSet ); + } + + const bool bRet = aNewSet.Put( rSet ); + + // #i76273# Robust + if ( pStyleNames ) + { + aNewSet.Put( *pStyleNames ); + } + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static bool Put_BC( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, const SfxPoolItem& rAttr, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + + // for a correct broadcast, we need to do a SetModifyAtAttr with the items + // from aNewSet. The 'regular' SetModifyAtAttr is done in GetNewAutoStyle + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + + const bool bRet = aNewSet.Put_BC( rAttr, pOld, pNew ); + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static bool Put_BC( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, const SfxItemSet& rSet, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + + // #i76273# Robust + std::optional<SfxItemSetFixed<RES_FRMATR_STYLE_NAME, RES_FRMATR_CONDITIONAL_STYLE_NAME>> pStyleNames; + if ( SfxItemState::SET == rSet.GetItemState( RES_FRMATR_STYLE_NAME, false ) ) + { + pStyleNames.emplace( *aNewSet.GetPool() ); + pStyleNames->Put( aNewSet ); + } + + // for a correct broadcast, we need to do a SetModifyAtAttr with the items + // from aNewSet. The 'regular' SetModifyAtAttr is done in GetNewAutoStyle + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + + const bool bRet = aNewSet.Put_BC( rSet, pOld, pNew ); + + // #i76273# Robust + if ( pStyleNames ) + { + aNewSet.Put( *pStyleNames ); + } + + if ( bRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + + return bRet; +} + +static sal_uInt16 ClearItem_BC( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, sal_uInt16 nWhich, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + const sal_uInt16 nRet = aNewSet.ClearItem_BC( nWhich, pOld, pNew ); + if ( nRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return nRet; +} + +static sal_uInt16 ClearItem_BC( std::shared_ptr<const SwAttrSet>& rpAttrSet, + const SwContentNode& rNode, + sal_uInt16 nWhich1, sal_uInt16 nWhich2, + SwAttrSet* pOld, SwAttrSet* pNew ) +{ + SwAttrSet aNewSet( *rpAttrSet ); + if( rNode.GetModifyAtAttr() ) + aNewSet.SetModifyAtAttr( &rNode ); + const sal_uInt16 nRet = aNewSet.ClearItem_BC( nWhich1, nWhich2, pOld, pNew ); + if ( nRet ) + GetNewAutoStyle( rpAttrSet, rNode, aNewSet ); + return nRet; +} + +} + +/** Returns the section level at the position given by aIndex. + * + * We use the following logic: + * S = Start, E = End, C = ContentNode + * Level 0 = E + * 1 = S E + * 2 = SC + * + * All EndNodes of the BaseSection have level 0 + * All StartNodes of the BaseSection have level 1 + */ +sal_uInt16 SwNode::GetSectionLevel() const +{ + // EndNode of a BaseSection? They are always 0! + if( IsEndNode() && SwNodeOffset(0) == m_pStartOfSection->StartOfSectionIndex() ) + return 0; + + sal_uInt16 nLevel; + const SwNode* pNode = IsStartNode() ? this : m_pStartOfSection; + for( nLevel = 1; SwNodeOffset(0) != pNode->StartOfSectionIndex(); ++nLevel ) + pNode = pNode->m_pStartOfSection; + return IsEndNode() ? nLevel-1 : nLevel; +} + +#ifdef DBG_UTIL +tools::Long SwNode::s_nSerial = 0; +#endif + +/// only used by SwContentNodeTmp in SwTextNode::Update +SwNode::SwNode() + : m_nNodeType( SwNodeType::Start ) + , m_nAFormatNumLvl( 0 ) + , m_bIgnoreDontExpand( false) + , m_eMerge(Merge::None) +#ifdef DBG_UTIL + , m_nSerial( s_nSerial++) +#endif + , m_pStartOfSection( nullptr ) +{} + +SwNode::SwNode( const SwNode& rWhere, const SwNodeType nNdType ) + : m_nNodeType( nNdType ) + , m_nAFormatNumLvl( 0 ) + , m_bIgnoreDontExpand( false) + , m_eMerge(Merge::None) +#ifdef DBG_UTIL + , m_nSerial( s_nSerial++) +#endif + , m_pStartOfSection( nullptr ) +{ + SwNodeOffset nWhereOffset = rWhere.GetIndex(); + if( !nWhereOffset ) + return; + + SwNodes& rNodes = const_cast<SwNodes&> (rWhere.GetNodes()); + SwNode* pNd = rNodes[ nWhereOffset -1 ]; + rNodes.InsertNode( this, nWhereOffset ); + m_pStartOfSection = pNd->GetStartNode(); + if( nullptr == m_pStartOfSection ) + { + m_pStartOfSection = pNd->m_pStartOfSection; + if( pNd->GetEndNode() ) // Skip EndNode ? Section + { + pNd = m_pStartOfSection; + m_pStartOfSection = pNd->m_pStartOfSection; + } + } +} + +/** Inserts a node into the rNodes array at the rWhere position + * + * @param rNodes the variable array in that the node will be inserted + * @param nPos position within the array where the node will be inserted + * @param nNdType the type of node to insert + */ +SwNode::SwNode( SwNodes& rNodes, SwNodeOffset nPos, const SwNodeType nNdType ) + : m_nNodeType( nNdType ) + , m_nAFormatNumLvl( 0 ) + , m_bIgnoreDontExpand( false) + , m_eMerge(Merge::None) +#ifdef DBG_UTIL + , m_nSerial( s_nSerial++) +#endif + , m_pStartOfSection( nullptr ) +{ + if( !nPos ) + return; + + SwNode* pNd = rNodes[ nPos - 1 ]; + rNodes.InsertNode( this, nPos ); + m_pStartOfSection = pNd->GetStartNode(); + if( nullptr == m_pStartOfSection ) + { + m_pStartOfSection = pNd->m_pStartOfSection; + if( pNd->GetEndNode() ) // Skip EndNode ? Section! + { + pNd = m_pStartOfSection; + m_pStartOfSection = pNd->m_pStartOfSection; + } + } +} + +SwNode::~SwNode() +{ + assert(m_aAnchoredFlys.empty() || GetDoc().IsInDtor()); // must all be deleted + InvalidateInSwCache(RES_OBJECTDYING); + assert(!IsInCache()); +} + +/// Find the TableNode in which it is located. +/// If we're not in a table: return 0 +SwTableNode* SwNode::FindTableNode() +{ + if( IsTableNode() ) + return GetTableNode(); + SwStartNode* pTmp = m_pStartOfSection; + while( !pTmp->IsTableNode() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return pTmp->GetTableNode(); +} + +/// Is the node located in the visible area of the Shell? +bool SwNode::IsInVisibleArea( SwViewShell const * pSh ) const +{ + bool bRet = false; + const SwContentNode* pNd; + + if( SwNodeType::Start & m_nNodeType ) + { + SwNodeIndex aIdx( *this ); + pNd = GetNodes().GoNext( &aIdx ); + } + else if( SwNodeType::End & m_nNodeType ) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + pNd = SwNodes::GoPrevious( &aIdx ); + } + else + pNd = GetContentNode(); + + if( !pSh ) + // Get the Shell from the Doc + pSh = GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( pSh ) + { + const SwFrame* pFrame; + if (pNd && nullptr != (pFrame = pNd->getLayoutFrame(pSh->GetLayout(), nullptr, nullptr))) + { + + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if( !pFrame->isFrameAreaDefinitionValid() ) + { + do + { + pFrame = pFrame->FindPrev(); + } + while ( pFrame && !pFrame->isFrameAreaDefinitionValid() ); + } + + if( !pFrame || pSh->VisArea().Overlaps( pFrame->getFrameArea() ) ) + bRet = true; + } + } + + return bRet; +} + +bool SwNode::IsInProtectSect() const +{ + const SwNode* pNd = SwNodeType::Section == m_nNodeType ? m_pStartOfSection : this; + const SwSectionNode* pSectNd = pNd->FindSectionNode(); + return pSectNd && pSectNd->GetSection().IsProtectFlag(); +} + +/// Does the node contain anything protected? +/// I.e.: Area/Frame/Table rows/... including the Anchor for +/// Frames/Footnotes/... +bool SwNode::IsProtect() const +{ + const SwNode* pNd = SwNodeType::Section == m_nNodeType ? m_pStartOfSection : this; + const SwStartNode* pSttNd = pNd->FindSectionNode(); + if( pSttNd && static_cast<const SwSectionNode*>(pSttNd)->GetSection().IsProtectFlag() ) + return true; + + pSttNd = FindTableBoxStartNode(); + if( nullptr != pSttNd ) + { + SwContentFrame* pCFrame; + if( IsContentNode() && nullptr != (pCFrame = static_cast<const SwContentNode*>(this)->getLayoutFrame( GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() ) )) + return pCFrame->IsProtected(); + + const SwTableBox* pBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + //Robust #149568 + if( pBox && pBox->GetFrameFormat()->GetProtect().IsContentProtected() ) + return true; + } + + SwFrameFormat* pFlyFormat = GetFlyFormat(); + if( pFlyFormat ) + { + if (pFlyFormat->GetProtect().IsContentProtected()) + return true; + const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor(); + const SwNode* pAnchorNode = rAnchor.GetAnchorNode(); + if (!pAnchorNode) + return false; + return pAnchorNode != this && pAnchorNode->IsProtect(); + } + + pSttNd = FindFootnoteStartNode(); + if( nullptr != pSttNd ) + { + const SwTextFootnote* pTFootnote = GetDoc().GetFootnoteIdxs().SeekEntry( + *pSttNd ); + if( pTFootnote ) + return pTFootnote->GetTextNode().IsProtect(); + } + + return false; +} + +/// Find the PageDesc that is used to format this node. If the Layout is available, +/// we search through that. Else we can only do it the hard way by searching onwards through the nodes. +const SwPageDesc* SwNode::FindPageDesc( SwNodeOffset* pPgDescNdIdx ) const +{ + if ( !GetNodes().IsDocNodes() ) + { + return nullptr; + } + + const SwPageDesc* pPgDesc = nullptr; + + const SwContentNode* pNode; + if( SwNodeType::Start & m_nNodeType ) + { + SwNodeIndex aIdx( *this ); + pNode = GetNodes().GoNext( &aIdx ); + } + else if( SwNodeType::End & m_nNodeType ) + { + SwNodeIndex aIdx( *EndOfSectionNode() ); + pNode = SwNodes::GoPrevious( &aIdx ); + } + else + { + pNode = GetContentNode(); + if( pNode ) + pPgDesc = pNode->GetAttr( RES_PAGEDESC ).GetPageDesc(); + } + + // Are we going through the layout? + if( !pPgDesc ) + { + const SwFrame* pFrame; + const SwPageFrame* pPage; + if (pNode && nullptr != (pFrame = pNode->getLayoutFrame(pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) && + nullptr != ( pPage = pFrame->FindPageFrame() ) ) + { + pPgDesc = pPage->GetPageDesc(); + if ( pPgDescNdIdx ) + { + *pPgDescNdIdx = pNode->GetIndex(); + } + } + } + + if( !pPgDesc ) + { + // Thus via the nodes array + const SwDoc& rDoc = GetDoc(); + const SwNode* pNd = this; + const SwStartNode* pSttNd; + if( pNd->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() && + nullptr != ( pSttNd = pNd->FindFlyStartNode() ) ) + { + // Find the right Anchor first + const SwFrameFormat* pFormat = nullptr; + const sw::SpzFrameFormats& rFormats = *rDoc.GetSpzFrameFormats(); + + for(sw::SpzFrameFormat* pSpz: rFormats) + { + const SwFormatContent& rContent = pSpz->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == static_cast<SwNode const *>(pSttNd) ) + { + pFormat = pSpz; + break; + } + } + + if( pFormat ) + { + const SwFormatAnchor* pAnchor = &pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && + pAnchor->GetAnchorNode() ) + { + pNd = pAnchor->GetAnchorNode(); + const SwNode* pFlyNd = pNd->FindFlyStartNode(); + while( pFlyNd ) + { + // Get up through the Anchor + size_t n; + for( n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFrameFormat = rFormats[ n ]; + const SwNodeIndex* pIdx = pFrameFormat->GetContent(). + GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { + if( pFormat == pFrameFormat ) + { + pNd = pFlyNd; + pFlyNd = nullptr; + break; + } + pAnchor = &pFrameFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId()) || + !pAnchor->GetAnchorNode() ) + { + pFlyNd = nullptr; + break; + } + + pFlyNd = pAnchor->GetAnchorNode()->FindFlyStartNode(); + break; + } + } + if( n >= rFormats.size() ) + { + OSL_ENSURE( false, "FlySection, but no Format found" ); + return nullptr; + } + } + } + } + // pNd should now contain the correct Anchor or it's still this + } + + if( pNd->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() ) + { + if( pNd->GetIndex() > GetNodes().GetEndOfAutotext().GetIndex() ) + { + pPgDesc = &rDoc.GetPageDesc( 0 ); + pNd = nullptr; + } + else + { + // Find the Body text node + if( nullptr != ( pSttNd = pNd->FindHeaderStartNode() ) || + nullptr != ( pSttNd = pNd->FindFooterStartNode() )) + { + // Then find this StartNode in the PageDescs + sal_uInt16 nId; + UseOnPage eAskUse; + if( SwHeaderStartNode == pSttNd->GetStartNodeType()) + { + nId = RES_HEADER; + eAskUse = UseOnPage::HeaderShare; + } + else + { + nId = RES_FOOTER; + eAskUse = UseOnPage::FooterShare; + } + + for( size_t n = rDoc.GetPageDescCnt(); n && !pPgDesc; ) + { + const SwPageDesc& rPgDsc = rDoc.GetPageDesc( --n ); + const SwFrameFormat* pFormat = &rPgDsc.GetMaster(); + int nStt = 0, nLast = 1; + if( !( eAskUse & rPgDsc.ReadUseOn() )) ++nLast; + + for( ; nStt < nLast; ++nStt, pFormat = &rPgDsc.GetLeft() ) + { + const SwFrameFormat * pHdFtFormat = nId == RES_HEADER + ? static_cast<SwFormatHeader const &>( + pFormat->GetFormatAttr(nId)).GetHeaderFormat() + : static_cast<SwFormatFooter const &>( + pFormat->GetFormatAttr(nId)).GetFooterFormat(); + if( pHdFtFormat ) + { + const SwFormatContent& rContent = pHdFtFormat->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == + static_cast<SwNode const *>(pSttNd) ) + { + pPgDesc = &rPgDsc; + break; + } + } + } + } + + if( !pPgDesc ) + pPgDesc = &rDoc.GetPageDesc( 0 ); + pNd = nullptr; + } + else if( nullptr != ( pSttNd = pNd->FindFootnoteStartNode() )) + { + // the Anchor can only be in the Body text + const SwTextFootnote* pTextFootnote; + const SwFootnoteIdxs& rFootnoteArr = rDoc.GetFootnoteIdxs(); + for( size_t n = 0; n < rFootnoteArr.size(); ++n ) + if( nullptr != ( pTextFootnote = rFootnoteArr[ n ])->GetStartNode() && + static_cast<SwNode const *>(pSttNd) == + &pTextFootnote->GetStartNode()->GetNode() ) + { + pNd = &pTextFootnote->GetTextNode(); + break; + } + } + else + { + // Can only be a page-bound Fly (or something newer). + // we can only return the standard here + OSL_ENSURE( pNd->FindFlyStartNode(), + "Where is this Node?" ); + + pPgDesc = &rDoc.GetPageDesc( 0 ); + pNd = nullptr; + } + } + } + + if( pNd ) + { + SwFindNearestNode aInfo( *pNd ); + // Over all Nodes of all PageDescs + for (const SfxPoolItem* pItem : rDoc.GetAttrPool().GetItemSurrogates(RES_PAGEDESC)) + { + auto pPageDescItem = dynamic_cast<const SwFormatPageDesc*>(pItem); + if( pPageDescItem && pPageDescItem->GetDefinedIn() ) + { + const sw::BroadcastingModify* pMod = pPageDescItem->GetDefinedIn(); + if( auto pContentNode = dynamic_cast<const SwContentNode*>( pMod) ) + aInfo.CheckNode( *pContentNode ); + else if( auto pFormat = dynamic_cast<const SwFormat*>( pMod) ) + pFormat->GetInfo( aInfo ); + } + } + + pNd = aInfo.GetFoundNode(); + if( nullptr != pNd ) + { + if( pNd->IsContentNode() ) + pPgDesc = pNd->GetContentNode()->GetAttr( RES_PAGEDESC ).GetPageDesc(); + else if( pNd->IsTableNode() ) + pPgDesc = pNd->GetTableNode()->GetTable(). + GetFrameFormat()->GetPageDesc().GetPageDesc(); + else if( pNd->IsSectionNode() ) + pPgDesc = pNd->GetSectionNode()->GetSection(). + GetFormat()->GetPageDesc().GetPageDesc(); + if ( pPgDescNdIdx ) + { + *pPgDescNdIdx = pNd->GetIndex(); + } + } + if( !pPgDesc ) + pPgDesc = &rDoc.GetPageDesc( 0 ); + } + } + return pPgDesc; +} + +/// If the node is located in a Fly, we return it formatted accordingly +SwFrameFormat* SwNode::GetFlyFormat() const +{ + SwFrameFormat* pRet = nullptr; + const SwNode* pSttNd = FindFlyStartNode(); + if( pSttNd ) + { + if( IsContentNode() ) + { + SwContentFrame* pFrame = SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<const SwContentNode*>(this)).First(); + if( pFrame && pFrame->FindFlyFrame()) + pRet = pFrame->FindFlyFrame()->GetFormat(); + } + if( !pRet ) + { + // The hard way through the Doc is our last way out + const sw::SpzFrameFormats& rSpzs = *GetDoc().GetSpzFrameFormats(); + for(sw::SpzFrameFormat* pSpz: rSpzs) + { + // Only Writer fly frames can contain Writer nodes. + if (pSpz->Which() != RES_FLYFRMFMT) + continue; + const SwFormatContent& rContent = pSpz->GetContent(); + if( rContent.GetContentIdx() && + &rContent.GetContentIdx()->GetNode() == pSttNd ) + { + pRet = pSpz; + break; + } + } + } + } + return pRet; +} + +SwTableBox* SwNode::GetTableBox() const +{ + SwTableBox* pBox = nullptr; + const SwNode* pSttNd = FindTableBoxStartNode(); + if( pSttNd ) + pBox = const_cast<SwTableBox*>(pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() )); + return pBox; +} + +SwStartNode* SwNode::FindSttNodeByType( SwStartNodeType eTyp ) +{ + SwStartNode* pTmp = IsStartNode() ? static_cast<SwStartNode*>(this) : m_pStartOfSection; + + while( eTyp != pTmp->GetStartNodeType() && pTmp->GetIndex() ) + pTmp = pTmp->m_pStartOfSection; + return eTyp == pTmp->GetStartNodeType() ? pTmp : nullptr; +} + +const SwTextNode* SwNode::FindOutlineNodeOfLevel(sal_uInt8 const nLvl, + SwRootFrame const*const pLayout) const +{ + const SwTextNode* pRet = nullptr; + const SwOutlineNodes& rONds = GetNodes().GetOutLineNds(); + if( MAXLEVEL > nLvl && !rONds.empty() ) + { + SwOutlineNodes::size_type nPos; + SwNode* pNd = const_cast<SwNode*>(this); + bool bCheckFirst = false; + if( !rONds.Seek_Entry( pNd, &nPos )) + { + if (nPos == 0) + bCheckFirst = true; + } + else + { + ++nPos; + } + + if( bCheckFirst ) + { + // The first OutlineNode comes after the one asking. + // Test if both are on the same page. + // If not it's invalid. + for (nPos = 0; nPos < rONds.size(); ++nPos) + { + pRet = rONds[nPos]->GetTextNode(); + if (!pLayout || sw::IsParaPropsNode(*pLayout, *pRet)) + { + break; + } + } + if (nPos == rONds.size()) + { + return nullptr; + } + + const SwContentNode* pCNd = GetContentNode(); + + Point aPt( 0, 0 ); + std::pair<Point, bool> const tmp(aPt, false); + const SwFrame* pFrame = pRet->getLayoutFrame(pRet->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp), + * pMyFrame = pCNd ? pCNd->getLayoutFrame(pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + const SwPageFrame* pPgFrame = pFrame ? pFrame->FindPageFrame() : nullptr; + if( pPgFrame && pMyFrame && + pPgFrame->getFrameArea().Top() > pMyFrame->getFrameArea().Top() ) + { + // The one asking precedes the Page, thus its invalid + pRet = nullptr; + } + } + else + { + for ( ; 0 < nPos; --nPos) + { + SwTextNode const*const pNode = rONds[nPos - 1]->GetTextNode(); + if ((nPos == 1 /*as before*/ || pNode->GetAttrOutlineLevel() - 1 <= nLvl) + && (!pLayout || sw::IsParaPropsNode(*pLayout, *pNode))) + { + pRet = pNode; + break; + } + } + } + } + return pRet; +} + +static bool IsValidNextPrevNd( const SwNode& rNd ) +{ + return SwNodeType::Table == rNd.GetNodeType() || + ( SwNodeType::ContentMask & rNd.GetNodeType() ) || + ( SwNodeType::End == rNd.GetNodeType() && rNd.StartOfSectionNode() && + SwNodeType::Table == rNd.StartOfSectionNode()->GetNodeType() ); +} + +sal_uInt8 SwNode::HasPrevNextLayNode() const +{ + // assumption: <this> node is a node inside the document nodes array section. + + sal_uInt8 nRet = 0; + if( IsValidNextPrevNd( *this )) + { + SwNodeIndex aIdx( *this, -1 ); + // #i77805# - skip section start and end nodes + while ( aIdx.GetNode().IsSectionNode() || + ( aIdx.GetNode().IsEndNode() && + aIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) ) + { + --aIdx; + } + if( IsValidNextPrevNd( aIdx.GetNode() )) + nRet |= ND_HAS_PREV_LAYNODE; + // #i77805# - skip section start and end nodes + aIdx.Assign(*this, +1); + while ( aIdx.GetNode().IsSectionNode() || + ( aIdx.GetNode().IsEndNode() && + aIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) ) + { + ++aIdx; + } + if( IsValidNextPrevNd( aIdx.GetNode() )) + nRet |= ND_HAS_NEXT_LAYNODE; + } + return nRet; +} + +void SwNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + const char* pName = "???"; + switch (GetNodeType()) + { + case SwNodeType::End: + pName = "end"; + break; + case SwNodeType::Start: + case SwNodeType::Text: + case SwNodeType::Ole: + abort(); // overridden + case SwNodeType::Table: + pName = "table"; + break; + case SwNodeType::Grf: + pName = "grf"; + break; + default: break; + } + (void)xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(OString::number(static_cast<sal_uInt8>(GetNodeType())).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr())); + + switch (GetNodeType()) + { + case SwNodeType::Grf: + { + auto pNoTextNode = static_cast<const SwNoTextNode*>(this); + const tools::PolyPolygon* pContour = pNoTextNode->HasContour(); + if (pContour) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pContour")); + for (sal_uInt16 i = 0; i < pContour->Count(); ++i) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("polygon")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(i).getStr())); + const tools::Polygon& rPolygon = pContour->GetObject(i); + for (sal_uInt16 j = 0; j < rPolygon.GetSize(); ++j) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("point")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(j).getStr())); + const Point& rPoint = rPolygon.GetPoint(j); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("x"), + BAD_CAST(OString::number(rPoint.X()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("y"), + BAD_CAST(OString::number(rPoint.Y()).getStr())); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + } + break; + default: + break; + } + + (void)xmlTextWriterEndElement(pWriter); + if (GetNodeType() == SwNodeType::End) + (void)xmlTextWriterEndElement(pWriter); // end start node +} + +SwStartNode::SwStartNode( const SwNode& rWhere, const SwNodeType nNdType, + SwStartNodeType eSttNd ) + : SwNode( rWhere, nNdType ), m_eStartNodeType( eSttNd ) +{ + if( !rWhere.GetIndex() ) + { + SwNodes& rNodes = const_cast<SwNodes&> (rWhere.GetNodes()); + rNodes.InsertNode( this, rWhere.GetIndex() ); + m_pStartOfSection = this; + } + // Just do this temporarily until the EndNode is inserted + m_pEndOfSection = reinterpret_cast<SwEndNode*>(this); +} + +SwStartNode::SwStartNode( SwNodes& rNodes, SwNodeOffset nPos ) + : SwNode( rNodes, nPos, SwNodeType::Start ), m_eStartNodeType( SwNormalStartNode ) +{ + if( !nPos ) + { + rNodes.InsertNode( this, nPos ); + m_pStartOfSection = this; + } + // Just do this temporarily until the EndNode is inserted + m_pEndOfSection = reinterpret_cast<SwEndNode*>(this); +} + +void SwStartNode::CheckSectionCondColl() const +{ + SwNodeIndex aIdx( *this ); + SwNodeOffset nEndIdx = EndOfSectionIndex(); + const SwNodes& rNds = GetNodes(); + SwContentNode* pCNd; + while( nullptr != ( pCNd = rNds.GoNext( &aIdx )) && pCNd->GetIndex() < nEndIdx ) + pCNd->ChkCondColl(); +} + +void SwStartNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + const char* pName = "???"; + switch (GetNodeType()) + { + case SwNodeType::Table: + pName = "table"; + break; + default: + switch(GetStartNodeType()) + { + case SwNormalStartNode: + pName = "start"; + break; + case SwTableBoxStartNode: + pName = "tablebox"; + break; + case SwFlyStartNode: + pName = "fly"; + break; + case SwFootnoteStartNode: + pName = "footnote"; + break; + case SwHeaderStartNode: + pName = "header"; + break; + case SwFooterStartNode: + pName = "footer"; + break; + } + break; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(OString::number(static_cast<sal_uInt8>(GetNodeType())).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr())); + + if (GetStartNodeType() == SwTableBoxStartNode) + { + if (SwTableBox* pBox = GetTableBox()) + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("rowspan"), BAD_CAST(OString::number(pBox->getRowSpan()).getStr())); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("attrset")); + if (SwTableBox* pBox = GetTableBox()) + pBox->GetFrameFormat()->GetAttrSet().dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + + // (void)xmlTextWriterEndElement(pWriter); - it is a start node, so don't end, will make xml better nested +} + + +/** Insert a node into the array + * + * The StartOfSection pointer is set to the given node. + * + * The EndOfSection pointer of the corresponding start node is set to this node. + * + * @param rWhere position where the node should be inserted + * @param rSttNd the start note of the section + */ + +SwEndNode::SwEndNode( const SwNode& rWhere, SwStartNode& rSttNd ) + : SwNode( rWhere, SwNodeType::End ) +{ + m_pStartOfSection = &rSttNd; + m_pStartOfSection->m_pEndOfSection = this; +} + +SwEndNode::SwEndNode( SwNodes& rNds, SwNodeOffset nPos, SwStartNode& rSttNd ) + : SwNode( rNds, nPos, SwNodeType::End ) +{ + m_pStartOfSection = &rSttNd; + m_pStartOfSection->m_pEndOfSection = this; +} + +/// only used by SwContentNodeTmp in SwTextNode::Update +SwContentNode::SwContentNode() + : SwNode() + , m_aCondCollListener( *this ) + , m_pCondColl( nullptr ) + , mbSetModifyAtAttr( false ) +{} + +SwContentNode::SwContentNode( const SwNode& rWhere, const SwNodeType nNdType, + SwFormatColl *pColl ) + : SwNode( rWhere, nNdType ) + , m_aCondCollListener( *this ) + , m_pCondColl( nullptr ) + , mbSetModifyAtAttr( false ) +{ + if(pColl) + pColl->Add(this); +} + +SwContentNode::~SwContentNode() +{ + // The base class SwClient of SwFrame excludes itself from the dependency list! + // Thus, we need to delete all Frames in the dependency list. + if (!IsTextNode()) // see ~SwTextNode + { + DelFrames(nullptr); + } + + m_aCondCollListener.EndListeningAll(); + m_pCondColl = nullptr; + + if ( mpAttrSet && mbSetModifyAtAttr ) + const_cast<SwAttrSet*>(mpAttrSet.get())->SetModifyAtAttr( nullptr ); + InvalidateInSwCache(RES_OBJECTDYING); +} +void SwContentNode::UpdateAttr(const SwUpdateAttr& rUpdate) +{ + if (GetNodes().IsDocNodes() + && IsTextNode() + && RES_ATTRSET_CHG == rUpdate.getWhichAttr()) + static_cast<SwTextNode*>(this)->SetCalcHiddenCharFlags(); + CallSwClientNotify(sw::LegacyModifyHint(&rUpdate, &rUpdate)); +} + +void SwContentNode::SwClientNotify( const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyHint->GetWhich(); + InvalidateInSwCache(nWhich); + + bool bSetParent = false; + bool bCalcHidden = false; + SwFormatColl* pFormatColl = nullptr; + switch(nWhich) + { + case RES_OBJECTDYING: + { + SwFormat* pFormat = pLegacyHint->m_pNew + ? static_cast<SwFormat*>(static_cast<const SwPtrMsgPoolItem*>(pLegacyHint->m_pNew)->pObject) + : nullptr; + // Do not mangle pointers if it is the upper-most format! + if(pFormat && GetRegisteredIn() == pFormat) + { + // As ~SwFormat calls CheckRegistrationFormat before + // ~SwModify, which sends the RES_OBJECTDYING, we should never + // reach this point. + assert(false); + } + } + break; + + case RES_FMT_CHG: + // If the Format parent was switched, register the Attrset at the new one + // Skip own Modify! + if(GetpSwAttrSet() + && pLegacyHint->m_pNew + && static_cast<const SwFormatChg*>(pLegacyHint->m_pNew)->pChangedFormat == GetRegisteredIn()) + { + pFormatColl = GetFormatColl(); + bSetParent = true; + } + break; + + case RES_ATTRSET_CHG: + if (GetNodes().IsDocNodes() + && IsTextNode() + && pLegacyHint->m_pOld + && SfxItemState::SET == pLegacyHint->m_pOld->StaticWhichCast(RES_ATTRSET_CHG).GetChgSet()->GetItemState(RES_CHRATR_HIDDEN, false)) + bCalcHidden = true; + break; + + case RES_UPDATE_ATTR: + // RES_UPDATE_ATTR _should_ always contain a SwUpdateAttr hint in old and new. + // However, faking one with just a basic SfxPoolItem setting a WhichId has been observed. + // This makes the crude "WhichId" type divert from the true type, which is bad. + // Thus we are asserting here, but falling back to an proper + // hint instead. so that we at least will not spread such poison further. +#ifdef DBG_UTIL + if (!SfxPoolItem::areSame(pLegacyHint->m_pNew, pLegacyHint->m_pOld)) + { + auto pBT = sal::backtrace_get(20); + SAL_WARN("sw.core", "UpdateAttr not matching! " << sal::backtrace_to_string(pBT.get())); + } +#endif + assert(SfxPoolItem::areSame(pLegacyHint->m_pNew, pLegacyHint->m_pOld)); + assert(dynamic_cast<const SwUpdateAttr*>(pLegacyHint->m_pNew)); + const SwUpdateAttr aFallbackHint(0,0,0); + const SwUpdateAttr& rUpdateAttr = pLegacyHint->m_pNew ? *static_cast<const SwUpdateAttr*>(pLegacyHint->m_pNew) : aFallbackHint; + UpdateAttr(rUpdateAttr); + return; + } + if(bSetParent && GetpSwAttrSet()) + AttrSetHandleHelper::SetParent(mpAttrSet, *this, pFormatColl, pFormatColl); + if(bCalcHidden) + static_cast<SwTextNode*>(this)->SetCalcHiddenCharFlags(); + CallSwClientNotify(rHint); + } + else if (rHint.GetId() == SfxHintId::SwAutoFormatUsedHint) + { + static_cast<const sw::AutoFormatUsedHint&>(rHint).CheckNode(this); + return; + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + m_pCondColl = const_cast<SwFormatColl*>(static_cast<const SwFormatColl*>(pModifyChangedHint->m_pNew)); + } + else if(auto pCondCollCondChgHint = dynamic_cast<const sw::CondCollCondChg*>(&rHint)) + { + ChkCondColl(&pCondCollCondChgHint->m_rColl); + } +} + +bool SwContentNode::InvalidateNumRule() +{ + SwNumRule* pRule = nullptr; + const SfxPoolItem* pItem; + if( GetNodes().IsDocNodes() && + nullptr != ( pItem = GetNoCondAttr( RES_PARATR_NUMRULE, true )) && + !static_cast<const SwNumRuleItem*>(pItem)->GetValue().isEmpty() && + nullptr != (pRule = GetDoc().FindNumRulePtr( + static_cast<const SwNumRuleItem*>(pItem)->GetValue() ) ) ) + { + pRule->SetInvalidRule( true ); + } + return nullptr != pRule; +} + +SwContentFrame *SwContentNode::getLayoutFrame( const SwRootFrame* _pRoot, + const SwPosition *const pPos, + std::pair<Point, bool> const*const pViewPosAndCalcFrame) const +{ + return static_cast<SwContentFrame*>( ::GetFrameOfModify( _pRoot, *this, FRM_CNTNT, + pPos, pViewPosAndCalcFrame)); +} + +SwRect SwContentNode::FindLayoutRect( const bool bPrtArea, const Point* pPoint ) const +{ + SwRect aRet; + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + SwContentFrame* pFrame = static_cast<SwContentFrame*>( ::GetFrameOfModify( nullptr, *this, + FRM_CNTNT, nullptr, pPoint ? &tmp : nullptr) ); + if( pFrame ) + aRet = bPrtArea ? pFrame->getFramePrintArea() : pFrame->getFrameArea(); + return aRet; +} + +SwRect SwContentNode::FindPageFrameRect() const +{ + SwRect aRet; + SwFrame* pFrame = ::GetFrameOfModify( nullptr, *this, FRM_CNTNT ); + if( pFrame && nullptr != ( pFrame = pFrame->FindPageFrame() )) + aRet = pFrame->getFrameArea(); + return aRet; +} + +sal_Int32 SwContentNode::Len() const { return 0; } + +SwFormatColl *SwContentNode::ChgFormatColl( SwFormatColl *pNewColl ) +{ + OSL_ENSURE( pNewColl, "Collectionpointer is 0." ); + SwFormatColl *pOldColl = GetFormatColl(); + + if( pNewColl != pOldColl ) + { + pNewColl->Add( this ); + + // Set the Parent of out AutoAttributes to the new Collection + if( GetpSwAttrSet() ) + AttrSetHandleHelper::SetParent( mpAttrSet, *this, pNewColl, pNewColl ); + + SetCondFormatColl( nullptr ); + + if( !IsModifyLocked() ) + { + assert(dynamic_cast<SwTextFormatColl*>(pNewColl)); + ChkCondColl(static_cast<SwTextFormatColl*>(pNewColl)); + SwFormatChg aTmp1( pOldColl ); + SwFormatChg aTmp2( pNewColl ); + CallSwClientNotify( sw::LegacyModifyHint(&aTmp1, &aTmp2) ); + } + } + InvalidateInSwCache(RES_ATTRSET_CHG); + return pOldColl; +} + +bool SwContentNode::GoNext(SwPosition* pPos, SwCursorSkipMode nMode ) const +{ + if (!GoNext(&pPos->nContent, nMode)) + return false; + if (pPos->nContent.GetContentNode() != &pPos->GetNode()) + pPos->nNode.Assign(*pPos->nContent.GetContentNode()); + return true; +} + +bool SwContentNode::GoNext(SwContentIndex * pIdx, SwCursorSkipMode nMode ) const +{ + bool bRet = true; + if( pIdx->GetIndex() < Len() ) + { + if( !IsTextNode() ) + ++(*pIdx); + else + { + const SwTextNode& rTNd = *GetTextNode(); + sal_Int32 nPos = pIdx->GetIndex(); + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + sal_Int32 nDone = 0; + sal_uInt16 nItrMode = ( SwCursorSkipMode::Cells & nMode ) ? + CharacterIteratorMode::SKIPCELL : + CharacterIteratorMode::SKIPCONTROLCHARACTER; + nPos = g_pBreakIt->GetBreakIter()->nextCharacters( rTNd.GetText(), nPos, + g_pBreakIt->GetLocale( rTNd.GetLang( nPos ) ), + nItrMode, 1, nDone ); + + // Check if nPos is inside hidden text range: + if ( SwCursorSkipMode::Hidden & nMode ) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( rTNd, nPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenStart != COMPLETE_STRING && nHiddenStart != nPos ) + nPos = nHiddenEnd; + } + + if( 1 == nDone ) + *pIdx = nPos; + else + bRet = false; + } + } + else + bRet = false; + return bRet; +} + +bool SwContentNode::GoPrevious(SwContentIndex * pIdx, SwCursorSkipMode nMode ) const +{ + bool bRet = true; + if( pIdx->GetIndex() > 0 ) + { + if( !IsTextNode() ) + --(*pIdx); + else + { + const SwTextNode& rTNd = *GetTextNode(); + sal_Int32 nPos = pIdx->GetIndex(); + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + sal_Int32 nDone = 0; + sal_uInt16 nItrMode = ( SwCursorSkipMode::Cells & nMode ) ? + CharacterIteratorMode::SKIPCELL : + CharacterIteratorMode::SKIPCONTROLCHARACTER; + nPos = g_pBreakIt->GetBreakIter()->previousCharacters( rTNd.GetText(), nPos, + g_pBreakIt->GetLocale( rTNd.GetLang( nPos ) ), + nItrMode, 1, nDone ); + + // Check if nPos is inside hidden text range: + if ( SwCursorSkipMode::Hidden & nMode ) + { + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( rTNd, nPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenStart != COMPLETE_STRING ) + nPos = nHiddenStart; + } + + if( 1 == nDone ) + *pIdx = nPos; + else + bRet = false; + } + } + else + bRet = false; + return bRet; +} + +/** + * Creates all Views for the Doc for this Node. + * The created ContentFrames are attached to the corresponding Layout. + */ +void SwContentNode::MakeFramesForAdjacentContentNode(SwContentNode& rNode) +{ + OSL_ENSURE( &rNode != this, + "No ContentNode or CopyNode and new Node identical." ); + + if( !HasWriterListeners() || &rNode == this ) // Do we actually have Frames? + return; + + SwFrame *pFrame; + SwLayoutFrame *pUpper; + // Create Frames for Nodes which come after the Table? + OSL_ENSURE( FindTableNode() == rNode.FindTableNode(), "Table confusion" ); + + SwNode2Layout aNode2Layout( *this, rNode.GetIndex() ); + + while( nullptr != (pUpper = aNode2Layout.UpperFrame( pFrame, rNode )) ) + { + if (pUpper->getRootFrame()->HasMergedParas() + && !rNode.IsCreateFrameWhenHidingRedlines()) + { + continue; + } + SwFrame *pNew = rNode.MakeFrame( pUpper ); + pNew->Paste( pUpper, pFrame ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if ( pNew->IsTextFrame() ) + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + auto pPrev = pNew->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + } +} + +/** + * Deletes all Views from the Doc for this Node. + * The ContentFrames are removed from the corresponding Layout. + */ +void SwContentNode::DelFrames(SwRootFrame const*const pLayout) +{ + if( !HasWriterListeners() ) + return; + + SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( SwContentFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + if (pLayout && pLayout != pFrame->getRootFrame()) + { + continue; // skip it + } + if (pFrame->IsTextFrame()) + { + if (sw::MergedPara * pMerged = + static_cast<SwTextFrame *>(pFrame)->GetMergedPara()) + { + if (this != pMerged->pFirstNode) + { + // SwNodes::RemoveNode iterates *backwards* - so + // ensure there are no more extents pointing to this + // node as SwFrame::InvalidatePage() will access them. + // Note: cannot send via SwClientNotify from dtor + // because that would access deleted wrong-lists + sw::UpdateMergedParaForDelete(*pMerged, true, + *static_cast<SwTextNode*>(this), 0, Len()); + if (this == pMerged->pParaPropsNode) + { + // otherwise pointer should have been updated to a different node + assert(this == pMerged->pLastNode); + assert(pMerged->extents.empty()); + for (SwNodeOffset i = pMerged->pLastNode->GetIndex() - 1;; + --i) + { + assert(pMerged->pFirstNode->GetIndex() <= i); + SwNode *const pNode(GetNodes()[i]); + if (pNode->IsTextNode() + && pNode->GetRedlineMergeFlag() != Merge::Hidden) + { + pMerged->pParaPropsNode = pNode->GetTextNode(); + break; + } + else if (pMerged->pFirstNode->GetIndex() == i) + { // this can only happen when called from CheckParaRedlineMerge() + // and the pMerged will be deleted anyway + pMerged->pParaPropsNode = pMerged->pFirstNode; + break; + } + } + assert(pMerged->listener.IsListeningTo(pMerged->pParaPropsNode)); + } + assert(GetIndex() <= pMerged->pLastNode->GetIndex()); + if (this == pMerged->pLastNode) + { + // tdf#130680 find the previous node that is a + // listener of pMerged; see CheckParaRedlineMerge() + for (SwNodeOffset i = GetIndex() - 1; + this == pMerged->pLastNode; --i) + { + SwNode *const pNode = GetNodes()[i]; + if (pNode->IsTextNode()) + { + pMerged->pLastNode = pNode->GetTextNode(); + } + else if (SwEndNode const*const pEnd = pNode->GetEndNode()) + { + SwStartNode const*const pStart(pEnd->StartOfSectionNode()); + i = pStart->GetIndex(); // skip table or section + } + } + assert(pMerged->pFirstNode->GetIndex() <= pMerged->pLastNode->GetIndex()); + assert(pMerged->listener.IsListeningTo(pMerged->pLastNode)); + } + // avoid re-parenting mess (ModifyChangedHint) + pMerged->listener.EndListening(this); + continue; // don't delete + } + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } +#endif + } + + if( pFrame->IsFollow() ) + { + SwContentFrame* pMaster = pFrame->FindMaster(); + pMaster->SetFollow( pFrame->GetFollow() ); + } + pFrame->SetFollow( nullptr );//So it doesn't get funny ideas. + //Otherwise it could be possible that a follow + //gets destroyed before its master. Following + //the now invalid pointer will then lead to an + //illegal memory access. The chain can be + //crushed here because we'll destroy all of it + //anyway. + + if( pFrame->GetUpper() && pFrame->IsInFootnote() && !pFrame->GetIndNext() && + !pFrame->GetIndPrev() ) + { + SwFootnoteFrame *pFootnote = pFrame->FindFootnoteFrame(); + OSL_ENSURE( pFootnote, "You promised a FootnoteFrame?" ); + SwContentFrame* pCFrame; + if( !pFootnote->GetFollow() && !pFootnote->GetMaster() && + nullptr != ( pCFrame = pFootnote->GetRefFromAttr()) && pCFrame->IsFollow() ) + { + OSL_ENSURE( pCFrame->IsTextFrame(), "NoTextFrame has Footnote?" ); + pCFrame->FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); + } + } + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } +} + +SwContentNode *SwContentNode::JoinNext() +{ + return this; +} + + +/// Get info from Modify +bool SwContentNode::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_FINDNEARESTNODE: + if( GetAttr( RES_PAGEDESC ).GetPageDesc() ) + static_cast<SwFindNearestNode&>(rInfo).CheckNode( *this ); + return true; + } + return sw::BroadcastingModify::GetInfo( rInfo ); +} + +/// @param rAttr the attribute to set +bool SwContentNode::SetAttr(const SfxPoolItem& rAttr ) +{ + if( !GetpSwAttrSet() ) // Have the Nodes created by the corresponding AttrSets + NewAttrSet( GetDoc().GetAttrPool() ); + + OSL_ENSURE( GetpSwAttrSet(), "Why did't we create an AttrSet?"); + + InvalidateInSwCache(RES_ATTRSET_CHG); + + bool bRet = false; + // If Modify is locked, we do not send any Modifys + if( IsModifyLocked() || + ( !HasWriterListeners() && RES_PARATR_NUMRULE != rAttr.Which() )) + { + bRet = nullptr != AttrSetHandleHelper::Put( mpAttrSet, *this, rAttr ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bRet = AttrSetHandleHelper::Put_BC( mpAttrSet, *this, rAttr, &aOld, &aNew ); + if( bRet ) + sw::ClientNotifyAttrChg(*this, *GetpSwAttrSet(), aOld, aNew); + } + return bRet; +} + +bool SwContentNode::SetAttr( const SfxItemSet& rSet ) +{ + InvalidateInSwCache(RES_ATTRSET_CHG); + + if( const SwFormatAutoFormat* pFnd = rSet.GetItemIfSet( RES_AUTO_STYLE, false ) ) + { + OSL_ENSURE( rSet.Count() == 1, "SetAutoStyle mixed with other attributes?!" ); + + // If there already is an attribute set (usually containing a numbering + // item), we have to merge the attribute of the new set into the old set: + bool bSetParent = true; + if ( GetpSwAttrSet() ) + { + bSetParent = false; + AttrSetHandleHelper::Put( mpAttrSet, *this, *pFnd->GetStyleHandle() ); + } + else + { + std::shared_ptr<SfxItemSet> pItemSet = pFnd->GetStyleHandle(); + mpAttrSet = std::dynamic_pointer_cast<SwAttrSet>(pItemSet); + assert(bool(pItemSet) == bool(mpAttrSet) && "types do not match"); + } + + if ( bSetParent ) + { + // If the content node has a conditional style, we have to set the + // string item containing the correct conditional style name (the + // style name property has already been set during the import!) + // In case we do not have a conditional style, we make use of the + // fact that nobody else uses the attribute set behind the handle. + // FME 2007-07-10 #i78124# If autostyle does not have a parent, + // the string is empty. + const SfxStringItem* pNameItem = nullptr; + if ( nullptr != GetCondFormatColl() || + !(pNameItem = mpAttrSet->GetItemIfSet( RES_FRMATR_STYLE_NAME, false )) || + pNameItem->GetValue().isEmpty() ) + AttrSetHandleHelper::SetParent( mpAttrSet, *this, &GetAnyFormatColl(), GetFormatColl() ); + else + const_cast<SwAttrSet*>(mpAttrSet.get())->SetParent( &GetFormatColl()->GetAttrSet() ); + } + + return true; + } + + if( !GetpSwAttrSet() ) // Have the AttrsSets created by the corresponding Nodes + NewAttrSet( GetDoc().GetAttrPool() ); + + bool bRet = false; + // If Modify is locked, do not send any Modifys + if ( IsModifyLocked() || + ( !HasWriterListeners() && + SfxItemState::SET != rSet.GetItemState( RES_PARATR_NUMRULE, false ) ) ) + { + // Some special treatment for Attributes + bRet = AttrSetHandleHelper::Put( mpAttrSet, *this, rSet ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bRet = AttrSetHandleHelper::Put_BC( mpAttrSet, *this, rSet, &aOld, &aNew ); + if( bRet ) + sw::ClientNotifyAttrChg(*this, *GetpSwAttrSet(), aOld, aNew); + } + return bRet; +} + +// With nWhich it takes the Hint from the Delta array +bool SwContentNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + if( !GetpSwAttrSet() ) + return false; + + InvalidateInSwCache(RES_ATTRSET_CHG); + + // If Modify is locked, do not send out any Modifys + if( IsModifyLocked() ) + { + sal_uInt16 nDel = 0; + if ( !nWhich2 || nWhich2 < nWhich1 ) + { + nDel = ClearItemsFromAttrSet( { nWhich1 } ); + } + else + nDel = AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, nWhich1, nWhich2, nullptr, nullptr ); + + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + return 0 != nDel; + } + + // No valid area defined? + if( !nWhich2 || nWhich2 < nWhich1 ) + nWhich2 = nWhich1; // Then set only this Item to 1st Id + + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bool bRet = 0 != AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, nWhich1, nWhich2, &aOld, &aNew ); + + if( bRet ) + { + sw::ClientNotifyAttrChg(*this, *GetpSwAttrSet(), aOld, aNew); + + if( !GetpSwAttrSet()->Count() ) // Empty?, delete it + mpAttrSet.reset(); + } + return bRet; +} + +bool SwContentNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr ) +{ + if( !GetpSwAttrSet() ) + return false; + + InvalidateInSwCache(RES_ATTRSET_CHG); + // If Modify is locked, do not send out any Modifys + sal_uInt16 nDel = 0; + if( IsModifyLocked() ) + { + nDel = ClearItemsFromAttrSet( rWhichArr ); + } + else + { + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + + for ( const auto& rWhich : rWhichArr ) + if( AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, rWhich, &aOld, &aNew )) + ++nDel; + + if( nDel ) + sw::ClientNotifyAttrChg(*this, *GetpSwAttrSet(), aOld, aNew); + } + if( !GetpSwAttrSet()->Count() ) // Empty?, delete it + mpAttrSet.reset(); + return 0 != nDel ; +} + +sal_uInt16 SwContentNode::ResetAllAttr() +{ + if( !GetpSwAttrSet() ) + return 0; + InvalidateInSwCache(RES_ATTRSET_CHG); + + // If Modify is locked, do not send out any Modifys + if( IsModifyLocked() ) + { + sal_uInt16 nDel = ClearItemsFromAttrSet( { 0 } ); + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + return nDel; + } + + SwAttrSet aOld( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ), + aNew( *GetpSwAttrSet()->GetPool(), GetpSwAttrSet()->GetRanges() ); + bool bRet = 0 != AttrSetHandleHelper::ClearItem_BC( mpAttrSet, *this, 0, &aOld, &aNew ); + + if( bRet ) + { + sw::ClientNotifyAttrChg(*this, *GetpSwAttrSet(), aOld, aNew); + if( !GetpSwAttrSet()->Count() ) // Empty? Delete + mpAttrSet.reset(); + } + return aNew.Count(); +} + +bool SwContentNode::GetAttr( SfxItemSet& rSet ) const +{ + if( rSet.Count() ) + rSet.ClearItem(); + + const SwAttrSet& rAttrSet = GetSwAttrSet(); + return rSet.Set( rAttrSet ); +} + +sal_uInt16 SwContentNode::ClearItemsFromAttrSet( const std::vector<sal_uInt16>& rWhichIds ) +{ + sal_uInt16 nRet = 0; + if ( rWhichIds.empty() ) + return nRet; + + OSL_ENSURE( GetpSwAttrSet(), "no item set" ); + SwAttrSet aNewAttrSet( *GetpSwAttrSet() ); + for ( const auto& rWhichId : rWhichIds ) + { + nRet = nRet + aNewAttrSet.ClearItem( rWhichId ); + } + if ( nRet ) + AttrSetHandleHelper::GetNewAutoStyle( mpAttrSet, *this, aNewAttrSet ); + + return nRet; +} + +const SfxPoolItem* SwContentNode::GetNoCondAttr( sal_uInt16 nWhich, + bool bInParents ) const +{ + const SfxPoolItem* pFnd = nullptr; + if( m_pCondColl && m_pCondColl->GetRegisteredIn() ) + { + if( !GetpSwAttrSet() || ( SfxItemState::SET != GetpSwAttrSet()->GetItemState( + nWhich, false, &pFnd ) && bInParents )) + { + (void)static_cast<const SwFormat*>(GetRegisteredIn())->GetItemState( nWhich, bInParents, &pFnd ); + } + } + // undo change of issue #i51029# + // Note: <GetSwAttrSet()> returns <mpAttrSet>, if set, otherwise it returns + // the attribute set of the paragraph style, which is valid for the + // content node - see file <node.hxx> + else + { + GetSwAttrSet().GetItemState( nWhich, bInParents, &pFnd ); + } + return pFnd; +} + +static bool lcl_CheckMaxLength(SwNode const& rPrev, SwNode const& rNext) +{ + if (rPrev.GetNodeType() != rNext.GetNodeType()) + { + return false; + } + if (!rPrev.IsTextNode()) + { + return true; + } + + // Check if a node can contain the other (order is not significant) + return rPrev.GetTextNode()->GetSpaceLeft() > rNext.GetTextNode()->Len(); +} + +/// Can we join two Nodes? +/// We can return the 2nd position in pIdx. +bool SwContentNode::CanJoinNext( SwNodeIndex* pIdx ) const +{ + const SwNodes& rNds = GetNodes(); + SwNodeIndex aIdx( *this, 1 ); + + const SwNode* pNd = this; + while( aIdx < rNds.Count()-1 && + (( pNd = &aIdx.GetNode())->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ))) + ++aIdx; + + if (rNds.Count()-1 == aIdx.GetIndex()) + return false; + if (!lcl_CheckMaxLength(*this, *pNd)) + { + return false; + } + if( pIdx ) + *pIdx = aIdx; + return true; +} + +/// Can we join two Nodes? +/// We can return the 2nd position in pIdx. +bool SwContentNode::CanJoinNext( SwPosition* pIdx ) const +{ + const SwNodes& rNds = GetNodes(); + SwNodeIndex aIdx( *this, 1 ); + + const SwNode* pNd = this; + while( aIdx < rNds.Count()-1 && + (( pNd = &aIdx.GetNode())->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ))) + ++aIdx; + + if (rNds.Count()-1 == aIdx.GetIndex()) + return false; + if (!lcl_CheckMaxLength(*this, *pNd)) + { + return false; + } + if( pIdx ) + pIdx->Assign(aIdx); + return true; +} + +/// Can we join two Nodes? +/// We can return the 2nd position in pIdx. +bool SwContentNode::CanJoinPrev( SwNodeIndex* pIdx ) const +{ + SwNodeIndex aIdx( *this, -1 ); + + const SwNode* pNd = this; + while( aIdx.GetIndex() && + (( pNd = &aIdx.GetNode())->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ))) + --aIdx; + + if (SwNodeOffset(0) == aIdx.GetIndex()) + return false; + if (!lcl_CheckMaxLength(*pNd, *this)) + { + return false; + } + if( pIdx ) + *pIdx = aIdx; + return true; +} + +void SwContentNode::SetCondFormatColl(SwFormatColl* pColl) +{ + if( !((!pColl && m_pCondColl) || ( pColl && !m_pCondColl ) || + ( pColl && pColl != m_pCondColl->GetRegisteredIn() )) ) + return; + + SwFormatColl* pOldColl = GetCondFormatColl(); + m_aCondCollListener.EndListeningAll(); + if(pColl) + m_aCondCollListener.StartListening(pColl); + m_pCondColl = pColl; + if(GetpSwAttrSet()) + AttrSetHandleHelper::SetParent(mpAttrSet, *this, &GetAnyFormatColl(), GetFormatColl()); + + if(!IsModifyLocked()) + { + SwFormatChg aTmp1(pOldColl ? pOldColl : GetFormatColl()); + SwFormatChg aTmp2(pColl ? pColl : GetFormatColl()); + CallSwClientNotify(sw::LegacyModifyHint(&aTmp1, &aTmp2)); + } + InvalidateInSwCache(RES_ATTRSET_CHG); +} + +bool SwContentNode::IsAnyCondition( SwCollCondition& rTmp ) const +{ + const SwNodes& rNds = GetNodes(); + { + Master_CollCondition nCond = Master_CollCondition::NONE; + const SwStartNode* pSttNd = StartOfSectionNode(); + while( pSttNd ) + { + switch( pSttNd->GetNodeType() ) + { + case SwNodeType::Table: nCond = Master_CollCondition::PARA_IN_TABLEBODY; break; + case SwNodeType::Section: nCond = Master_CollCondition::PARA_IN_SECTION; break; + + default: + switch( pSttNd->GetStartNodeType() ) + { + case SwTableBoxStartNode: + { + nCond = Master_CollCondition::PARA_IN_TABLEBODY; + const SwTableNode* pTableNd = pSttNd->FindTableNode(); + const SwTableBox* pBox; + if( pTableNd && nullptr != ( pBox = pTableNd->GetTable(). + GetTableBox(pSttNd->GetIndex()) ) && + pBox->IsInHeadline( &pTableNd->GetTable() ) ) + nCond = Master_CollCondition::PARA_IN_TABLEHEAD; + } + break; + case SwFlyStartNode: nCond = Master_CollCondition::PARA_IN_FRAME; break; + case SwFootnoteStartNode: + { + nCond = Master_CollCondition::PARA_IN_FOOTNOTE; + const SwFootnoteIdxs& rFootnoteArr = rNds.GetDoc().GetFootnoteIdxs(); + const SwTextFootnote* pTextFootnote; + const SwNode* pSrchNd = pSttNd; + + for( size_t n = 0; n < rFootnoteArr.size(); ++n ) + if( nullptr != ( pTextFootnote = rFootnoteArr[ n ])->GetStartNode() && + pSrchNd == &pTextFootnote->GetStartNode()->GetNode() ) + { + if( pTextFootnote->GetFootnote().IsEndNote() ) + nCond = Master_CollCondition::PARA_IN_ENDNOTE; + break; + } + } + break; + case SwHeaderStartNode: nCond = Master_CollCondition::PARA_IN_HEADER; break; + case SwFooterStartNode: nCond = Master_CollCondition::PARA_IN_FOOTER; break; + case SwNormalStartNode: break; + } + } + + if( nCond != Master_CollCondition::NONE ) + { + rTmp.SetCondition( nCond, 0 ); + return true; + } + pSttNd = pSttNd->GetIndex() + ? pSttNd->StartOfSectionNode() + : nullptr; + } + } + + { + SwOutlineNodes::size_type nPos; + const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); + if( !rOutlNds.empty() ) + { + if( !rOutlNds.Seek_Entry( const_cast<SwContentNode*>(this), &nPos ) && nPos ) + --nPos; + if( nPos < rOutlNds.size() && + rOutlNds[ nPos ]->GetIndex() < GetIndex() ) + { + SwTextNode* pOutlNd = rOutlNds[ nPos ]->GetTextNode(); + + if( pOutlNd->IsOutline()) + { + rTmp.SetCondition( Master_CollCondition::PARA_IN_OUTLINE, pOutlNd->GetAttrOutlineLevel() - 1 ); + return true; + } + } + } + } + + return false; +} + +void SwContentNode::ChkCondColl(const SwTextFormatColl* pColl) +{ + if(pColl != GetRegisteredIn()) + { + SAL_INFO("sw.core", "Not our cond collection, skipping check of Cond Colls."); + return; + } + if(&GetNodes() != &GetDoc().GetNodes()) + { + SAL_WARN("sw.core", "Nodes amiss, skipping check of Cond Colls."); + return; + } + // Check, just to be sure + if( RES_CONDTXTFMTCOLL != GetFormatColl()->Which() ) + return; + + SwCollCondition aTmp( nullptr, Master_CollCondition::NONE, 0 ); + const SwCollCondition* pCColl; + + bool bDone = false; + + if( IsAnyCondition( aTmp )) + { + pCColl = static_cast<SwConditionTextFormatColl*>(GetFormatColl()) + ->HasCondition( aTmp ); + + if (pCColl) + { + SetCondFormatColl( pCColl->GetTextFormatColl() ); + bDone = true; + } + } + + if (bDone) + return; + + if( IsTextNode() && static_cast<SwTextNode*>(this)->GetNumRule()) + { + // Is at which Level in a list? + aTmp.SetCondition( Master_CollCondition::PARA_IN_LIST, + static_cast<SwTextNode*>(this)->GetActualListLevel() ); + pCColl = static_cast<SwConditionTextFormatColl*>(GetFormatColl())-> + HasCondition( aTmp ); + } + else + pCColl = nullptr; + + if( pCColl ) + SetCondFormatColl( pCColl->GetTextFormatColl() ); + else if( m_pCondColl ) + SetCondFormatColl( nullptr ); +} + +// #i42921# +SvxFrameDirection SwContentNode::GetTextDirection( const SwPosition& rPos, + const Point* pPt ) const +{ + SvxFrameDirection nRet = SvxFrameDirection::Unknown; + + Point aPt; + if( pPt ) + aPt = *pPt; + + // #i72024# - No format of the frame, because this can cause recursive layout actions + std::pair<Point, bool> const tmp(aPt, false); + SwFrame* pFrame = getLayoutFrame(GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), &rPos, &tmp); + + if ( pFrame ) + { + if ( pFrame->IsVertical() ) + { + if (pFrame->IsVertLRBT()) + nRet = SvxFrameDirection::Vertical_LR_BT; + else if (pFrame->IsRightToLeft()) + nRet = SvxFrameDirection::Vertical_LR_TB; + else + nRet = SvxFrameDirection::Vertical_RL_TB; + } + else + { + if ( pFrame->IsRightToLeft() ) + nRet = SvxFrameDirection::Horizontal_RL_TB; + else + nRet = SvxFrameDirection::Horizontal_LR_TB; + } + } + + return nRet; +} + +std::unique_ptr<SwOLENodes> SwContentNode::CreateOLENodesArray( const SwFormatColl& rColl, bool bOnlyWithInvalidSize ) +{ + std::unique_ptr<SwOLENodes> pNodes; + SwIterator<SwContentNode,SwFormatColl> aIter( rColl ); + for( SwContentNode* pNd = aIter.First(); pNd; pNd = aIter.Next() ) + { + SwOLENode *pONd = pNd->GetOLENode(); + if ( pONd && (!bOnlyWithInvalidSize || pONd->IsOLESizeInvalid()) ) + { + if ( !pNodes ) + pNodes.reset(new SwOLENodes); + pNodes->push_back( pONd ); + } + } + + return pNodes; +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwContentNode::getSdrAllFillAttributesHelper() const +{ + return drawinglayer::attribute::SdrAllFillAttributesHelperPtr(); +} + +/* + * Document Interface Access + */ +const IDocumentSettingAccess* SwNode::getIDocumentSettingAccess() const { return &GetDoc().GetDocumentSettingManager(); } +const IDocumentDeviceAccess& SwNode::getIDocumentDeviceAccess() const { return GetDoc().getIDocumentDeviceAccess(); } +const IDocumentRedlineAccess& SwNode::getIDocumentRedlineAccess() const { return GetDoc().getIDocumentRedlineAccess(); } +const IDocumentStylePoolAccess& SwNode::getIDocumentStylePoolAccess() const { return GetDoc().getIDocumentStylePoolAccess(); } +const IDocumentDrawModelAccess& SwNode::getIDocumentDrawModelAccess() const { return GetDoc().getIDocumentDrawModelAccess(); } +const IDocumentLayoutAccess& SwNode::getIDocumentLayoutAccess() const { return GetDoc().getIDocumentLayoutAccess(); } +IDocumentLayoutAccess& SwNode::getIDocumentLayoutAccess() { return GetDoc().getIDocumentLayoutAccess(); } +const IDocumentLinksAdministration& SwNode::getIDocumentLinksAdministration() const { return GetDoc().getIDocumentLinksAdministration(); } +IDocumentLinksAdministration& SwNode::getIDocumentLinksAdministration() { return GetDoc().getIDocumentLinksAdministration(); } +const IDocumentFieldsAccess& SwNode::getIDocumentFieldsAccess() const { return GetDoc().getIDocumentFieldsAccess(); } +IDocumentFieldsAccess& SwNode::getIDocumentFieldsAccess() { return GetDoc().getIDocumentFieldsAccess(); } +IDocumentContentOperations& SwNode::getIDocumentContentOperations() { return GetDoc().getIDocumentContentOperations(); } +IDocumentListItems& SwNode::getIDocumentListItems() { return GetDoc().getIDocumentListItems(); } // #i83479# + +const IDocumentMarkAccess* SwNode::getIDocumentMarkAccess() const { return GetDoc().getIDocumentMarkAccess(); } +IStyleAccess& SwNode::getIDocumentStyleAccess() { return GetDoc().GetIStyleAccess(); } + +bool SwNode::IsInRedlines() const +{ + const SwDoc& rDoc = GetDoc(); + + return rDoc.getIDocumentRedlineAccess().IsInRedlines(*this); +} + +void SwNode::AddAnchoredFly(SwFrameFormat *const pFlyFormat) +{ + assert(pFlyFormat); + assert(pFlyFormat->GetAnchor(false).GetAnchorNode() == this); + // check node type, cf. SwFormatAnchor::SetAnchor() + assert(IsTextNode() || IsStartNode() || IsTableNode()); + m_aAnchoredFlys.push_back(pFlyFormat); +} + +void SwNode::RemoveAnchoredFly(SwFrameFormat *const pFlyFormat) +{ + assert(pFlyFormat); + // cannot assert this in Remove because it is called when new anchor is already set +// assert(&pFlyFormat->GetAnchor(false).GetContentAnchor()->GetNode() == this); + assert(IsTextNode() || IsStartNode() || IsTableNode()); + auto it(std::find(m_aAnchoredFlys.begin(), m_aAnchoredFlys.end(), pFlyFormat)); + assert(it != m_aAnchoredFlys.end()); + m_aAnchoredFlys.erase(it); +} + +void SwNode::resetAndQueueAccessibilityCheck(bool bIssueObjectNameChanged) +{ + GetDoc().getOnlineAccessibilityCheck()->resetAndQueue(this, bIssueObjectNameChanged); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/node2lay.cxx b/sw/source/core/docnode/node2lay.cxx new file mode 100644 index 0000000000..607ebada8e --- /dev/null +++ b/sw/source/core/docnode/node2lay.cxx @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <calbck.hxx> +#include <node.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <ftnfrm.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <frmtool.hxx> +#include <section.hxx> +#include <node2lay.hxx> +#include <osl/diagnose.h> + +/** + * The SwNode2LayImpl class does the actual work, the SwNode2Layout class is + * just the public interface. + */ +class SwNode2LayImpl +{ + std::unique_ptr<SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti>> mpIter; + sw::BroadcastingModify* mpMod; + std::vector<SwFrame*> mvUpperFrames; // To collect the Upper + SwNodeOffset mnIndex; // The Index of the to-be-inserted Nodes + bool mbMaster : 1; // true => only Master, false => only Frames without Follow + bool mbInit : 1; // Did we already call First() at SwClient? + + SwNode2LayImpl(const SwNode2LayImpl&) = delete; + SwNode2LayImpl& operator=(const SwNode2LayImpl&) = delete; + +public: + SwNode2LayImpl( const SwNode& rNode, SwNodeOffset nIdx, bool bSearch ); + SwFrame* NextFrame(); // Returns the next "useful" Frame + SwLayoutFrame* UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ); + void SaveUpperFrames(); // Saves (and locks if needed) the pUpper + // Inserts a Frame under every pUpper of the array + void RestoreUpperFrames( SwNodes& rNds, SwNodeOffset nStt, SwNodeOffset nEnd ); + + SwFrame* GetFrame( const Point* pDocPos ) const; +}; + +static SwNode* GoNextWithFrame(const SwNodes& rNodes, SwNodeIndex *pIdx, SwFlowFrame const**const ppFrame) +{ + if( pIdx->GetIndex() >= rNodes.Count() - 1 ) + return nullptr; + + SwNodeIndex aTmp(*pIdx, +1); + SwNode* pNd = nullptr; + while( aTmp < rNodes.Count()-1 ) + { + pNd = &aTmp.GetNode(); + SwFrame const* pFound(nullptr); + if ( pNd->IsContentNode() ) + // sw_redlinehide: assume that it's OK to find a node with the same + // frame as the caller's one + pFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First(); + else if ( pNd->IsTableNode() ) + pFound = SwIterator<SwFrame,SwFormat>(*static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()).First() ; + else if( pNd->IsEndNode() && !pNd->StartOfSectionNode()->IsSectionNode() ) + { + pNd = nullptr; + break; + } + if (pFound != nullptr) + { + if (ppFrame) + { + *ppFrame = SwFlowFrame::CastFlowFrame(pFound); + assert(*ppFrame); + } + break; + } + ++aTmp; + } + + if( aTmp == rNodes.Count()-SwNodeOffset(1) ) + pNd = nullptr; + else if( pNd ) + (*pIdx) = aTmp; + return pNd; +} + +static SwNode* GoPreviousWithFrame(SwNodeIndex *pIdx, SwFlowFrame const**const ppFrame) +{ + if( !pIdx->GetIndex() ) + return nullptr; + + SwNodeIndex aTmp( *pIdx, -1 ); + SwNode* pNd(nullptr); + while( aTmp.GetIndex() ) + { + pNd = &aTmp.GetNode(); + SwFrame const* pFound(nullptr); + if ( pNd->IsContentNode() ) + // sw_redlinehide: assume that it's OK to find a node with the same + // frame as the caller's one + pFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First(); + else if ( pNd->IsTableNode() ) + pFound = SwIterator<SwFrame,SwFormat>(*static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()).First(); + else if( pNd->IsStartNode() && !pNd->IsSectionNode() ) + { + pNd = nullptr; + break; + } + if (pFound != nullptr) + { + if (ppFrame) + { + *ppFrame = SwFlowFrame::CastFlowFrame(pFound); + assert(*ppFrame); + } + break; + } + --aTmp; + } + + if( !aTmp.GetIndex() ) + pNd = nullptr; + else if( pNd ) + (*pIdx) = aTmp; + return pNd; +} + +namespace sw { + +SwFrame const* FindNeighbourFrameForNode(SwNode const& rNode) +{ + SwNodeIndex idx(rNode); + SwFlowFrame const* pFlow(nullptr); + if (SwNode *const pNode = GoPreviousWithFrame(&idx, &pFlow)) + { + if (::CheckNodesRange(rNode, idx.GetNode(), true)) + { + while (pFlow->HasFollow()) + { // try to get the one on the current page + pFlow = pFlow->GetFollow(); + } + return &pFlow->GetFrame(); + } + } + idx = rNode; + if (SwNode *const pNode = GoNextWithFrame(idx.GetNodes(), &idx, &pFlow)) + { + if (::CheckNodesRange(rNode, idx.GetNode(), true)) + { + while (pFlow->IsFollow()) + { // try to get the one on the current page + pFlow = pFlow->GetPrecede(); + } + return &pFlow->GetFrame(); + } + } + return nullptr; +} + +} + +/** + * The main purpose of this ctor is to find the right sw::BroadcastingModify to iterate over. + * + * @param bSearch true: find the next Content or TableNode which contains + * Frames (to collect the pUpper). + * Else we assume that rNode points already to such a + * Content or TableNode. + * We insert before or after it. + */ +SwNode2LayImpl::SwNode2LayImpl( const SwNode& rNode, SwNodeOffset nIdx, bool bSearch ) + : mnIndex( nIdx ), mbInit( false ) +{ + const SwNode* pNd; + if( bSearch || rNode.IsSectionNode() ) + { + // Find the next Content/TableNode that contains a Frame, so that we can add + // ourselves before/after it + if( !bSearch && rNode.GetIndex() < mnIndex ) + { + SwNodeIndex aTmp( *rNode.EndOfSectionNode(), +1 ); + pNd = GoPreviousWithFrame(&aTmp, nullptr); + if( pNd && rNode.GetIndex() > pNd->GetIndex() ) + pNd = nullptr; // Do not go over the limits + mbMaster = false; + } + else + { + SwNodeIndex aTmp( rNode, -1 ); + pNd = GoNextWithFrame(rNode.GetNodes(), &aTmp, nullptr); + mbMaster = true; + if( !bSearch && pNd && rNode.EndOfSectionIndex() < pNd->GetIndex() ) + pNd = nullptr; // Do not go over the limits + } + } + else + { + pNd = &rNode; + mbMaster = mnIndex < rNode.GetIndex(); + } + if( pNd ) + { + if( pNd->IsContentNode() ) + mpMod = const_cast<sw::BroadcastingModify*>(static_cast<sw::BroadcastingModify const *>(pNd->GetContentNode())); + else + { + assert(pNd->IsTableNode()); + mpMod = pNd->GetTableNode()->GetTable().GetFrameFormat(); + } + mpIter.reset(new SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti>(*mpMod)); + } + else + { + mpIter = nullptr; + mpMod = nullptr; + } +} + +/** + * Returns the next "useful" Frame. + * + * When calling this method for the first time, a First is triggered at the + * actual Iterator. The result is check for suitability: Follows are not + * accepted, a Master is accepted when collecting the pUpper and when + * inserting before it. + * When inserting after it, we find and return the last Follow starting + * from the Master. + * + * If the Frame is located in a SectionFrame, we check to see whether the + * SectionFrame is the suitable return value (instead of the Frame itself). + * This is the case if the to-be-inserted Node is outside of the Section. + */ +SwFrame* SwNode2LayImpl::NextFrame() +{ + SwFrame* pRet; + if( !mpIter ) + return nullptr; + if( !mbInit ) + { + pRet = mpIter->First(); + mbInit = true; + } + else + pRet = mpIter->Next(); + while( pRet ) + { + SwFlowFrame* pFlow = SwFlowFrame::CastFlowFrame( pRet ); + assert(pFlow); + // Follows are pretty volatile, thus we ignore them. + // Even if we insert after the Frame, we start from the Master + // and iterate through it until the last Follow + if( !pFlow->IsFollow() ) + { + if( !mbMaster ) + { + while( pFlow->HasFollow() ) + pFlow = pFlow->GetFollow(); + pRet = &(pFlow->GetFrame()); + } + if( pRet->IsInSct() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + // ATTENTION: If we are in a Footnote, from a Layout point of view + // it could be located in a Section with columns, although it + // should be outside of it when looking at the Nodes. + // Thus, when dealing with Footnotes, we need to check whether the + // SectionFrame is also located within the Footnote and not outside of it. + if( !pRet->IsInFootnote() || pSct->IsInFootnote() ) + { + assert(pSct && pSct->GetSection()); + SwSectionNode* pNd = pSct->GetSection()->GetFormat()->GetSectionNode(); + assert(pNd); + // If the result Frame is located within a Section Frame + // whose Section does not contain the Node, we return with + // the SectionFrame, else we return with the Content/TabFrame + if( mbMaster ) + { + if( pNd->GetIndex() >= mnIndex ) + pRet = pSct; + } + else if( pNd->EndOfSectionIndex() < mnIndex ) + pRet = pSct; + } + } + return pRet; + } + pRet = mpIter->Next(); + } + return nullptr; +} + +void SwNode2LayImpl::SaveUpperFrames() +{ + SwFrame* pFrame; + while( nullptr != (pFrame = NextFrame()) ) + { + SwFrame* pPrv = pFrame->GetPrev(); + pFrame = pFrame->GetUpper(); + if( pFrame ) + { + if( pFrame->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pFrame)->ColLock(); + else if( pFrame->IsInSct() ) + pFrame->FindSctFrame()->ColLock(); + if( pPrv && pPrv->IsSctFrame() ) + static_cast<SwSectionFrame*>(pPrv)->LockJoin(); + mvUpperFrames.push_back( pPrv ); + mvUpperFrames.push_back( pFrame ); + } + } + mpIter.reset(); + mpMod = nullptr; +} + +SwLayoutFrame* SwNode2LayImpl::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ) +{ + rpFrame = NextFrame(); + if( !rpFrame ) + return nullptr; + SwLayoutFrame* pUpper = rpFrame->GetUpper(); + if( rpFrame->IsSctFrame() ) + { + const SwNode* pNode = rNode.StartOfSectionNode(); + if( pNode->IsSectionNode() ) + { + SwFrame* pFrame = mbMaster ? rpFrame->FindPrev() : rpFrame->FindNext(); + if( pFrame && pFrame->IsSctFrame() ) + { + // pFrame could be a "dummy"-section + if( static_cast<SwSectionFrame*>(pFrame)->GetSection() && + (&static_cast<const SwSectionNode*>(pNode)->GetSection() == + static_cast<SwSectionFrame*>(pFrame)->GetSection()) ) + { + // #i22922# - consider columned sections + // 'Go down' the section frame as long as the layout frame + // is found, which would contain content. + while ( pFrame->IsLayoutFrame() && + static_cast<SwLayoutFrame*>(pFrame)->Lower() && + !static_cast<SwLayoutFrame*>(pFrame)->Lower()->IsFlowFrame() && + static_cast<SwLayoutFrame*>(pFrame)->Lower()->IsLayoutFrame() ) + { + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + } + assert(pFrame->IsLayoutFrame()); + rpFrame = mbMaster ? nullptr + : static_cast<SwLayoutFrame*>(pFrame)->Lower(); + assert((!rpFrame || rpFrame->IsFlowFrame()) && + "<SwNode2LayImpl::UpperFrame(..)> - expected sibling isn't a flow frame." ); + return static_cast<SwLayoutFrame*>(pFrame); + } + + pUpper = new SwSectionFrame(const_cast<SwSectionNode*>(static_cast<const SwSectionNode*>(pNode))->GetSection(), rpFrame); + pUpper->Paste( rpFrame->GetUpper(), + mbMaster ? rpFrame : rpFrame->GetNext() ); + // coverity[freed_arg : FALSE] - pUpper->Lower() is not freed here + static_cast<SwSectionFrame*>(pUpper)->Init(); + rpFrame = nullptr; + // 'Go down' the section frame as long as the layout frame + // is found, which would contain content. + while ( pUpper->Lower() && + !pUpper->Lower()->IsFlowFrame() && + pUpper->Lower()->IsLayoutFrame() ) + { + pUpper = static_cast<SwLayoutFrame*>(pUpper->Lower()); + } + return pUpper; + } + } + } + if( !mbMaster ) + rpFrame = rpFrame->GetNext(); + return pUpper; +} + +void SwNode2LayImpl::RestoreUpperFrames( SwNodes& rNds, SwNodeOffset nStt, SwNodeOffset nEnd ) +{ + SwNode* pNd; + SwDoc& rDoc = rNds.GetDoc(); + bool bFirst = true; + for( ; nStt < nEnd; ++nStt ) + { + SwFrame* pNew = nullptr; + SwFrame* pNxt; + SwLayoutFrame* pUp; + if( (pNd = rNds[nStt])->IsContentNode() ) + for( std::vector<SwFrame*>::size_type n = 0; n < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[n++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[n++]); + if( pNxt ) + pNxt = pNxt->GetNext(); + else + pNxt = pUp->Lower(); + pNew = static_cast<SwContentNode*>(pNd)->MakeFrame( pUp ); + pNew->Paste( pUp, pNxt ); + mvUpperFrames[n-2] = pNew; + } + else if( pNd->IsTableNode() ) + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[x++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]); + if( pNxt ) + pNxt = pNxt->GetNext(); + else + pNxt = pUp->Lower(); + pNew = static_cast<SwTableNode*>(pNd)->MakeFrame( pUp ); + assert(pNew->IsTabFrame()); + pNew->Paste( pUp, pNxt ); + static_cast<SwTabFrame*>(pNew)->RegistFlys(); + mvUpperFrames[x-2] = pNew; + } + else if( pNd->IsSectionNode() ) + { + nStt = pNd->EndOfSectionIndex(); + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ) + { + pNxt = mvUpperFrames[x++]; + if( bFirst && pNxt && pNxt->IsSctFrame() ) + static_cast<SwSectionFrame*>(pNxt)->UnlockJoin(); + pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]); + OSL_ENSURE( pUp->GetUpper() || pUp->IsFlyFrame(), "Lost Upper" ); + ::InsertCnt_( pUp, &rDoc, pNd->GetIndex(), false, nStt+1, pNxt ); + pNxt = pUp->GetLastLower(); + mvUpperFrames[x-2] = pNxt; + } + } + bFirst = false; + } + for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ++x ) + { + SwFrame* pTmp = mvUpperFrames[++x]; + if( pTmp->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pTmp)->ColUnlock(); + else if ( pTmp->IsInSct() ) + { + SwSectionFrame* pSctFrame = pTmp->FindSctFrame(); + pSctFrame->ColUnlock(); + // #i18103# - invalidate size of section in order to + // assure, that the section is formatted, unless it was 'Collocked' + // from its 'collection' until its 'restoration'. + pSctFrame->InvalidateSize_(); + } + } +} + +SwFrame* SwNode2LayImpl::GetFrame( const Point* pDocPos ) const +{ + // test if change of member pIter -> pMod broke anything + std::pair<Point, bool> tmp; + if (pDocPos) + { + tmp.first = *pDocPos; + tmp.second = false; + } + return mpMod ? ::GetFrameOfModify(nullptr, *mpMod, FRM_ALL, nullptr, pDocPos ? &tmp : nullptr) : nullptr; +} + +SwNode2Layout::SwNode2Layout( const SwNode& rNd, SwNodeOffset nIdx ) + : m_pImpl( new SwNode2LayImpl( rNd, nIdx, false ) ) +{ +} + +SwNode2LayoutSaveUpperFrames::SwNode2LayoutSaveUpperFrames(const SwNode& rNd) + : m_pImpl( new SwNode2LayImpl( rNd, rNd.GetIndex(), true ) ) +{ + m_pImpl->SaveUpperFrames(); +} + +void SwNode2LayoutSaveUpperFrames::RestoreUpperFrames( + SwNodes& rNds, SwNodeOffset const nStt, SwNodeOffset const nEnd) +{ + m_pImpl->RestoreUpperFrames( rNds, nStt, nEnd ); +} + +SwFrame* SwNode2Layout::NextFrame() +{ + return m_pImpl->NextFrame(); +} + +SwLayoutFrame* SwNode2Layout::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode ) +{ + return m_pImpl->UpperFrame( rpFrame, rNode ); +} + +SwNode2Layout::~SwNode2Layout() +{ +} + +SwNode2LayoutSaveUpperFrames::~SwNode2LayoutSaveUpperFrames() +{ +} + +SwFrame* SwNode2Layout::GetFrame( const Point* pDocPos ) const +{ + return m_pImpl->GetFrame( pDocPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/nodes.cxx b/sw/source/core/docnode/nodes.cxx new file mode 100644 index 0000000000..c6ecb9ccf9 --- /dev/null +++ b/sw/source/core/docnode/nodes.cxx @@ -0,0 +1,2574 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <stdlib.h> +#include <libxml/xmlwriter.h> +#include <osl/diagnose.h> +#include <tools/json_writer.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <sfx2/viewsh.hxx> +#include <comphelper/lok.hxx> + +#include <node.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <txtfld.hxx> +#include <fmtfld.hxx> +#include <numrule.hxx> +#include <ndtxt.hxx> +#include <ndnotxt.hxx> +#include <swtable.hxx> +#include <section.hxx> +#include <ddefld.hxx> +#include <swddetbl.hxx> +#include <txtatr.hxx> +#include <tox.hxx> +#include <fmtrfmrk.hxx> +#include <fmtftn.hxx> +#include <docsh.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> + +typedef std::vector<SwStartNode*> SwStartNodePointers; + +// function to determine the highest level in the given range +static sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ); + +/** Constructor + * + * creates the base sections (PostIts, Inserts, AutoText, RedLines, Content) + * + * @param rDocument TODO: provide documentation + */ +SwNodes::SwNodes( SwDoc& rDocument ) + : m_vIndices(nullptr), m_rMyDoc( rDocument ) +{ + m_bInNodesDel = m_bInDelUpdOutline = false; + + SwNodeOffset nPos(0); + SwStartNode* pSttNd = new SwStartNode( *this, nPos++ ); + m_pEndOfPostIts = new SwEndNode( *this, nPos++, *pSttNd ); + + SwStartNode* pTmp = new SwStartNode( *this, nPos++ ); + m_pEndOfInserts = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfAutotext = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfRedlines = new SwEndNode( *this, nPos++, *pTmp ); + + pTmp = new SwStartNode( *this, nPos++ ); + pTmp->m_pStartOfSection = pSttNd; + m_pEndOfContent.reset(new SwEndNode( *this, nPos++, *pTmp )); + + m_aOutlineNodes.clear(); +} + +/** Destructor + * + * Deletes all nodes whose pointer are in a dynamic array. This should be no + * problem as nodes cannot be created outside this array and, thus, cannot be + * part of multiple arrays. + */ +SwNodes::~SwNodes() +{ + m_aOutlineNodes.clear(); + + { + SwNodeIndex aNdIdx( *this ); + while( true ) + { + SwNode *pNode = &aNdIdx.GetNode(); + if( pNode == m_pEndOfContent.get() ) + break; + + ++aNdIdx; + delete pNode; + } + } + + // here, all SwNodeIndices must be unregistered + m_pEndOfContent.reset(); +} + +static bool IsInsertOutline(SwNodes const& rNodes, SwNodeOffset const nIndex) +{ + if (!rNodes.IsDocNodes()) + { + return false; + } + return nIndex < rNodes.GetEndOfRedlines().StartOfSectionNode()->GetIndex() + || rNodes.GetEndOfRedlines().GetIndex() < nIndex; +} + +void SwNodes::ChgNode( SwNodeIndex const & rDelPos, SwNodeOffset nSz, + SwNodeIndex& rInsPos, bool bNewFrames ) +{ + // no need for frames in the UndoArea + SwNodes& rNds = rInsPos.GetNodes(); + const SwNode* pPrevInsNd = rNds[ rInsPos.GetIndex() -SwNodeOffset(1) ]; + + // declare all fields as invalid, updating will happen + // in the idle-handler of the doc + if( GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rDelPos.GetNode(), nSz ) && + &rNds.GetDoc() != &GetDoc() ) + rNds.GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + + // NEVER include nodes from the RedLineArea + SwNodeOffset nNd = rInsPos.GetIndex(); + bool const bInsOutlineIdx = IsInsertOutline(rNds, nNd); + + if( &rNds == this ) // if in the same node array -> move + { + // Move order: from front to back, so that new entries are added at + // first position, thus, deletion position stays the same + const SwNodeOffset nDiff(rDelPos.GetIndex() < rInsPos.GetIndex() ? 0 : 1); + + for( SwNodeOffset n = rDelPos.GetIndex(); nSz; n += nDiff, --nSz ) + { + SwNodeIndex aDelIdx( *this, n ); + SwNode& rNd = aDelIdx.GetNode(); + + // #i57920# - correction of refactoring done by cws swnumtree: + // - <SwTextNode::SetLevel( NO_NUMBERING ) is deprecated and + // set <IsCounted> state of the text node to <false>, which + // isn't correct here. + if ( rNd.IsTextNode() ) + { + SwTextNode* pTextNode = rNd.GetTextNode(); + + pTextNode->RemoveFromList(); + + if (pTextNode->IsOutline()) + { + SwNode* pSrch = &rNd; + m_aOutlineNodes.erase( pSrch ); + } + } + + BigPtrArray::Move( sal_Int32(aDelIdx.GetIndex()), sal_Int32(rInsPos.GetIndex()) ); + + if( rNd.IsTextNode() ) + { + SwTextNode& rTextNd = static_cast<SwTextNode&>(rNd); + + rTextNd.AddToList(); + + if (bInsOutlineIdx && rTextNd.IsOutline()) + { + SwNode* pSrch = &rNd; + m_aOutlineNodes.insert( pSrch ); + } + rTextNd.InvalidateNumRule(); + + if( RES_CONDTXTFMTCOLL == rTextNd.GetTextColl()->Which() ) + rTextNd.ChkCondColl(); + } + else if( rNd.IsContentNode() ) + static_cast<SwContentNode&>(rNd).InvalidateNumRule(); + } + } + else + { + bool bSavePersData(GetDoc().GetIDocumentUndoRedo().IsUndoNodes(rNds)); + bool bRestPersData(GetDoc().GetIDocumentUndoRedo().IsUndoNodes(*this)); + SwDoc* pDestDoc = &rNds.GetDoc() != &GetDoc() ? &rNds.GetDoc() : nullptr; + OSL_ENSURE(!pDestDoc, "SwNodes::ChgNode(): " + "the code to handle text fields here looks broken\n" + "if the target is in a different document."); + if( !bRestPersData && !bSavePersData && pDestDoc ) + bSavePersData = bRestPersData = true; + + OUString sNumRule; + for( SwNodeOffset n(0); n < nSz; n++ ) + { + SwNode* pNd = &rDelPos.GetNode(); + + // NoTextNode keep their persistent data + if( pNd->IsNoTextNode() ) + { + if( bSavePersData ) + static_cast<SwNoTextNode*>(pNd)->SavePersistentData(); + } + else if( pNd->IsTextNode() ) + { + SwTextNode* pTextNd = static_cast<SwTextNode*>(pNd); + + // remove outline index from old nodes array + if (pTextNd->IsOutline()) + { + m_aOutlineNodes.erase( pNd ); + } + + // copy rules if needed + if( pDestDoc ) + { + const SwNumRule* pNumRule = pTextNd->GetNumRule(); + if( pNumRule && sNumRule != pNumRule->GetName() ) + { + sNumRule = pNumRule->GetName(); + SwNumRule* pDestRule = pDestDoc->FindNumRulePtr( sNumRule ); + if( pDestRule ) + pDestRule->SetInvalidRule( true ); + else + pDestDoc->MakeNumRule( sNumRule, pNumRule ); + } + } + else + { + // if movement into the UndoNodes-array, update numbering + if (sw::HasNumberingWhichNeedsLayoutUpdate(*pTextNd)) + { + pTextNd->InvalidateNumRule(); + } + } + + pTextNd->RemoveFromList(); + } + + RemoveNode( rDelPos.GetIndex(), SwNodeOffset(1), false ); // move indices + SwContentNode * pCNd = pNd->GetContentNode(); + rNds.InsertNode( pNd, rInsPos ); + + if( pCNd ) + { + SwTextNode* pTextNd = pCNd->GetTextNode(); + if( pTextNd ) + { + SwpHints * const pHts = pTextNd->GetpSwpHints(); + // OutlineNodes set the new nodes in the array + if (bInsOutlineIdx && pTextNd->IsOutline()) + { + rNds.m_aOutlineNodes.insert( pTextNd ); + } + + pTextNd->AddToList(); + + // special treatment for fields + if( pHts && pHts->Count() ) + { + bool const bToUndo = !pDestDoc && + GetDoc().GetIDocumentUndoRedo().IsUndoNodes(rNds); + for( size_t i = pHts->Count(); i; ) + { + SwTextAttr * const pAttr = pHts->Get( --i ); + switch ( pAttr->Which() ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + { + SwTextField* pTextField = static_txtattr_cast<SwTextField*>(pAttr); + rNds.GetDoc().getIDocumentFieldsAccess().InsDelFieldInFieldLst( !bToUndo, *pTextField ); + + const SwFieldType* pTyp = pTextField->GetFormatField().GetField()->GetTyp(); + if ( SwFieldIds::Postit == pTyp->Which() ) + { + rNds.GetDoc().GetDocShell()->Broadcast( + SwFormatFieldHint( + &pTextField->GetFormatField(), + ( pTextField->GetFormatField().IsFieldInDoc() + ? SwFormatFieldHintWhich::INSERTED + : SwFormatFieldHintWhich::REMOVED ) ) ); + } + else if( SwFieldIds::Dde == pTyp->Which() ) + { + if( bToUndo ) + const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pTyp))->DecRefCnt(); + else + const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pTyp))->IncRefCnt(); + } + static_cast<SwFormatField&>(pAttr->GetAttr()) + .InvalidateField(); + } + break; + + case RES_TXTATR_FTN: + static_cast<SwFormatFootnote&>(pAttr->GetAttr()) + .InvalidateFootnote(); + break; + + case RES_TXTATR_TOXMARK: + static_cast<SwTOXMark&>(pAttr->GetAttr()) + .InvalidateTOXMark(); + break; + + case RES_TXTATR_REFMARK: + static_cast<SwFormatRefMark&>(pAttr->GetAttr()) + .InvalidateRefMark(); + break; + + case RES_TXTATR_META: + case RES_TXTATR_METAFIELD: + { + SwTextMeta *const pTextMeta( + static_txtattr_cast<SwTextMeta*>(pAttr)); + // force removal of UNO object + pTextMeta->ChgTextNode(nullptr); + pTextMeta->ChgTextNode(pTextNd); + } + break; + + default: + break; + } + } + } + + if( RES_CONDTXTFMTCOLL == pTextNd->GetTextColl()->Which() ) + pTextNd->ChkCondColl(); + } + else + { + // Moved into different Docs? Persist data again! + if( pCNd->IsNoTextNode() && bRestPersData ) + static_cast<SwNoTextNode*>(pCNd)->RestorePersistentData(); + } + + // reset Accessibility issue state + pCNd->resetAndQueueAccessibilityCheck(); + } + } + } + + // declare all fields as invalid, updating will happen + // in the idle-handler of the doc + GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + if( &rNds.GetDoc() != &GetDoc() ) + rNds.GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, SwNodeOffset(0) ); + + if( bNewFrames ) + bNewFrames = &GetDoc().GetNodes() == &rNds && + GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( !bNewFrames ) + return; + + // get the frames: + SwNodeIndex aIdx( *pPrevInsNd, 1 ); + SwNode* pFrameNd = rNds.FindPrvNxtFrameNode( aIdx.GetNode(), + rNds[ rInsPos.GetIndex() - 1 ] ); + + if( !pFrameNd ) + return; + + while( aIdx != rInsPos ) + { + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd ) + { + if( pFrameNd->IsTableNode() ) + static_cast<SwTableNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); + else if( pFrameNd->IsSectionNode() ) + static_cast<SwSectionNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(aIdx); + else + static_cast<SwContentNode*>(pFrameNd)->MakeFramesForAdjacentContentNode(*pCNd); + pFrameNd = pCNd; + } + ++aIdx; + } +} + +// TODO: provide documentation +/** move the node pointer + * + * Move the node pointer from "(inclusive) start position to (exclusive) end + * position" to target position. + * If the target is in front of the first or in the area between first and + * last element to move, nothing happens. + * If the area to move is empty or the end position is before the start + * position, nothing happens. + * + * @param aRange range to move (excluding end node) + * @return + */ +bool SwNodes::MoveNodes( const SwNodeRange& aRange, SwNodes & rNodes, + SwNode& rPos, bool bNewFrames ) +{ + SwNode * pCurrentNode; + if( rPos.GetIndex() == SwNodeOffset(0) || + ( (pCurrentNode = &rPos)->GetStartNode() && + !pCurrentNode->StartOfSectionIndex() )) + return false; + + SwNodeRange aRg( aRange ); + + // skip "simple" start or end nodes + while( SwNodeType::Start == (pCurrentNode = &aRg.aStart.GetNode())->GetNodeType() + || ( pCurrentNode->IsEndNode() && + !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) + ++aRg.aStart; + --aRg.aStart; + + // if aEnd-1 points to no ContentNode, search previous one + --aRg.aEnd; + while( ( (( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && + !pCurrentNode->IsSectionNode() ) || + ( pCurrentNode->IsEndNode() && + SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) && + aRg.aEnd > aRg.aStart ) + --aRg.aEnd; + + // if in same array, check insertion position + if( aRg.aStart >= aRg.aEnd ) + return false; + + if( this == &rNodes ) + { + if( ( rPos.GetIndex()-SwNodeOffset(1) >= aRg.aStart.GetIndex() && + rPos.GetIndex()-SwNodeOffset(1) < aRg.aEnd.GetIndex()) || + ( rPos.GetIndex()-SwNodeOffset(1) == aRg.aEnd.GetIndex() ) ) + return false; + } + + SwNodeOffset nInsPos(0); // counter for tmp array + + // array as a stack, storing all StartOfSelections + SwStartNodePointers aSttNdStack; + SwStartNodePointers::size_type nLevel = 0; // level counter + + // set start index + SwNodeIndex aIdx( rPos ); + + SwStartNode* pStartNode = aIdx.GetNode().m_pStartOfSection; + aSttNdStack.insert( aSttNdStack.begin(), pStartNode ); + + SwNodeRange aOrigInsPos( aIdx, SwNodeOffset(-1), aIdx ); // original insertion position + + // call DelFrames/MakeFrames for the upmost SectionNode + int nSectNdCnt = 0; + bool bSaveNewFrames = bNewFrames; + + // continue until everything has been moved + while( aRg.aStart < aRg.aEnd ) + { + pCurrentNode = &aRg.aEnd.GetNode(); + switch( pCurrentNode->GetNodeType() ) + { + case SwNodeType::End: + { + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = SwNodeOffset(0); + } + + SwStartNode* pSttNd = pCurrentNode->m_pStartOfSection; + if( pSttNd->IsTableNode() ) + { + SwTableNode* pTableNd = static_cast<SwTableNode*>(pSttNd); + + // move the whole table/range + nInsPos = (aRg.aEnd.GetIndex() - + pSttNd->GetIndex() )+1; + aRg.aEnd -= nInsPos; + + // NEVER include nodes from the RedLineArea + SwNodeOffset nNd = aIdx.GetIndex(); + bool const bInsOutlineIdx = IsInsertOutline(rNodes, nNd); + + if( bNewFrames ) + // delete all frames + pTableNd->DelFrames(nullptr); + if( &rNodes == this ) // move into self? + { + // move all Start/End/ContentNodes + // ContentNodes: delete also the frames! + pTableNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; + for( SwNodeOffset n(0); n < nInsPos; ++n ) + { + SwNodeIndex aMvIdx( aRg.aEnd, 1 ); + SwContentNode* pCNd = nullptr; + SwNode* pTmpNd = &aMvIdx.GetNode(); + if( pTmpNd->IsContentNode() ) + { + pCNd = static_cast<SwContentNode*>(pTmpNd); + if( pTmpNd->IsTextNode() ) + static_cast<SwTextNode*>(pTmpNd)->RemoveFromList(); + + // remove outline index from old nodes array + if (pCNd->IsTextNode() && pCNd->GetTextNode()->IsOutline()) + { + m_aOutlineNodes.erase( pCNd ); + } + else + pCNd = nullptr; + } + + BigPtrArray::Move( sal_Int32(aMvIdx.GetIndex()), sal_Int32(aIdx.GetIndex()) ); + + if( bInsOutlineIdx && pCNd ) + m_aOutlineNodes.insert( pCNd ); + if( pTmpNd->IsTextNode() ) + static_cast<SwTextNode*>(pTmpNd)->AddToList(); + } + } + else + { + // get StartNode + // Even aIdx points to a startnode, we need the startnode + // of the environment of aIdx (#i80941) + SwStartNode* pSttNode = aIdx.GetNode().m_pStartOfSection; + + // get all boxes with content because their indices + // pointing to the StartNodes need to be reset + // (copying the array and deleting all found ones eases + // searching) + SwNodeIndex aMvIdx( aRg.aEnd, 1 ); + for( SwNodeOffset n(0); n < nInsPos; ++n ) + { + SwNode* pNd = &aMvIdx.GetNode(); + + const bool bOutlNd = pNd->IsTextNode() && pNd->GetTextNode()->IsOutline(); + // delete outline indices from old node array + if( bOutlNd ) + m_aOutlineNodes.erase( pNd ); + + RemoveNode( aMvIdx.GetIndex(), SwNodeOffset(1), false ); + pNd->m_pStartOfSection = pSttNode; + rNodes.InsertNode( pNd, aIdx ); + + // set correct indices in Start/EndNodes + if( bInsOutlineIdx && bOutlNd ) + // and put them into the new node array + rNodes.m_aOutlineNodes.insert( pNd ); + else if( pNd->IsStartNode() ) + pSttNode = static_cast<SwStartNode*>(pNd); + else if( pNd->IsEndNode() ) + { + pSttNode->m_pEndOfSection = static_cast<SwEndNode*>(pNd); + if( pSttNode->IsSectionNode() ) + static_cast<SwSectionNode*>(pSttNode)->NodesArrChgd(); + pSttNode = pSttNode->m_pStartOfSection; + } + } + + if( auto pDDETable = dynamic_cast<SwDDETable*>(&pTableNd->GetTable()) ) + { + SwDDEFieldType* pTyp = pDDETable->GetDDEFieldType(); + if( pTyp ) + { + if( rNodes.IsDocNodes() ) + pTyp->IncRefCnt(); + else + pTyp->DecRefCnt(); + } + } + + if (GetDoc().GetIDocumentUndoRedo().IsUndoNodes(rNodes)) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + pTableFormat->GetNotifier().Broadcast(SfxHint(SfxHintId::Dying)); + } + } + if( bNewFrames ) + { + pTableNd->MakeOwnFrames(); + } + aIdx -= nInsPos; + nInsPos = SwNodeOffset(0); + } + else if( pSttNd->GetIndex() < aRg.aStart.GetIndex() ) + { + // SectionNode: not the whole section will be moved, thus, + // move only the ContentNodes + // StartNode: create a new section at the given position + do { // middle check loop + if( !pSttNd->IsSectionNode() ) + { + // create StartNode and EndNode at InsertPos + SwStartNode* pTmp = new SwStartNode( aIdx.GetNode(), + SwNodeType::Start, +/*?? NodeType ??*/ SwNormalStartNode ); + + nLevel++; // put the index to StartNode on the stack + aSttNdStack.insert( aSttNdStack.begin() + nLevel, pTmp ); + + // create EndNode + new SwEndNode( aIdx.GetNode(), *pTmp ); + } + else if (GetDoc().GetIDocumentUndoRedo().IsUndoNodes( + rNodes)) + { + // use placeholder in UndoNodes array + new SwPlaceholderNode(aIdx.GetNode()); + } + else + { + // JP 18.5.2001 (Bug 70454) creating new section? + --aRg.aEnd; + break; + + } + + --aRg.aEnd; + --aIdx; + } while( false ); + } + else + { + // move StartNode and EndNode in total + + // if Start is exactly the Start of the area, + // then the Node needs to be re-visited + if( &aRg.aStart.GetNode() == pSttNd ) + --aRg.aStart; + + SwSectionNode* pSctNd = pSttNd->GetSectionNode(); + if( bNewFrames && pSctNd ) + { // tdf#135056 skip over code in DelFrames() that moves + // SwNodeIndex around because in case of nested + // sections, m_pStartOfSection will point between + // undo nodes-array and doc nodes-array + pSctNd->DelFrames(nullptr, true); + } + + RemoveNode( aRg.aEnd.GetIndex(), SwNodeOffset(1), false ); // delete EndNode + SwNodeOffset nSttPos = pSttNd->GetIndex(); + + // this StartNode will be removed later + SwStartNode* pTmpSttNd = new SwStartNode( *this, nSttPos+1 ); + pTmpSttNd->m_pStartOfSection = pSttNd->m_pStartOfSection; + + RemoveNode( nSttPos, SwNodeOffset(1), false ); // delete SttNode + + pSttNd->m_pStartOfSection = aIdx.GetNode().m_pStartOfSection; + rNodes.InsertNode( pSttNd, aIdx ); + rNodes.InsertNode( pCurrentNode, aIdx ); + --aIdx; + pSttNd->m_pEndOfSection = static_cast<SwEndNode*>(pCurrentNode); + + --aRg.aEnd; + + nLevel++; // put the index pointing to the StartNode onto the stack + aSttNdStack.insert( aSttNdStack.begin() + nLevel, pSttNd ); + + // reset remaining indices if SectionNode + if( pSctNd ) + { + pSctNd->NodesArrChgd(); + ++nSectNdCnt; + // tdf#132326 do not let frames survive in undo nodes + if (!GetDoc().GetIDocumentUndoRedo().IsUndoNodes(rNodes)) + { + bNewFrames = false; + } + } + } + } + break; + + case SwNodeType::Section: + if( !nLevel && + GetDoc().GetIDocumentUndoRedo().IsUndoNodes(rNodes)) + { + // here, a SectionDummyNode needs to be inserted at the current position + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = SwNodeOffset(0); + } + new SwPlaceholderNode(aIdx.GetNode()); + --aRg.aEnd; + --aIdx; + break; + } + [[fallthrough]]; + case SwNodeType::Table: + case SwNodeType::Start: + { + // empty section -> nothing to do + // and only if it's a top level section + if( !nInsPos && !nLevel ) + { + --aRg.aEnd; + break; + } + + if( !nLevel ) // level is decreasing + { + // create decrease + SwNodeIndex aTmpSIdx( aOrigInsPos.aStart, 1 ); + SwStartNode* pTmpStt = new SwStartNode( aTmpSIdx.GetNode(), + SwNodeType::Start, + static_cast<SwStartNode*>(pCurrentNode)->GetStartNodeType() ); + + --aTmpSIdx; + + SwNodeIndex aTmpEIdx( aOrigInsPos.aEnd ); + new SwEndNode( aTmpEIdx.GetNode(), *pTmpStt ); + --aTmpEIdx; + ++aTmpSIdx; + + // set correct StartOfSection + ++aRg.aEnd; + { + SwNodeIndex aCntIdx( aRg.aEnd ); + for( SwNodeOffset n(0); n < nInsPos; n++, ++aCntIdx) + aCntIdx.GetNode().m_pStartOfSection = pTmpStt; + } + + // also set correct StartNode for all decreased nodes + while( aTmpSIdx < aTmpEIdx ) + if( nullptr != (( pCurrentNode = &aTmpEIdx.GetNode())->GetEndNode()) ) + aTmpEIdx = pCurrentNode->StartOfSectionIndex(); + else + { + pCurrentNode->m_pStartOfSection = pTmpStt; + --aTmpEIdx; + } + + --aIdx; // after the inserted StartNode + --aRg.aEnd; // before StartNode + // copy array. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos+1; + nInsPos = SwNodeOffset(0); + } + else // all nodes between StartNode and EndNode were moved + { + OSL_ENSURE( pCurrentNode == aSttNdStack[nLevel] || + ( pCurrentNode->IsStartNode() && + aSttNdStack[nLevel]->IsSectionNode()), + "wrong StartNode" ); + + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos+1; // before inserted StartNode + nInsPos = SwNodeOffset(0); + + // remove pointer from node array + RemoveNode( aRg.aEnd.GetIndex(), SwNodeOffset(1), true ); + --aRg.aEnd; + + SwSectionNode* pSectNd = aSttNdStack[ nLevel ]->GetSectionNode(); + if( pSectNd && !--nSectNdCnt ) + { + SwNodeIndex aTmp( *pSectNd ); + pSectNd->MakeOwnFrames(&aTmp); + bNewFrames = bSaveNewFrames; + } + aSttNdStack.erase( aSttNdStack.begin() + nLevel ); // remove from stack + nLevel--; + } + + // delete all resulting empty start/end node pairs + SwNode* pTmpNode = (*this)[ aRg.aEnd.GetIndex()+1 ]->GetEndNode(); + if( pTmpNode && SwNodeType::Start == (pCurrentNode = &aRg.aEnd.GetNode()) + ->GetNodeType() && pCurrentNode->StartOfSectionIndex() && + pTmpNode->StartOfSectionNode() == pCurrentNode ) + { + DelNodes( aRg.aEnd, SwNodeOffset(2) ); + --aRg.aEnd; + } + } + break; + + case SwNodeType::Text: + //Add special function to text node. + { + if( bNewFrames && pCurrentNode->GetContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->DelFrames(nullptr); + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + --aRg.aEnd; + } + break; + case SwNodeType::Grf: + case SwNodeType::Ole: + { + if( bNewFrames && pCurrentNode->GetContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->DelFrames(nullptr); + + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + --aRg.aEnd; + + // reset Accessibility issue state + pCurrentNode->resetAndQueueAccessibilityCheck(); + } + break; + + case SwNodeType::PlaceHolder: + if (GetDoc().GetIDocumentUndoRedo().IsUndoNodes(*this)) + { + if( &rNodes == this ) // inside UndoNodesArray + { + // move everything + pCurrentNode->m_pStartOfSection = aSttNdStack[ nLevel ]; + nInsPos++; + } + else // move into "normal" node array + { + // than a SectionNode (start/end) is needed at the current + // InsPos; if so skip it, otherwise ignore current node + if( nInsPos ) // move everything until here + { + // delete and copy. CAUTION: all indices after + // "aRg.aEnd+1" will be moved as well! + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + aIdx -= nInsPos; + nInsPos = SwNodeOffset(0); + } + SwNode* pTmpNd = &aIdx.GetNode(); + if( pTmpNd->IsSectionNode() || + pTmpNd->StartOfSectionNode()->IsSectionNode() ) + --aIdx; // skip + } + } + else { + assert(!"How can this node be in the node array?"); + } + --aRg.aEnd; + break; + + default: + assert(!"Unknown node type"); + break; + } + } + + if( nInsPos ) // copy remaining rest + { + // rest should be ok + SwNodeIndex aSwIndex( aRg.aEnd, 1 ); + ChgNode( aSwIndex, nInsPos, aIdx, bNewFrames ); + } + ++aRg.aEnd; // again, exclusive end + + // delete all resulting empty start/end node pairs + if( ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() && + aRg.aEnd.GetNode().GetEndNode() ) + DelNodes( aRg.aStart, SwNodeOffset(2) ); + + // initialize numbering update + ++aOrigInsPos.aStart; + // Moved in same node array? Then call update top down! + if( this == &rNodes && + aRg.aEnd.GetIndex() >= aOrigInsPos.aStart.GetIndex() ) + { + UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + } + else + { + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + rNodes.UpdateOutlineIdx( aOrigInsPos.aStart.GetNode() ); + } + + return true; +} + +/** create a start/end section pair + * + * Other nodes might be in between. + * + * After this method call, the start node of pRange will be pointing to the + * first node after the start section node and the end node will be the index + * of the end section node. If this method is called multiple times with the + * same input, multiple sections containing the previous ones will be created + * (no content nodes between start or end node). + * + * @note Start and end node of the range must be on the same level but MUST + * NOT be on the top level. + * + * @param [IN,OUT] pRange the range (excl. end) + * @param eSttNdTyp type of the start node + */ +void SwNodes::SectionDown(SwNodeRange *pRange, SwStartNodeType eSttNdTyp ) +{ + if( pRange->aStart >= pRange->aEnd || + pRange->aEnd >= Count() || + !::CheckNodesRange(pRange->aStart.GetNode(), pRange->aEnd.GetNode(), false)) + { + return; + } + + // If the beginning of a range is before or at a start node position, so + // delete it, otherwise empty S/E or E/S nodes would be created. + // For other nodes, create a new start node. + SwNode * pCurrentNode = &pRange->aStart.GetNode(); + SwNodeIndex aTmpIdx( *pCurrentNode->StartOfSectionNode() ); + + if( pCurrentNode->GetEndNode() ) + DelNodes( pRange->aStart ); // prevent empty section + else + { + // insert a new StartNode + SwNode* pSttNd = new SwStartNode( pRange->aStart.GetNode(), SwNodeType::Start, eSttNdTyp ); + pRange->aStart = *pSttNd; + aTmpIdx = pRange->aStart; + } + + // If the end of a range is before or at a StartNode, so delete it, + // otherwise empty S/E or E/S nodes would be created. + // For other nodes, insert a new end node. + --pRange->aEnd; + if( pRange->aEnd.GetNode().GetStartNode() ) + DelNodes( pRange->aEnd ); + else + { + ++pRange->aEnd; + // insert a new EndNode + new SwEndNode( pRange->aEnd.GetNode(), *pRange->aStart.GetNode().GetStartNode() ); + } + --pRange->aEnd; + + SectionUpDown( aTmpIdx, pRange->aEnd ); +} + +/** increase level of the given range + * + * The range contained in pRange will be lifted to the next higher level. + * This is done by adding an end node at pRange.start and a start node at + * pRange.end. Furthermore all indices for this range will be updated. + * + * After this method call, the start node of pRange will be pointing to the + * first node inside the lifted range and the end node will be pointing to the + * last position inside the lifted range. + * + * @param [IN,OUT] pRange the range of nodes where the level should be increased + */ +void SwNodes::SectionUp(SwNodeRange *pRange) +{ + if( pRange->aStart >= pRange->aEnd || + pRange->aEnd >= Count() || + !::CheckNodesRange(pRange->aStart.GetNode(), pRange->aEnd.GetNode(), false) || + ( HighestLevel( *this, *pRange ) <= 1 )) + { + return; + } + + // If the beginning of a range is before or at a start node position, so + // delete it, otherwise empty S/E or E/S nodes would be created. + // For other nodes, create a new start node. + SwNode * pCurrentNode = &pRange->aStart.GetNode(); + SwNodeIndex aIdx( *pCurrentNode->StartOfSectionNode() ); + if( pCurrentNode->IsStartNode() ) // is StartNode itself + { + SwEndNode* pEndNd = pRange->aEnd.GetNode().GetEndNode(); + if (pEndNd && pCurrentNode == pEndNd->m_pStartOfSection) + { + // there was a pairwise reset, adjust only those in the range + SwStartNode* pTmpSttNd = pCurrentNode->m_pStartOfSection; + RemoveNode( pRange->aStart.GetIndex(), SwNodeOffset(1), true ); + RemoveNode( pRange->aEnd.GetIndex(), SwNodeOffset(1), true ); + + SwNodeIndex aTmpIdx( pRange->aStart ); + while( aTmpIdx < pRange->aEnd ) + { + pCurrentNode = &aTmpIdx.GetNode(); + pCurrentNode->m_pStartOfSection = pTmpSttNd; + if( pCurrentNode->IsStartNode() ) + aTmpIdx = pCurrentNode->EndOfSectionIndex() + 1; + else + ++aTmpIdx; + } + return ; + } + DelNodes( pRange->aStart ); + } + else if( aIdx == pRange->aStart.GetIndex()-1 ) // before StartNode + DelNodes( aIdx ); + else + new SwEndNode( pRange->aStart.GetNode(), *aIdx.GetNode().GetStartNode() ); + + // If the end of a range is before or at a StartNode, so delete it, + // otherwise empty S/E or E/S nodes would be created. + // For other nodes, insert a new end node. + SwNodeIndex aTmpIdx( pRange->aEnd ); + if( pRange->aEnd.GetNode().IsEndNode() ) + DelNodes( pRange->aEnd ); + else + { + new SwStartNode( pRange->aEnd.GetNode() ); +/*?? which NodeType ??*/ + aTmpIdx = *pRange->aEnd.GetNode().EndOfSectionNode(); + --pRange->aEnd; + } + + SectionUpDown( aIdx, aTmpIdx ); +} + +/** correct indices after movement + * + * Update all indices after movement so that the levels are consistent again. + * + * @param aStart index of the start node + * @param aEnd index of the end point + * + * @see SwNodes::SectionUp + * @see SwNodes::SectionDown + */ +void SwNodes::SectionUpDown( const SwNodeIndex & aStart, const SwNodeIndex & aEnd ) +{ + SwNodeIndex aTmpIdx( aStart, +1 ); + // array forms a stack, holding all StartOfSelections + SwStartNodePointers aSttNdStack; + SwStartNode* pTmp = aStart.GetNode().GetStartNode(); + aSttNdStack.push_back( pTmp ); + + // loop until the first start node that needs to be change was found + // (the indices are updated from the end node backwards to the start) + for( ;; ++aTmpIdx ) + { + SwNode * pCurrentNode = &aTmpIdx.GetNode(); + pCurrentNode->m_pStartOfSection = aSttNdStack[ aSttNdStack.size()-1 ]; + + if( pCurrentNode->GetStartNode() ) + { + pTmp = static_cast<SwStartNode*>(pCurrentNode); + aSttNdStack.push_back( pTmp ); + } + else if( pCurrentNode->GetEndNode() ) + { + SwStartNode* pSttNd = aSttNdStack[ aSttNdStack.size() - 1 ]; + pSttNd->m_pEndOfSection = static_cast<SwEndNode*>(pCurrentNode); + aSttNdStack.pop_back(); + if( !aSttNdStack.empty() ) + continue; // still enough EndNodes on the stack + + else if( aTmpIdx < aEnd ) // too many StartNodes + // if the end is not reached, yet, get the start of the section above + { + aSttNdStack.insert( aSttNdStack.begin(), pSttNd->m_pStartOfSection ); + } + else // finished, as soon as out of the range + break; + } + } +} + +void SwNodes::Delete(const SwNodeIndex &rIndex, SwNodeOffset nNodes) +{ + Delete(rIndex.GetNode(), nNodes); +} + +/** delete nodes + * + * This is a specific implementation of a delete function for a variable array. + * It is necessary as there might be inconsistencies after deleting start or + * end nodes. This method can clean those up. + * + * @param rIndex position to delete at (unchanged afterwards) + * @param nNodes number of nodes to delete (default: 1) + */ +void SwNodes::Delete(const SwNode &rIndex, SwNodeOffset nNodes) +{ + int nLevel = 0; // level counter + SwNode * pCurrentNode; + + SwNodeOffset nCnt = Count() - rIndex.GetIndex() - 1; + if( nCnt > nNodes ) nCnt = nNodes; + + if( nCnt == SwNodeOffset(0) ) // no count -> return + return; + + SwNodeRange aRg( rIndex, SwNodeOffset(0), rIndex, nCnt-1 ); + // check if [rIndex..rIndex + nCnt] is larger than the range + if( ( !aRg.aStart.GetNode().StartOfSectionIndex() && + !aRg.aStart.GetIndex() ) || + !::CheckNodesRange(aRg.aStart.GetNode(), aRg.aEnd.GetNode(), false)) + { + return; + } + + // if aEnd is not on a ContentNode, search the previous one + while( ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() || + ( pCurrentNode->GetEndNode() && + !pCurrentNode->m_pStartOfSection->IsTableNode() )) + --aRg.aEnd; + + nCnt = SwNodeOffset(0); +//TODO: check/improve comment + // increase start so that we are able to use "<" (using "<=" might cause + // problems if aEnd == aStart and aEnd is deleted, so aEnd <= aStart) + --aRg.aStart; + + bool bSaveInNodesDel = m_bInNodesDel; + m_bInNodesDel = true; + bool bUpdateOutline = false; + + // loop until everything is deleted + while( aRg.aStart < aRg.aEnd ) + { + pCurrentNode = &aRg.aEnd.GetNode(); + + if( pCurrentNode->GetEndNode() ) + { + // delete the whole section? + if( pCurrentNode->StartOfSectionIndex() > aRg.aStart.GetIndex() ) + { + SwTableNode* pTableNd = pCurrentNode->m_pStartOfSection->GetTableNode(); + if( pTableNd ) + pTableNd->DelFrames(); + + SwNode *pNd, *pChkNd = pCurrentNode->m_pStartOfSection; + SwOutlineNodes::size_type nIdxPos; + do { + pNd = &aRg.aEnd.GetNode(); + + if( pNd->IsTextNode() ) + { + SwTextNode *const pTextNode(pNd->GetTextNode()); + if (pTextNode->IsOutline() && + m_aOutlineNodes.Seek_Entry( pNd, &nIdxPos )) + { + // remove outline indices + m_aOutlineNodes.erase_at(nIdxPos); + bUpdateOutline = true; + } + pTextNode->InvalidateNumRule(); + } + else if( pNd->IsEndNode() && + pNd->m_pStartOfSection->IsTableNode() ) + static_cast<SwTableNode*>(pNd->m_pStartOfSection)->DelFrames(); + + --aRg.aEnd; + nCnt++; + + } while( pNd != pChkNd ); + } + else + { + RemoveNode( aRg.aEnd.GetIndex()+1, nCnt, true ); // delete + nCnt = SwNodeOffset(0); + --aRg.aEnd; // before the EndNode + nLevel++; + } + } + else if( pCurrentNode->GetStartNode() ) // found StartNode + { + if( nLevel == 0 ) // decrease one level + { + if( nCnt ) + { + // now delete array + ++aRg.aEnd; + RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); + nCnt = SwNodeOffset(0); + } + } + else // remove all nodes between start and end node (incl. both) + { + RemoveNode( aRg.aEnd.GetIndex(), nCnt + 2, true ); // delete array + nCnt = SwNodeOffset(0); + nLevel--; + } + + // after deletion, aEnd might point to an EndNode... + // delete all empty start/end node pairs + SwNode* pTmpNode = aRg.aEnd.GetNode().GetEndNode(); + --aRg.aEnd; + while( pTmpNode && + ( pCurrentNode = &aRg.aEnd.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() ) + { + // remove end and start node + DelNodes( aRg.aEnd, SwNodeOffset(2) ); + pTmpNode = aRg.aEnd.GetNode().GetEndNode(); + --aRg.aEnd; + } + } + else // "normal" node, so insert into TmpArray + { + SwTextNode* pTextNd = pCurrentNode->GetTextNode(); + if( pTextNd ) + { + if( pTextNd->IsOutline()) + { + // delete outline indices + m_aOutlineNodes.erase( pTextNd ); + bUpdateOutline = true; + } + if (sw::HasNumberingWhichNeedsLayoutUpdate(*pTextNd)) + { + pTextNd->InvalidateNumRule(); + } + } + else if( pCurrentNode->IsContentNode() ) + static_cast<SwContentNode*>(pCurrentNode)->InvalidateNumRule(); + + --aRg.aEnd; + nCnt++; + } + } + + ++aRg.aEnd; + if( nCnt != SwNodeOffset(0) ) + RemoveNode( aRg.aEnd.GetIndex(), nCnt, true ); // delete the rest + + // delete all empty start/end node pairs + while( aRg.aEnd.GetNode().GetEndNode() && + ( pCurrentNode = &aRg.aStart.GetNode())->GetStartNode() && + pCurrentNode->StartOfSectionIndex() ) + // but none of the holy 5. (???) + { + DelNodes( aRg.aStart, SwNodeOffset(2) ); // delete start and end node + --aRg.aStart; + } + + m_bInNodesDel = bSaveInNodesDel; + + if( !m_bInNodesDel ) + { + // update numbering + if( bUpdateOutline || m_bInDelUpdOutline ) + { + UpdateOutlineIdx( aRg.aEnd.GetNode() ); + m_bInDelUpdOutline = false; + } + + } + else + { + if( bUpdateOutline ) + m_bInDelUpdOutline = true; + } +} + +/** get section level at the given position + * + * @note The first node in an array should always be a start node. + * Because of this, there is a special treatment here based on the + * assumption that this is true in this context as well. + * + * @param rIdx position of the node + * @return section level at the given position + */ +sal_uInt16 SwNodes::GetSectionLevel(const SwNode &rIdx) +{ + // special treatment for 1st Node + if(rIdx.GetIndex() == SwNodeOffset(0)) return 1; + // no recursion! This calls a SwNode::GetSectionLevel (missing "s") + return rIdx.GetSectionLevel(); +} + +void SwNodes::GoStartOfSection(SwNodeIndex *pIdx) +{ + // after the next start node + SwNodeIndex aTmp( *pIdx->GetNode().StartOfSectionNode(), +1 ); + + // If index points to no ContentNode, then go to one. + // If there is no further available, do not change the index' position! + while( !aTmp.GetNode().IsContentNode() ) + { // go from this StartNode (can only be one) to its end + if( *pIdx <= aTmp ) + return; // ERROR: already after the section + aTmp = aTmp.GetNode().EndOfSectionIndex()+1; + if( *pIdx <= aTmp ) + return; // ERROR: already after the section + } + (*pIdx) = aTmp; // is on a ContentNode +} + +void SwNodes::GoEndOfSection(SwNodeIndex *pIdx) +{ + if( !pIdx->GetNode().IsEndNode() ) + (*pIdx) = *pIdx->GetNode().EndOfSectionNode(); +} + +SwContentNode* SwNodes::GoNext(SwNodeIndex *pIdx) const +{ + if( pIdx->GetIndex() >= Count() - 1 ) + return nullptr; + + SwNodeIndex aTmp(*pIdx, +1); + SwNode* pNd = nullptr; + while( aTmp < Count()-1 && !( pNd = &aTmp.GetNode())->IsContentNode() ) + ++aTmp; + + if( aTmp == Count()-1 ) + pNd = nullptr; + else + (*pIdx) = aTmp; + return static_cast<SwContentNode*>(pNd); +} + +SwContentNode* SwNodes::GoNext(SwPosition *pIdx) const +{ + if( pIdx->GetNodeIndex() >= Count() - 1 ) + return nullptr; + + SwNodeIndex aTmp(pIdx->GetNode(), +1); + SwNode* pNd = nullptr; + while( aTmp < Count()-1 && !( pNd = &aTmp.GetNode())->IsContentNode() ) + ++aTmp; + + if( aTmp == Count()-1 ) + pNd = nullptr; + else + pIdx->Assign(aTmp); + return static_cast<SwContentNode*>(pNd); +} + +SwContentNode* SwNodes::GoPrevious(SwNodeIndex *pIdx) +{ + if( !pIdx->GetIndex() ) + return nullptr; + + SwNodeIndex aTmp( *pIdx, -1 ); + SwNode* pNd = nullptr; + while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() ) + --aTmp; + + if( !aTmp.GetIndex() ) + pNd = nullptr; + else + (*pIdx) = aTmp; + return static_cast<SwContentNode*>(pNd); +} + +SwContentNode* SwNodes::GoPrevious(SwPosition *pIdx) +{ + if( !pIdx->GetNodeIndex() ) + return nullptr; + + SwNodeIndex aTmp( pIdx->GetNode(), -1 ); + SwNode* pNd = nullptr; + while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() ) + --aTmp; + + if( !aTmp.GetIndex() ) + pNd = nullptr; + else + pIdx->Assign(aTmp); + return static_cast<SwContentNode*>(pNd); +} + +/** Delete a number of nodes + * + * @param rStart starting position in this nodes array + * @param nCnt number of nodes to delete + */ +void SwNodes::DelNodes( const SwNodeIndex & rStart, SwNodeOffset nCnt ) +{ + SwNodeOffset nSttIdx = rStart.GetIndex(); + + if( !nSttIdx && nCnt == GetEndOfContent().GetIndex()+1 ) + { + // The whole nodes array will be destroyed, you're in the Doc's DTOR! + // The initial start/end nodes should be only destroyed in the SwNodes' DTOR! + SwNode* aEndNdArr[] = { m_pEndOfContent.get(), + m_pEndOfPostIts, m_pEndOfInserts, + m_pEndOfAutotext, m_pEndOfRedlines, + nullptr + }; + + SwNode** ppEndNdArr = aEndNdArr; + while( *ppEndNdArr ) + { + nSttIdx = (*ppEndNdArr)->StartOfSectionIndex() + 1; + SwNodeOffset nEndIdx = (*ppEndNdArr)->GetIndex(); + + if( nSttIdx != nEndIdx ) + RemoveNode( nSttIdx, nEndIdx - nSttIdx, true ); + + ++ppEndNdArr; + } + } + else + { + int bUpdateNum = 0; + for( SwNodeOffset n = nSttIdx, nEnd = nSttIdx + nCnt; n < nEnd; ++n ) + { + SwNode* pNd = (*this)[ n ]; + + if (pNd->IsTextNode() && pNd->GetTextNode()->IsOutline()) + { + // remove the outline indices + if (m_aOutlineNodes.erase(pNd)) + bUpdateNum = 1; + } + if( pNd->IsContentNode() ) + { + static_cast<SwContentNode*>(pNd)->InvalidateNumRule(); + static_cast<SwContentNode*>(pNd)->DelFrames(nullptr); + } + } + RemoveNode( nSttIdx, nCnt, true ); + + // update numbering + if( bUpdateNum ) + UpdateOutlineIdx( rStart.GetNode() ); + } +} + +namespace { + +struct HighLevel +{ + sal_uInt16 nLevel, nTop; + explicit HighLevel( sal_uInt16 nLv ) : nLevel( nLv ), nTop( nLv ) {} +}; + +} + +static bool lcl_HighestLevel( SwNode* pNode, void * pPara ) +{ + HighLevel * pHL = static_cast<HighLevel*>(pPara); + if( pNode->GetStartNode() ) + { + pHL->nLevel++; + if( pHL->nTop > pHL->nLevel ) + pHL->nTop = pHL->nLevel; + } + else if( pNode->GetEndNode() ) + pHL->nLevel--; + return true; + +} + +/** Calculate the highest level in a range + * + * @param rNodes the nodes array + * @param rRange the range to inspect + * @return the highest level + */ +sal_uInt16 HighestLevel( SwNodes & rNodes, const SwNodeRange & rRange ) +{ + HighLevel aPara( SwNodes::GetSectionLevel( rRange.aStart.GetNode() )); + rNodes.ForEach( rRange.aStart, rRange.aEnd, lcl_HighestLevel, &aPara ); + return aPara.nTop; + +} + +/** move a range + * + * @param rPam the range to move + * @param rPos to destination position in the given nodes array + * @param rNodes the node array to move the range into + */ +void SwNodes::MoveRange( SwPaM & rPam, SwPosition & rPos, SwNodes& rNodes ) +{ + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + if( !rPam.HasMark() || *pStt >= *pEnd ) + return; + + if( this == &rNodes && *pStt <= rPos && rPos < *pEnd ) + return; + + SwNodeIndex aEndIdx( pEnd->GetNode() ); + SwNodeIndex aSttIdx( pStt->GetNode() ); + SwTextNode *const pSrcNd = aSttIdx.GetNode().GetTextNode(); + SwTextNode * pDestNd = rPos.GetNode().GetTextNode(); + bool bSplitDestNd = true; + bool bCopyCollFormat = pDestNd && pDestNd->GetText().isEmpty(); + + if( pSrcNd ) + { + // if the first node is a TextNode, then there must + // be also a TextNode in the NodesArray to store the content + if( !pDestNd ) + { + pDestNd = rNodes.MakeTextNode( rPos.GetNode(), pSrcNd->GetTextColl() ); + --rPos.nNode; + rPos.nContent.Assign( pDestNd, 0 ); + bCopyCollFormat = true; + } + bSplitDestNd = pDestNd->Len() > rPos.GetContentIndex() || + pEnd->GetNode().IsTextNode(); + + // move the content into the new node + bool bOneNd = pStt->GetNode() == pEnd->GetNode(); + const sal_Int32 nLen = + ( bOneNd ? std::min(pEnd->GetContentIndex(), pSrcNd->Len()) : pSrcNd->Len() ) + - pStt->GetContentIndex(); + + if( !pEnd->GetNode().IsContentNode() ) + { + bOneNd = true; + SwNodeOffset nSttNdIdx = pStt->GetNodeIndex() + 1; + const SwNodeOffset nEndNdIdx = pEnd->GetNodeIndex(); + for( ; nSttNdIdx < nEndNdIdx; ++nSttNdIdx ) + { + if( (*this)[ nSttNdIdx ]->IsContentNode() ) + { + bOneNd = false; + break; + } + } + } + + // templates must be copied/set after a split + if( !bOneNd && bSplitDestNd ) + { + if( !rPos.GetContentIndex() ) + { + bCopyCollFormat = true; + } + if( rNodes.IsDocNodes() ) + { + SwDoc& rInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(rInsDoc.GetIDocumentUndoRedo()); + rInsDoc.getIDocumentContentOperations().SplitNode( rPos, false ); + } + else + { + pDestNd->SplitContentNode(rPos, nullptr); + } + + if( rPos.GetNode() == aEndIdx.GetNode() ) + { + --aEndIdx; + } + bSplitDestNd = true; + + pDestNd = rNodes[ rPos.GetNodeIndex() - 1 ]->GetTextNode(); + if( nLen ) + { + pSrcNd->CutText( pDestNd, SwContentIndex( pDestNd, pDestNd->Len()), + pStt->nContent, nLen ); + } + } + else if ( nLen ) + { + pSrcNd->CutText( pDestNd, rPos.nContent, pStt->nContent, nLen ); + } + + if( bCopyCollFormat ) + { + SwDoc& rInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const undoGuard(rInsDoc.GetIDocumentUndoRedo()); + pSrcNd->CopyCollFormat( *pDestNd ); + } + + if( bOneNd ) + { + // Correct the PaM, because it might have happened that the move + // went over the node borders (so the data might be in different nodes). + // Also, a selection is invalidated. + pEnd->nContent = pStt->nContent; + rPam.DeleteMark(); + GetDoc().GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, + rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); + return; + } + + ++aSttIdx; + } + else if( pDestNd ) + { + if( rPos.GetContentIndex() ) + { + if( rPos.GetContentIndex() == pDestNd->Len() ) + { + ++rPos.nNode; + } + else if( rPos.GetContentIndex() ) + { + // if the EndNode is split than correct the EndIdx + const bool bCorrEnd = aEndIdx == rPos.nNode; + + // if no text is attached to the TextNode, split it + if( rNodes.IsDocNodes() ) + { + SwDoc& rInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(rInsDoc.GetIDocumentUndoRedo()); + rInsDoc.getIDocumentContentOperations().SplitNode( rPos, false ); + } + else + { + pDestNd->SplitContentNode(rPos, nullptr); + } + + if ( bCorrEnd ) + { + --aEndIdx; + } + } + } + // at the end only an empty TextNode is left over + bSplitDestNd = true; + } + + SwTextNode* const pEndSrcNd = aEndIdx.GetNode().GetTextNode(); + if ( pEndSrcNd ) + { + // at the end of this range a new TextNode will be created + if( !bSplitDestNd ) + { + if( rPos.GetNode() < rNodes.GetEndOfContent() ) + { + ++rPos.nNode; + } + + pDestNd = + rNodes.MakeTextNode( rPos.GetNode(), pEndSrcNd->GetTextColl() ); + --rPos.nNode; + rPos.nContent.Assign( pDestNd, 0 ); + } + else + { + pDestNd = rPos.GetNode().GetTextNode(); + } + + if (pDestNd && pEnd->GetContentIndex()) + { + // move the content into the new node + SwContentIndex aIdx( pEndSrcNd, 0 ); + pEndSrcNd->CutText( pDestNd, rPos.nContent, aIdx, + pEnd->GetContentIndex()); + } + + if (pDestNd && bCopyCollFormat) + { + SwDoc& rInsDoc = pDestNd->GetDoc(); + ::sw::UndoGuard const ug(rInsDoc.GetIDocumentUndoRedo()); + pEndSrcNd->CopyCollFormat( *pDestNd ); + } + } + else + { + if ( pSrcNd && aEndIdx.GetNode().IsContentNode() ) + { + ++aEndIdx; + } + if( !bSplitDestNd ) + { + rPos.Adjust(SwNodeOffset(1)); + } + } + + if( aEndIdx != aSttIdx ) + { + // move the nodes into the NodesArray + const SwNodeOffset nSttDiff = aSttIdx.GetIndex() - pStt->GetNodeIndex(); + SwNodeRange aRg( aSttIdx, aEndIdx ); + MoveNodes( aRg, rNodes, rPos.GetNode() ); + + // if in the same node array, all indices are now at new positions (so correct them) + if( &rNodes == this ) + { + pStt->nNode = aRg.aEnd.GetIndex() - nSttDiff; + } + } + + // if the StartNode was moved to whom the cursor pointed, so + // the content must be registered in the current content! + if ( pStt->GetNode() == GetEndOfContent() ) + { + const bool bSuccess = GoPrevious( &pStt->nNode ); + OSL_ENSURE( bSuccess, "Move() - no ContentNode here" ); + } + pStt->nContent.Assign( pStt->GetNode().GetContentNode(), + pStt->GetContentIndex() ); + // Correct the PaM, because it might have happened that the move + // went over the node borders (so the data might be in different nodes). + // Also, a selection is invalidated. + *pEnd = *pStt; + rPam.DeleteMark(); + GetDoc().GetDocShell()->Broadcast( SwFormatFieldHint( nullptr, + rNodes.IsDocNodes() ? SwFormatFieldHintWhich::INSERTED : SwFormatFieldHintWhich::REMOVED ) ); +} + +///@see SwNodes::MoveNodes (TODO: seems to be C&P programming here) +void SwNodes::CopyNodes( const SwNodeRange& rRange, + SwNode& rPos, bool bNewFrames, bool bTableInsDummyNode ) const +{ + SwDoc& rDoc = rPos.GetDoc(); + + SwNode * pCurrentNode; + if( rPos.GetIndex() == SwNodeOffset(0) || + ( (pCurrentNode = &rPos)->GetStartNode() && + !pCurrentNode->StartOfSectionIndex() )) + return; + + SwNodeRange aRg( rRange ); + + // skip "simple" StartNodes or EndNodes + while( SwNodeType::Start == (pCurrentNode = & aRg.aStart.GetNode())->GetNodeType() + || ( pCurrentNode->IsEndNode() && + !pCurrentNode->m_pStartOfSection->IsSectionNode() ) ) + ++aRg.aStart; + + const SwNode *aEndNode = &aRg.aEnd.GetNode(); + SwNodeOffset nIsEndOfContent((aEndNode == &aEndNode->GetNodes().GetEndOfContent()) ? 1 : 0); + + if (SwNodeOffset(0) == nIsEndOfContent) + { + // if aEnd-1 points to no ContentNode, search previous one + --aRg.aEnd; + // #i107142#: if aEnd is start node of a special section, do nothing. + // Otherwise this could lead to crash: going through all previous + // special section nodes and then one before the first. + if (aRg.aEnd.GetNode().StartOfSectionIndex() != SwNodeOffset(0)) + { + while( ((pCurrentNode = & aRg.aEnd.GetNode())->GetStartNode() && + !pCurrentNode->IsSectionNode() ) || + ( pCurrentNode->IsEndNode() && + SwNodeType::Start == pCurrentNode->m_pStartOfSection->GetNodeType()) ) + { + --aRg.aEnd; + } + } + ++aRg.aEnd; + } + + // is there anything left to copy? + if( aRg.aStart >= aRg.aEnd ) + return; + + // when inserting into the source range, nothing need to be done + OSL_ENSURE( &aRg.aStart.GetNodes() == this, + "aRg should use this node array" ); + OSL_ENSURE( &aRg.aStart.GetNodes() == &aRg.aEnd.GetNodes(), + "Range across different nodes arrays? You deserve punishment!"); + if( &rPos.GetNodes() == &aRg.aStart.GetNodes() && + rPos.GetIndex() >= aRg.aStart.GetIndex() && + rPos.GetIndex() < aRg.aEnd.GetIndex() ) + return; + + SwNodeIndex aInsPos( rPos ); + SwNodeIndex aOrigInsPos( rPos, -1 ); // original insertion position + int nLevel = 0; // level counter + + for( SwNodeOffset nNodeCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + nNodeCnt > SwNodeOffset(0); --nNodeCnt ) + { + pCurrentNode = &aRg.aStart.GetNode(); + switch( pCurrentNode->GetNodeType() ) + { + case SwNodeType::Table: + // Does it copy a table in(to) a footnote? + if( aInsPos < rDoc.GetNodes().GetEndOfInserts().GetIndex() && + rDoc.GetNodes().GetEndOfInserts().StartOfSectionIndex() + < aInsPos.GetIndex() ) + { + const SwNodeOffset nDistance = + pCurrentNode->EndOfSectionIndex() - + aRg.aStart.GetIndex(); + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = SwNodeOffset(1); + + // insert a DummyNode for a TableNode + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos.GetNode()); + + // copy all of the table's nodes into the current cell + for( ++aRg.aStart; aRg.aStart.GetIndex() < + pCurrentNode->EndOfSectionIndex(); + ++aRg.aStart ) + { + // insert a DummyNode for the box-StartNode? + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos.GetNode()); + + SwStartNode* pSttNd = aRg.aStart.GetNode().GetStartNode(); + CopyNodes( SwNodeRange( *pSttNd, SwNodeOffset(+ 1), + *pSttNd->EndOfSectionNode() ), + aInsPos.GetNode(), bNewFrames ); + + // insert a DummyNode for the box-EndNode? + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos.GetNode()); + aRg.aStart = *pSttNd->EndOfSectionNode(); + } + // insert a DummyNode for the table-EndNode + if( bTableInsDummyNode ) + new SwPlaceholderNode(aInsPos.GetNode()); + aRg.aStart = *pCurrentNode->EndOfSectionNode(); + } + else + { + SwNodeIndex nStt( aInsPos, -1 ); + SwTableNode* pTableNd = static_cast<SwTableNode*>(pCurrentNode)-> + MakeCopy( rDoc, aInsPos ); + const SwNodeOffset nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = SwNodeOffset(1) - nIsEndOfContent; + + aRg.aStart = pCurrentNode->EndOfSectionIndex(); + + if( bNewFrames && pTableNd ) + pTableNd->MakeOwnFrames(); + } + break; + + case SwNodeType::Section: + // If the end of the section is outside the copy range, + // the section node will skipped, not copied! + // If someone want to change this behaviour, he has to adjust the function + // lcl_NonCopyCount(..) in ndcopy.cxx which relies on it. + if( pCurrentNode->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) + { + // copy of the whole section, so create a new SectionNode + SwNodeIndex nStt( aInsPos, -1 ); + SwSectionNode* pSectNd = static_cast<SwSectionNode*>(pCurrentNode)-> + MakeCopy( rDoc, aInsPos ); + + const SwNodeOffset nDistance = aInsPos.GetIndex() - nStt.GetIndex() - 2; + if (nDistance < nNodeCnt) + nNodeCnt -= nDistance; + else + nNodeCnt = SwNodeOffset(1) - nIsEndOfContent; + aRg.aStart = pCurrentNode->EndOfSectionIndex(); + + if( bNewFrames && pSectNd && + !pSectNd->GetSection().IsHidden() ) + pSectNd->MakeOwnFrames(&nStt); + } + break; + + case SwNodeType::Start: + { + SwStartNode* pTmp = new SwStartNode( aInsPos.GetNode(), SwNodeType::Start, + static_cast<SwStartNode*>(pCurrentNode)->GetStartNodeType() ); + new SwEndNode( aInsPos.GetNode(), *pTmp ); + --aInsPos; + nLevel++; + } + break; + + case SwNodeType::End: + if( nLevel ) // complete section + { + --nLevel; + ++aInsPos; // EndNode already exists + } + else if( SwNodeOffset(1) == nNodeCnt && SwNodeOffset(1) == nIsEndOfContent ) + // we have reached the EndOfContent node - nothing to do! + continue; + else if( !pCurrentNode->m_pStartOfSection->IsSectionNode() ) + { + // create a section at the original InsertPosition + SwNodeRange aTmpRg( aOrigInsPos, SwNodeOffset(1), aInsPos ); + rDoc.GetNodes().SectionDown( &aTmpRg, + pCurrentNode->m_pStartOfSection->GetStartNodeType() ); + } + break; + + case SwNodeType::Text: + case SwNodeType::Grf: + case SwNodeType::Ole: + { + static_cast<SwContentNode*>(pCurrentNode)->MakeCopy( + rDoc, aInsPos.GetNode(), bNewFrames); + } + break; + + case SwNodeType::PlaceHolder: + if (GetDoc().GetIDocumentUndoRedo().IsUndoNodes(*this)) + { + // than a SectionNode (start/end) is needed at the current + // InsPos; if so skip it, otherwise ignore current node + SwNode *const pTmpNd = & aInsPos.GetNode(); + if( pTmpNd->IsSectionNode() || + pTmpNd->StartOfSectionNode()->IsSectionNode() ) + ++aInsPos; // skip + } + else { + assert(!"How can this node be in the node array?"); + } + break; + + default: + assert(false); + } + ++aRg.aStart; + } +} + +void SwNodes::DelDummyNodes( const SwNodeRange& rRg ) +{ + SwNodeIndex aIdx( rRg.aStart ); + while( aIdx.GetIndex() < rRg.aEnd.GetIndex() ) + { + if (SwNodeType::PlaceHolder == aIdx.GetNode().GetNodeType()) + RemoveNode( aIdx.GetIndex(), SwNodeOffset(1), true ); + else + ++aIdx; + } +} + +SwStartNode* SwNodes::MakeEmptySection( SwNode& rWhere, + SwStartNodeType eSttNdTyp ) +{ + SwStartNode* pSttNd = new SwStartNode( rWhere, SwNodeType::Start, eSttNdTyp ); + new SwEndNode( rWhere, *pSttNd ); + return pSttNd; +} + +SwStartNode* SwNodes::MakeTextSection( const SwNode & rWhere, + SwStartNodeType eSttNdTyp, + SwTextFormatColl *pColl ) +{ + SwStartNode* pSttNd = new SwStartNode( rWhere, SwNodeType::Start, eSttNdTyp ); + new SwEndNode( rWhere, *pSttNd ); + MakeTextNode( SwNodeIndex( rWhere, - 1 ).GetNode(), pColl ); + return pSttNd; +} + +//TODO: provide better documentation +/** go to next section that is not protected nor hidden + * + * @note if !bSkipHidden and !bSkipProtect, use GoNext/GoPrevious + * + * @param pIdx + * @param bSkipHidden + * @param bSkipProtect + * @return + * @see SwNodes::GoNext + * @see SwNodes::GoPrevious + * @see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +*/ +SwContentNode* SwNodes::GoNextSection( SwNodeIndex * pIdx, + bool bSkipHidden, bool bSkipProtect ) const +{ + bool bFirst = true; + SwNodeIndex aTmp( *pIdx ); + const SwNode* pNd; + while( aTmp < Count() - 1 ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::Section == pNd->GetNodeType()) + { + const SwSection& rSect = static_cast<const SwSectionNode*>(pNd)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + else if( bFirst ) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd->EndOfSectionNode(); + } + else + { + (*pIdx) = aTmp; + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + ++aTmp; + bFirst = false; + } + return nullptr; +} + +//TODO: provide better documentation +/** go to next section that is not protected nor hidden + * + * @note if !bSkipHidden and !bSkipProtect, use GoNext/GoPrevious + * + * @param pIdx + * @param bSkipHidden + * @param bSkipProtect + * @return + * @see SwNodes::GoNext + * @see SwNodes::GoPrevious + * @see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +*/ +SwContentNode* SwNodes::GoNextSection( SwPosition * pIdx, + bool bSkipHidden, bool bSkipProtect ) const +{ + bool bFirst = true; + SwNodeIndex aTmp( pIdx->GetNode() ); + const SwNode* pNd; + while( aTmp < Count() - 1 ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::Section == pNd->GetNodeType()) + { + const SwSection& rSect = static_cast<const SwSectionNode*>(pNd)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + else if( bFirst ) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip the section + aTmp = *pNd->EndOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd->EndOfSectionNode(); + } + else + { + pIdx->Assign(aTmp); + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + ++aTmp; + bFirst = false; + } + return nullptr; +} + +///@see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +SwContentNode* SwNodes::GoPrevSection( SwNodeIndex * pIdx, + bool bSkipHidden, bool bSkipProtect ) +{ + bool bFirst = true; + SwNodeIndex aTmp( *pIdx ); + const SwNode* pNd; + while( aTmp > SwNodeOffset(0) ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::End == pNd->GetNodeType()) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + bFirst = false; + } + else if( bFirst ) + { + bFirst = false; + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd; + } + else + { + (*pIdx) = aTmp; + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + --aTmp; + } + return nullptr; +} + +///@see SwNodes::GoNextSection (TODO: seems to be C&P programming here) +SwContentNode* SwNodes::GoPrevSection( SwPosition * pIdx, + bool bSkipHidden, bool bSkipProtect ) +{ + bool bFirst = true; + SwNodeIndex aTmp( pIdx->GetNode() ); + const SwNode* pNd; + while( aTmp > SwNodeOffset(0) ) + { + pNd = & aTmp.GetNode(); + if (SwNodeType::End == pNd->GetNodeType()) + { + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + bFirst = false; + } + else if( bFirst ) + { + bFirst = false; + if( pNd->m_pStartOfSection->IsSectionNode() ) + { + const SwSection& rSect = static_cast<SwSectionNode*>(pNd-> + m_pStartOfSection)->GetSection(); + if( (bSkipHidden && rSect.IsHiddenFlag()) || + (bSkipProtect && rSect.IsProtectFlag()) ) + // than skip section + aTmp = *pNd->StartOfSectionNode(); + } + } + else if( SwNodeType::ContentMask & pNd->GetNodeType() ) + { + const SwSectionNode* pSectNd; + if( ( bSkipHidden || bSkipProtect ) && + nullptr != (pSectNd = pNd->FindSectionNode() ) && + ( ( bSkipHidden && pSectNd->GetSection().IsHiddenFlag() ) || + ( bSkipProtect && pSectNd->GetSection().IsProtectFlag() )) ) + { + aTmp = *pSectNd; + } + else + { + pIdx->Assign(aTmp); + return const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)); + } + } + --aTmp; + } + return nullptr; +} + +//TODO: The inventor of the "single responsibility principle" will be crying if you ever show this code to him! +/** find the next/previous ContentNode or table node that should have layout + * frames that are siblings to the ones of the node at rFrameNd. + * + * Search is started backward with the one before rFrameNd and + * forward after pEnd. + * + * @param rFrameNd node with frames to search in + * @param pEnd last node after rFrameNd that should be excluded from search + * @return result node; nullptr if not found + */ +SwNode* SwNodes::FindPrvNxtFrameNode( const SwNode& rFrameNd, + SwNode const*const pEnd, + SwRootFrame const*const pLayout) const +{ + assert(pEnd != nullptr); // every caller currently + + // no layout -> skip + if (!GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell()) + return nullptr; + + const SwNode *const pSttNd = &rFrameNd; + + // inside a hidden section? + const SwSectionNode *const pSectNd = pSttNd->IsSectionNode() + ? pSttNd->StartOfSectionNode()->FindSectionNode() + : pSttNd->FindSectionNode(); + if (pSectNd && pSectNd->GetSection().CalcHiddenFlag()) + return nullptr; + + // in a table in table situation we have to assure that we don't leave the + // outer table cell when the inner table is looking for a PrvNxt... + const SwTableNode *const pTableNd = pSttNd->IsTableNode() + ? pSttNd->StartOfSectionNode()->FindTableNode() + : pSttNd->FindTableNode(); + SwNodeIndex aIdx( rFrameNd ); + + // search backward for a content or table node + + --aIdx; + SwNode* pFrameNd = &aIdx.GetNode(); + + do + { + if (pFrameNd->IsContentNode()) + { + // TODO why does this not check for nested tables like forward direction + return pFrameNd; + } + else if (pFrameNd->IsEndNode() && pFrameNd->StartOfSectionNode()->IsTableNode()) + { + if (pLayout == nullptr + || !pLayout->HasMergedParas() + || pFrameNd->StartOfSectionNode()->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + { + pFrameNd = pFrameNd->StartOfSectionNode(); + return pFrameNd; + } + else + { + aIdx = *pFrameNd->StartOfSectionNode(); + --aIdx; + pFrameNd = &aIdx.GetNode(); + } + } + else if (pFrameNd->IsSectionNode() + || (pFrameNd->IsEndNode() && pFrameNd->StartOfSectionNode()->IsSectionNode())) + { + pFrameNd = GoPrevSection( &aIdx, true, false ); + // did we move *into* a table? + if (pFrameNd && ::CheckNodesRange(aIdx.GetNode(), rFrameNd, true)) + { + for (SwTableNode * pTable = pFrameNd->FindTableNode(); + pTable && pTable->EndOfSectionIndex() < rFrameNd.GetIndex(); + pTable = pTable->StartOfSectionNode()->FindTableNode()) + { + pFrameNd = pTable->EndOfSectionNode(); + } + if (pFrameNd->IsEndNode()) + { // GoPrevSection() checks that text node isn't section-hidden, + // so table node between can't be section-hidden either + assert(pFrameNd->StartOfSectionNode()->IsTableNode()); + continue; // check other hidden conditions on next iteration + } + } + if ( nullptr != pFrameNd && !( + ::CheckNodesRange( aIdx.GetNode(), rFrameNd, true ) && + // Never out of the table at the start + pFrameNd->FindTableNode() == pTableNd && + // Bug 37652: Never out of the table at the end + (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() + == pSttNd->FindTableBoxStartNode() ) && + (!pSectNd || pSttNd->IsSectionNode() || + pSectNd->GetIndex() < pFrameNd->GetIndex()) + )) + { + pFrameNd = nullptr; // no preceding content node, stop search + } + } + else + { + pFrameNd = nullptr; // no preceding content node, stop search + } + } + while (pFrameNd != nullptr); + + // search forward for a content or table node + + aIdx = pEnd->GetIndex() + 1; + pFrameNd = &aIdx.GetNode(); + + do + { + if (pFrameNd->IsContentNode()) + { + // Undo when merging a table with one before, if there is also one after it. + // However, if the node is in a table, it needs to be returned if the + // SttNode is a section or a table! + SwTableNode *const pTableNode = pFrameNd->FindTableNode(); + if (pSttNd->IsTableNode() && + nullptr != pTableNode && + // TABLE IN TABLE: + pTableNode != pSttNd->StartOfSectionNode()->FindTableNode()) + { + pFrameNd = pTableNode; + } + return pFrameNd; + } + else if (pFrameNd->IsTableNode()) + { + if (pLayout == nullptr + || !pLayout->HasMergedParas() + || pFrameNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + { + return pFrameNd; + } + else + { + aIdx = *pFrameNd->EndOfSectionNode(); + ++aIdx; + pFrameNd = &aIdx.GetNode(); + } + } + else if (pFrameNd->IsSectionNode() + || (pFrameNd->IsEndNode() && pFrameNd->StartOfSectionNode()->IsSectionNode())) + { + pFrameNd = GoNextSection( &aIdx, true, false ); + // did we move *into* a table? + if (pFrameNd && ::CheckNodesRange(aIdx.GetNode(), rFrameNd, true)) + { + for (SwTableNode * pTable = pFrameNd->FindTableNode(); + pTable && pEnd->GetIndex() < pTable->GetIndex(); + pTable = pTable->StartOfSectionNode()->FindTableNode()) + { + pFrameNd = pTable; + } + if (pFrameNd->IsTableNode()) + { // GoNextSection() checks that text node isn't section-hidden, + // so table node between can't be section-hidden either + continue; // check other hidden conditions on next iteration + } + } + // NEVER leave the section when doing this! + if (pFrameNd + && !(::CheckNodesRange(aIdx.GetNode(), rFrameNd, true) + && (pFrameNd->FindTableNode() == pTableNd && + // NEVER go out of the table cell at the end + (!pFrameNd->FindTableNode() || pFrameNd->FindTableBoxStartNode() + == pSttNd->FindTableBoxStartNode())) + && (!pSectNd || pSttNd->IsSectionNode() || + pSectNd->EndOfSectionIndex() > pFrameNd->GetIndex())) + ) + { + pFrameNd = nullptr; // no following content node, stop search + } + } + else + { + pFrameNd = nullptr; // no preceding content node, stop search + } + } + while (pFrameNd != nullptr); + + return pFrameNd; +} + +void SwNodes::ForEach( SwNodeOffset nStart, SwNodeOffset nEnd, + FnForEach_SwNodes fn, void* pArgs ) +{ + if( nEnd > SwNodeOffset(m_nSize) ) + nEnd = SwNodeOffset(m_nSize); + + if( nStart >= nEnd ) + return; + + sal_uInt16 cur = Index2Block( sal_Int32(nStart) ); + BlockInfo** pp = m_ppInf.get() + cur; + BlockInfo* p = *pp; + sal_uInt16 nElem = sal_uInt16( sal_Int32(nStart) - p->nStart ); + auto pElem = p->mvData.begin() + nElem; + nElem = p->nElem - nElem; + for(;;) + { + if( !(*fn)( static_cast<SwNode *>(*pElem++), pArgs ) || ++nStart >= nEnd ) + break; + + // next element + if( !--nElem ) + { + // new block + p = *++pp; + pElem = p->mvData.begin(); + nElem = p->nElem; + } + } +} + +void SwNodes::ForEach( const SwNodeIndex& rStart, const SwNodeIndex& rEnd, + FnForEach_SwNodes fnForEach, void* pArgs ) +{ + ForEach( rStart.GetIndex(), rEnd.GetIndex(), fnForEach, pArgs ); +} + +void SwNodes::ForEach( SwNode& rStart, SwNode& rEnd, + FnForEach_SwNodes fnForEach, void* pArgs ) +{ + ForEach( rStart.GetIndex(), rEnd.GetIndex(), fnForEach, pArgs ); +} + +void SwNodes::RemoveNode( SwNodeOffset nDelPos, SwNodeOffset nSz, bool bDel ) +{ +#ifndef NDEBUG + SwNode *const pFirst((*this)[nDelPos]); +#endif + for (SwNodeOffset nCnt(0); nCnt < nSz; nCnt++) + { + SwNode* pNode = (*this)[ nDelPos + nCnt ]; + SwTextNode * pTextNd = pNode->GetTextNode(); + + if (pTextNd) + { + pTextNd->RemoveFromList(); + // remove RndStdIds::FLY_AS_CHAR *before* adjusting SwNodeIndex + // so their anchor still points to correct node when deleted! + // NOTE: this will call RemoveNode() recursively! + // so adjust our indexes to account for removed nodes + SwNodeOffset const nPos = pTextNd->GetIndex(); + SwpHints *const pHints(pTextNd->GetpSwpHints()); + if (pHints) + { + std::vector<SwTextAttr*> flys; + for (size_t i = 0; i < pHints->Count(); ++i) + { + SwTextAttr *const pHint(pHints->Get(i)); + if (RES_TXTATR_FLYCNT == pHint->Which()) + { + flys.push_back(pHint); + } + } + for (SwTextAttr * pHint : flys) + { + pTextNd->DeleteAttribute(pHint); + } // pHints may be dead now + SwNodeOffset const nDiff = nPos - pTextNd->GetIndex(); + if (nDiff) + { + nDelPos -= nDiff; + } + assert(pTextNd == (*this)[nDelPos + nCnt]); + assert(pFirst == (*this)[nDelPos]); + } + } + SwTableNode* pTableNode = pNode->GetTableNode(); + if (pTableNode) + { + // The node that is deleted is a table node. + // Need to make sure that all the redlines that are + // related to this table are removed from the + // 'Extra Redlines' array + pTableNode->RemoveRedlines(); + } + + SwSectionNode* pSectionNode = pNode->GetSectionNode(); + if (comphelper::LibreOfficeKit::isActive() && pSectionNode && !GetDoc().IsClipBoard() && SfxViewShell::Current()) + { + OUString fieldCommand = pSectionNode->GetSection().GetSectionName(); + tools::JsonWriter aJson; + aJson.put("commandName", ".uno:DeleteSection"); + aJson.put("success", true); + { + auto result = aJson.startNode("result"); + aJson.put("DeleteSection", fieldCommand); + } + + SfxViewShell::Current()->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString()); + + } + } + + SwNodeOffset nEnd = nDelPos + nSz; + SwNode* pNew = (*this)[ nEnd ]; + + for (SwNodeIndex& rIndex : m_vIndices->GetRingContainer()) + { + SwNodeOffset const nIdx = rIndex.GetIndex(); + if (nDelPos <= nIdx && nIdx < nEnd) + rIndex = *pNew; + } + + std::vector<BigPtrEntry> aTempEntries; + if( bDel ) + { + SwNodeOffset nCnt = nSz; + BigPtrEntry *pDel = (*this)[ nDelPos+nCnt-1 ], *pPrev = (*this)[ nDelPos+nCnt-2 ]; + + // set temporary object + // JP 24.08.98: this should actually be removed because one could + // call Remove recursively, e.g. for character bound frames. However, + // since there happens way too much here, this temporary object was + // inserted that will be deleted in Remove again (see Bug 55406) + aTempEntries.resize(sal_Int32(nCnt)); + + while( nCnt-- ) + { + delete pDel; + // coverity[use_after_free : FALSE] - pPrev will be reassigned if there will be another iteration to the loop + pDel = pPrev; + sal_uLong nPrevNdIdx = pPrev->GetPos(); + BigPtrEntry* pTempEntry = &aTempEntries[sal_Int32(nCnt)]; + BigPtrArray::Replace( nPrevNdIdx+1, pTempEntry ); + if( nCnt ) + pPrev = BigPtrArray::operator []( nPrevNdIdx - 1 ); + // the accessed element can be a naked BigPtrEntry from + // aTempEntries, so the downcast to SwNode* in + // SwNodes::operator[] would be illegal (and unnecessary) + } + nDelPos = SwNodeOffset(pDel->GetPos() + 1); + } + + BigPtrArray::Remove( sal_Int32(nDelPos), sal_Int32(nSz) ); +} + +void SwNodes::InsertNode( SwNode* pNode, const SwNodeIndex& rPos ) +{ + BigPtrEntry* pIns = pNode; + BigPtrArray::Insert( pIns, sal_Int32(rPos.GetIndex()) ); +} + +void SwNodes::InsertNode( SwNode* pNode, SwNodeOffset nPos ) +{ + BigPtrEntry* pIns = pNode; + BigPtrArray::Insert( pIns, sal_Int32(nPos) ); +} + +// ->#112139# +SwNode * SwNodes::DocumentSectionStartNode(SwNode * pNode) const +{ + if (nullptr != pNode) + { + SwNodeIndex aIdx(*pNode); + + if (aIdx <= (*this)[SwNodeOffset(0)]->EndOfSectionIndex()) + pNode = (*this)[SwNodeOffset(0)]; + else + { + while ((*this)[SwNodeOffset(0)] != pNode->StartOfSectionNode()) + pNode = pNode->StartOfSectionNode(); + } + } + + return pNode; +} + +SwNode * SwNodes::DocumentSectionEndNode(SwNode * pNode) const +{ + return DocumentSectionStartNode(pNode)->EndOfSectionNode(); +} + +bool SwNodes::IsDocNodes() const +{ + return this == &m_rMyDoc.GetNodes(); +} + +void SwNodes::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwNodes")); + for (SwNodeOffset i(0); i < Count(); ++i) + (*this)[i]->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/observablethread.cxx b/sw/source/core/docnode/observablethread.cxx new file mode 100644 index 0000000000..98eaabb29e --- /dev/null +++ b/sw/source/core/docnode/observablethread.cxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <observablethread.hxx> +#include <ifinishedthreadlistener.hxx> +#include <memory> + +/* class for an observable thread + + #i73788# +*/ +ObservableThread::ObservableThread() + : mnThreadID( 0 ) +{ +} + +ObservableThread::~ObservableThread() +{ +} + +void ObservableThread::SetListener( std::weak_ptr< IFinishedThreadListener > const & pThreadListener, + const oslInterlockedCount nThreadID ) +{ + mpThreadListener = pThreadListener; + mnThreadID = nThreadID; +} + +void SAL_CALL ObservableThread::run() +{ + acquire(); + + threadFunction(); +} + +void SAL_CALL ObservableThread::onTerminated() +{ + // notify observer + std::shared_ptr< IFinishedThreadListener > pThreadListener = mpThreadListener.lock(); + if ( pThreadListener ) + { + pThreadListener->NotifyAboutFinishedThread( mnThreadID ); + } + + release(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/pausethreadstarting.cxx b/sw/source/core/docnode/pausethreadstarting.cxx new file mode 100644 index 0000000000..953bd3a3f8 --- /dev/null +++ b/sw/source/core/docnode/pausethreadstarting.cxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <pausethreadstarting.hxx> +#include <swthreadmanager.hxx> + +/* Helper class to pause starting of threads during existence of an instance + of this class + + #i73788# +*/ + +SwPauseThreadStarting::SwPauseThreadStarting() + : mbPausedThreadStarting(false) +{ + if (SwThreadManager::ExistsThreadManager() + && !SwThreadManager::GetThreadManager().StartingOfThreadsSuspended()) + { + SwThreadManager::GetThreadManager().SuspendStartingOfThreads(); + mbPausedThreadStarting = true; + } +} + +SwPauseThreadStarting::~SwPauseThreadStarting() COVERITY_NOEXCEPT_FALSE +{ + if (mbPausedThreadStarting) + { + SwThreadManager::GetThreadManager().ResumeStartingOfThreads(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrievedinputstreamdata.cxx b/sw/source/core/docnode/retrievedinputstreamdata.cxx new file mode 100644 index 0000000000..311be07ca7 --- /dev/null +++ b/sw/source/core/docnode/retrievedinputstreamdata.cxx @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <retrievedinputstreamdata.hxx> +#include <retrieveinputstreamconsumer.hxx> +#include <vcl/svapp.hxx> + +// #i73788# + +SwRetrievedInputStreamDataManager::tDataKey SwRetrievedInputStreamDataManager::snNextKeyValue = 1; + +SwRetrievedInputStreamDataManager& SwRetrievedInputStreamDataManager::GetManager() +{ + static SwRetrievedInputStreamDataManager theSwRetrievedInputStreamDataManager; + return theSwRetrievedInputStreamDataManager; +} + +SwRetrievedInputStreamDataManager::tDataKey SwRetrievedInputStreamDataManager::ReserveData( + std::weak_ptr< SwAsyncRetrieveInputStreamThreadConsumer > const & pThreadConsumer ) +{ + std::unique_lock aGuard(maMutex); + + // create empty data container for given thread Consumer + tDataKey nDataKey( snNextKeyValue ); + tData aNewEntry( pThreadConsumer ); + maInputStreamData[ nDataKey ] = aNewEntry; + + // prepare next data key value + if ( snNextKeyValue < SAL_MAX_UINT64 ) + { + ++snNextKeyValue; + } + else + { + snNextKeyValue = 1; + } + + return nDataKey; +} + +void SwRetrievedInputStreamDataManager::PushData( + const tDataKey nDataKey, + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ) +{ + std::unique_lock aGuard(maMutex); + + std::map< tDataKey, tData >::iterator aIter = maInputStreamData.find( nDataKey ); + + if ( aIter == maInputStreamData.end() ) + return; + + // Fill data container. + (*aIter).second.mxInputStream = xInputStream; + (*aIter).second.mbIsStreamReadOnly = bIsStreamReadOnly; + + // post user event to process the retrieved input stream data + if ( GetpApp() ) + { + + tDataKey* pDataKey = new tDataKey; + *pDataKey = nDataKey; + Application::PostUserEvent( LINK( this, SwRetrievedInputStreamDataManager, LinkedInputStreamReady ), pDataKey ); + } + else + { + // no application available -> discard data + maInputStreamData.erase( aIter ); + } +} + +bool SwRetrievedInputStreamDataManager::PopData( const tDataKey nDataKey, + tData& rData ) +{ + std::unique_lock aGuard(maMutex); + + bool bDataProvided( false ); + + std::map< tDataKey, tData >::iterator aIter = maInputStreamData.find( nDataKey ); + + if ( aIter != maInputStreamData.end() ) + { + rData.mpThreadConsumer = (*aIter).second.mpThreadConsumer; + rData.mxInputStream = (*aIter).second.mxInputStream; + rData.mbIsStreamReadOnly = (*aIter).second.mbIsStreamReadOnly; + + maInputStreamData.erase( aIter ); + + bDataProvided = true; + } + + return bDataProvided; +} + +/** callback function, which is triggered by input stream data manager on + filling of the data container to provide retrieved input stream to the + thread Consumer using <Application::PostUserEvent(..)> + + #i73788# + Note: This method has to be run in the main thread. +*/ +IMPL_STATIC_LINK( SwRetrievedInputStreamDataManager, LinkedInputStreamReady, + void*, p, void ) +{ + SwRetrievedInputStreamDataManager::tDataKey* pDataKey = static_cast<SwRetrievedInputStreamDataManager::tDataKey*>(p); + if ( !pDataKey ) + { + return; + } + + SwRetrievedInputStreamDataManager& rDataManager = + SwRetrievedInputStreamDataManager::GetManager(); + SwRetrievedInputStreamDataManager::tData aInputStreamData; + if ( rDataManager.PopData( *pDataKey, aInputStreamData ) ) + { + std::shared_ptr< SwAsyncRetrieveInputStreamThreadConsumer > pThreadConsumer = + aInputStreamData.mpThreadConsumer.lock(); + if ( pThreadConsumer ) + { + pThreadConsumer->ApplyInputStream( aInputStreamData.mxInputStream, + aInputStreamData.mbIsStreamReadOnly ); + } + } + delete pDataKey; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrieveinputstream.cxx b/sw/source/core/docnode/retrieveinputstream.cxx new file mode 100644 index 0000000000..cc4e60758c --- /dev/null +++ b/sw/source/core/docnode/retrieveinputstream.cxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <retrieveinputstream.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <unotools/mediadescriptor.hxx> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <utility> + +/* class for a thread to retrieve an input stream given by a URL + + #i73788# +*/ +::rtl::Reference< ObservableThread > SwAsyncRetrieveInputStreamThread::createThread( + const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + const OUString& rLinkedURL, const OUString& rReferer ) +{ + SwAsyncRetrieveInputStreamThread* pNewThread = + new SwAsyncRetrieveInputStreamThread( nDataKey, rLinkedURL, rReferer ); + return pNewThread; +} + +SwAsyncRetrieveInputStreamThread::SwAsyncRetrieveInputStreamThread( + const SwRetrievedInputStreamDataManager::tDataKey nDataKey, + OUString aLinkedURL, + OUString aReferer ) + : mnDataKey( nDataKey ), + mrLinkedURL(std::move( aLinkedURL )), + mrReferer(std::move( aReferer )) +{ +} + +SwAsyncRetrieveInputStreamThread::~SwAsyncRetrieveInputStreamThread() +{ +} + +void SwAsyncRetrieveInputStreamThread::threadFunction() +{ + osl_setThreadName("SwAsyncRetrieveInputStreamThread"); + + css::uno::Sequence < css::beans::PropertyValue > xProps{ + comphelper::makePropertyValue("URL", mrLinkedURL), + comphelper::makePropertyValue("Referer", mrReferer) + }; + utl::MediaDescriptor aMedium( xProps ); + + aMedium.addInputStream(); + + css::uno::Reference<css::io::XInputStream> xInputStream; + aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream; + if ( !xInputStream.is() ) + { + css::uno::Reference<css::io::XStream> xStream; + aMedium[utl::MediaDescriptor::PROP_STREAM] >>= xStream; + if ( xStream.is() ) + { + xInputStream = xStream->getInputStream(); + } + } + + SwRetrievedInputStreamDataManager::GetManager().PushData( mnDataKey, + xInputStream, + aMedium.isStreamReadOnly() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/retrieveinputstreamconsumer.cxx b/sw/source/core/docnode/retrieveinputstreamconsumer.cxx new file mode 100644 index 0000000000..d89a053610 --- /dev/null +++ b/sw/source/core/docnode/retrieveinputstreamconsumer.cxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <retrieveinputstreamconsumer.hxx> +#include <ndgrf.hxx> +#include <retrieveinputstream.hxx> +#include <swthreadmanager.hxx> + +/* class to provide creation of a thread to retrieve an input stream given by + a URL and to consume the retrieved input stream. + + #i73788# +*/ +SwAsyncRetrieveInputStreamThreadConsumer::SwAsyncRetrieveInputStreamThreadConsumer( + SwGrfNode& rGrfNode ) + : mrGrfNode( rGrfNode ), + mnThreadID( 0 ) +{ +} + +SwAsyncRetrieveInputStreamThreadConsumer::~SwAsyncRetrieveInputStreamThreadConsumer() COVERITY_NOEXCEPT_FALSE +{ + SwThreadManager::GetThreadManager().RemoveThread( mnThreadID ); +} + +void SwAsyncRetrieveInputStreamThreadConsumer::CreateThread( const OUString& rURL, const OUString& rReferer ) +{ + // Get new data container for input stream data + SwRetrievedInputStreamDataManager::tDataKey nDataKey = + SwRetrievedInputStreamDataManager::GetManager().ReserveData( + mrGrfNode.GetThreadConsumer() ); + + rtl::Reference< ObservableThread > pNewThread = + SwAsyncRetrieveInputStreamThread::createThread( nDataKey, rURL, rReferer ); + + // Add thread to thread manager and pass ownership of thread to thread manager. + mnThreadID = SwThreadManager::GetThreadManager().AddThread( pNewThread ); +} + +void SwAsyncRetrieveInputStreamThreadConsumer::ApplyInputStream( + css::uno::Reference<css::io::XInputStream> const & xInputStream, + const bool bIsStreamReadOnly ) +{ + mrGrfNode.ApplyInputStream( xInputStream, bIsStreamReadOnly ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/section.cxx b/sw/source/core/docnode/section.cxx new file mode 100644 index 0000000000..12d9e281c2 --- /dev/null +++ b/sw/source/core/docnode/section.cxx @@ -0,0 +1,1511 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <libxml/xmlstring.h> +#include <libxml/xmlwriter.h> +#include <stdlib.h> +#include <hintids.hxx> +#include <osl/diagnose.h> +#include <sot/exchange.hxx> +#include <svl/stritem.hxx> +#include <sfx2/docfile.hxx> +#include <editeng/protitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/sfxsids.hrc> +#include <docary.hxx> +#include <fmtcntnt.hxx> +#include <fmtpdsc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <editsh.hxx> +#include <hints.hxx> +#include <docsh.hxx> +#include <ndtxt.hxx> +#include <section.hxx> +#include <swserv.hxx> +#include <shellio.hxx> +#include <poolfmt.hxx> +#include <swbaslnk.hxx> +#include <mvsave.hxx> +#include <ftnidx.hxx> +#include <doctxm.hxx> +#include <fmteiro.hxx> +#include <unosection.hxx> +#include <calbck.hxx> +#include <fmtclds.hxx> +#include <algorithm> +#include <utility> +#include "ndsect.hxx" + +using namespace ::com::sun::star; + +namespace { + class SwIntrnlSectRefLink : public SwBaseLink + { + SwSectionFormat& m_rSectFormat; + + public: + SwIntrnlSectRefLink(SwSectionFormat& rFormat, SfxLinkUpdateMode nUpdateType) + : SwBaseLink(nUpdateType, SotClipboardFormatId::RTF) + , m_rSectFormat(rFormat) + {} + + virtual void Closed() override; + virtual ::sfx2::SvBaseLink::UpdateResult DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) override; + + virtual const SwNode* GetAnchor() const override; + virtual bool IsInRange( SwNodeOffset nSttNd, SwNodeOffset nEndNd ) const override; + + SwSectionNode* GetSectNode() + { + const SwNode* pSectNd( GetAnchor() ); + return const_cast<SwSectionNode*>( pSectNd->GetSectionNode() ); + } + }; +} + +SwSectionData::SwSectionData(SectionType const eType, OUString aName) + : m_eType(eType) + , m_sSectionName(std::move(aName)) + , m_bHiddenFlag(false) + , m_bProtectFlag(false) + , m_bEditInReadonlyFlag(false) // edit in readonly sections + , m_bHidden(false) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(true) +{ +} + +// this must have the same semantics as operator=() +SwSectionData::SwSectionData(SwSection const& rSection) + : m_eType(rSection.GetType()) + , m_sSectionName(rSection.GetSectionName()) + , m_sCondition(rSection.GetCondition()) + , m_sLinkFileName(rSection.GetLinkFileName()) + , m_sLinkFilePassword(rSection.GetLinkFilePassword()) + , m_Password(rSection.GetPassword()) + , m_bHiddenFlag(rSection.IsHiddenFlag()) + , m_bProtectFlag(rSection.IsProtect()) + // edit in readonly sections + , m_bEditInReadonlyFlag(rSection.IsEditInReadonly()) + , m_bHidden(rSection.IsHidden()) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(rSection.IsConnectFlag()) +{ +} + +// this must have the same semantics as operator=() +SwSectionData::SwSectionData(SwSectionData const& rOther) + : m_eType(rOther.m_eType) + , m_sSectionName(rOther.m_sSectionName) + , m_sCondition(rOther.m_sCondition) + , m_sLinkFileName(rOther.m_sLinkFileName) + , m_sLinkFilePassword(rOther.m_sLinkFilePassword) + , m_Password(rOther.m_Password) + , m_bHiddenFlag(rOther.m_bHiddenFlag) + , m_bProtectFlag(rOther.m_bProtectFlag) + // edit in readonly sections + , m_bEditInReadonlyFlag(rOther.m_bEditInReadonlyFlag) + , m_bHidden(rOther.m_bHidden) + , m_bCondHiddenFlag(true) + , m_bConnectFlag(rOther.m_bConnectFlag) +{ +} + +// the semantics here are weird for reasons of backward compatibility +SwSectionData & SwSectionData::operator= (SwSectionData const& rOther) +{ + m_eType = rOther.m_eType; + m_sSectionName = rOther.m_sSectionName; + m_sCondition = rOther.m_sCondition; + m_sLinkFileName = rOther.m_sLinkFileName; + m_sLinkFilePassword = rOther.m_sLinkFilePassword; + m_bConnectFlag = rOther.m_bConnectFlag; + m_Password = rOther.m_Password; + + m_bEditInReadonlyFlag = rOther.m_bEditInReadonlyFlag; + m_bProtectFlag = rOther.m_bProtectFlag; + + m_bHidden = rOther.m_bHidden; + // FIXME: old code did not assign m_bHiddenFlag ? + // FIXME: why should m_bCondHiddenFlag always default to true? + m_bCondHiddenFlag = true; + + return *this; +} + +// the semantics here are weird for reasons of backward compatibility +bool SwSectionData::operator==(SwSectionData const& rOther) const +{ + return (m_eType == rOther.m_eType) + && (m_sSectionName == rOther.m_sSectionName) + && (m_sCondition == rOther.m_sCondition) + && (m_bHidden == rOther.m_bHidden) + && (m_bProtectFlag == rOther.m_bProtectFlag) + && (m_bEditInReadonlyFlag == rOther.m_bEditInReadonlyFlag) + && (m_sLinkFileName == rOther.m_sLinkFileName) + && (m_sLinkFilePassword == rOther.m_sLinkFilePassword) + && (m_Password == rOther.m_Password); + // FIXME: old code ignored m_bCondHiddenFlag m_bHiddenFlag m_bConnectFlag +} + +void SwSectionData::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwSectionData")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("section-name"), BAD_CAST(m_sSectionName.toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SwSection::SwSection( + SectionType const eType, OUString const& rName, SwSectionFormat & rFormat) + : SwClient(& rFormat) + , m_Data(eType, rName) +{ + StartListening(rFormat.GetNotifier()); + + SwSection *const pParentSect = GetParent(); + if( pParentSect ) + { + // edit in readonly sections + m_Data.SetEditInReadonlyFlag( pParentSect->IsEditInReadonlyFlag() ); + } + + m_Data.SetProtectFlag( rFormat.GetProtect().IsContentProtected() ); + + if (!m_Data.IsEditInReadonlyFlag()) // edit in readonly sections + { + m_Data.SetEditInReadonlyFlag( rFormat.GetEditInReadonly().GetValue() ); + } +} + +SwSection::~SwSection() +{ + SwSectionFormat* pFormat = GetFormat(); + if( !pFormat ) + return; + + SwDoc* pDoc = pFormat->GetDoc(); + if( pDoc->IsInDtor() ) + { + // We reattach our Format to the default FrameFormat + // to not get any dependencies + if( pFormat->DerivedFrom() != pDoc->GetDfltFrameFormat() ) + pFormat->RegisterToFormat( *pDoc->GetDfltFrameFormat() ); + } + else + { + pFormat->Remove( this ); // remove + SvtListener::EndListeningAll(); + + if (SectionType::Content != m_Data.GetType()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + + if (m_RefObj.is()) + { + pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( m_RefObj.get() ); + } + + // If the Section is the last Client in the Format we can delete it + pFormat->RemoveAllUnos(); + if( !pFormat->HasWriterListeners() ) + { + // Do not add to the Undo. This should've happened earlier. + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + pDoc->DelSectionFormat( pFormat ); + } + } + if (m_RefObj.is()) + { + m_RefObj->Closed(); + } +} + +void SwSection::SetSectionData(SwSectionData const& rData) +{ + bool const bOldHidden( m_Data.IsHidden() ); + m_Data = rData; + // The next two may actually overwrite the m_Data.m_bProtect or EditInReadonly Flag + // in Modify, which should result in same flag value as the old code! + SetProtect(m_Data.IsProtectFlag()); + SetEditInReadonly(m_Data.IsEditInReadonlyFlag()); + if (bOldHidden != m_Data.IsHidden()) // check if changed... + { + ImplSetHiddenFlag(m_Data.IsHidden(), m_Data.IsCondHidden()); + } +} + +bool SwSection::DataEquals(SwSectionData const& rCmp) const +{ + // note that the old code compared the flags of the parameter with the + // format attributes of this; the following mess should do the same... + (void) GetLinkFileName(); // updates m_sLinkFileName + bool const bProtect(m_Data.IsProtectFlag()); + bool const bEditInReadonly(m_Data.IsEditInReadonlyFlag()); + m_Data.SetProtectFlag(IsProtect()); + m_Data.SetEditInReadonlyFlag(IsEditInReadonly()); + bool const bResult( m_Data == rCmp ); + m_Data.SetProtectFlag(bProtect); + m_Data.SetEditInReadonlyFlag(bEditInReadonly); + return bResult; +} + +void SwSection::ImplSetHiddenFlag(bool const bTmpHidden, bool const bCondition) +{ + SwSectionFormat* pFormat = GetFormat(); + OSL_ENSURE(pFormat, "ImplSetHiddenFlag: no format?"); + if( !pFormat ) + return; + + const bool bHide = bTmpHidden && bCondition; + + if (bHide) // should be hidden + { + if (!m_Data.IsHiddenFlag()) // is not hidden + { + // Is the Parent hidden? + // This should be shown by the bHiddenFlag. + + // Tell all Children that they are hidden + const sw::SectionHidden aHint; + pFormat->CallSwClientNotify(aHint); + + // Delete all Frames + pFormat->DelFrames(); + } + } + else if (m_Data.IsHiddenFlag()) // show Nodes again + { + // Show all Frames (Child Sections are accounted for by MakeFrames) + // Only if the Parent Section is not restricting us! + SwSection* pParentSect = pFormat->GetParentSection(); + if( !pParentSect || !pParentSect->IsHiddenFlag() ) + { + // Tell all Children that the Parent is not hidden anymore + const sw::SectionHidden aHint(false); + pFormat->CallSwClientNotify(aHint); + + pFormat->MakeFrames(); + } + } +} + +bool SwSection::CalcHiddenFlag() const +{ + const SwSection* pSect = this; + do { + if( pSect->IsHidden() && pSect->IsCondHidden() ) + return true; + } while( nullptr != ( pSect = pSect->GetParent()) ); + + return false; +} + +bool SwSection::IsProtect() const +{ + SwSectionFormat const *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::IsProtect: no format?"); + return pFormat + ? pFormat->GetProtect().IsContentProtected() + : IsProtectFlag(); +} + +// edit in readonly sections +bool SwSection::IsEditInReadonly() const +{ + SwSectionFormat const *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::IsEditInReadonly: no format?"); + return pFormat + ? pFormat->GetEditInReadonly().GetValue() + : IsEditInReadonlyFlag(); +} + +void SwSection::SetHidden(bool const bFlag) +{ + if (!m_Data.IsHidden() == !bFlag) + return; + + m_Data.SetHidden(bFlag); + ImplSetHiddenFlag(bFlag, m_Data.IsCondHidden()); +} + +void SwSection::SetProtect(bool const bFlag) +{ + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::SetProtect: no format?"); + if (pFormat) + { + SvxProtectItem aItem( RES_PROTECT ); + aItem.SetContentProtect( bFlag ); + pFormat->SetFormatAttr( aItem ); + // note: this will call m_Data.SetProtectFlag via Modify! + } + else + { + m_Data.SetProtectFlag(bFlag); + } +} + +// edit in readonly sections +void SwSection::SetEditInReadonly(bool const bFlag) +{ + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::SetEditInReadonly: no format?"); + if (pFormat) + { + SwFormatEditInReadonly aItem; + aItem.SetValue( bFlag ); + pFormat->SetFormatAttr( aItem ); + // note: this will call m_Data.SetEditInReadonlyFlag via Modify! + } + else + { + m_Data.SetEditInReadonlyFlag(bFlag); + } +} + +void SwSection::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + Notify(rHint); +} + +void SwSection::Notify(SfxHint const& rHint) +{ + if (rHint.GetId() == SfxHintId::SwSectionHidden) + { + auto rSectionHidden = static_cast<const sw::SectionHidden&>(rHint); + m_Data.SetHiddenFlag(rSectionHidden.m_isHidden || (m_Data.IsHidden() && m_Data.IsCondHidden())); + return; + } else if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + auto pOld = pLegacy->m_pOld; + auto pNew = pLegacy->m_pNew; + bool bUpdateFootnote = false; + switch(pLegacy->GetWhich()) + { + case RES_ATTRSET_CHG: + if (pNew && pOld) + { + SfxItemSet* pNewSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pNew))->GetChgSet(); + SfxItemSet* pOldSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pOld))->GetChgSet(); + + if( const SvxProtectItem* pItem = pNewSet->GetItemIfSet( + RES_PROTECT, false ) ) + { + m_Data.SetProtectFlag( pItem->IsContentProtected() ); + pNewSet->ClearItem( RES_PROTECT ); + pOldSet->ClearItem( RES_PROTECT ); + } + + // --> edit in readonly sections + if( const SwFormatEditInReadonly* pItem = pNewSet->GetItemIfSet( + RES_EDIT_IN_READONLY, false ) ) + { + m_Data.SetEditInReadonlyFlag(pItem->GetValue()); + pNewSet->ClearItem( RES_EDIT_IN_READONLY ); + pOldSet->ClearItem( RES_EDIT_IN_READONLY ); + } + + if( SfxItemState::SET == pNewSet->GetItemState( + RES_FTN_AT_TXTEND, false ) || + SfxItemState::SET == pNewSet->GetItemState( + RES_END_AT_TXTEND, false )) + { + bUpdateFootnote = true; + } + + if( !pNewSet->Count() ) + return; + } + break; + + case RES_PROTECT: + if( pNew ) + { + bool bNewFlag = + static_cast<const SvxProtectItem*>(pNew)->IsContentProtected(); + // this used to inherit the flag from the parent, but then there is + // no way to turn it off in an inner section + m_Data.SetProtectFlag( bNewFlag ); + } + return; + // edit in readonly sections + case RES_EDIT_IN_READONLY: + if( pNew ) + { + const bool bNewFlag = + static_cast<const SwFormatEditInReadonly*>(pNew)->GetValue(); + m_Data.SetEditInReadonlyFlag( bNewFlag ); + } + return; + + case RES_COL: + // Is handled by the Layout, if appropriate + break; + + case RES_FTN_AT_TXTEND: + if( pNew && pOld ) + { + bUpdateFootnote = true; + } + break; + + case RES_END_AT_TXTEND: + if( pNew && pOld ) + { + bUpdateFootnote = true; + } + break; + + default: + CheckRegistration( pOld ); + break; + } + + if( bUpdateFootnote ) + { + SwSectionNode* pSectNd = GetFormat()->GetSectionNode(); + if( pSectNd ) + pSectNd->GetDoc().GetFootnoteIdxs().UpdateFootnote(*pSectNd); + } +} + +void SwSection::SetRefObject( SwServerObject* pObj ) +{ + m_RefObj = pObj; +} + +void SwSection::SetCondHidden(bool const bFlag) +{ + if (!m_Data.IsCondHidden() == !bFlag) + return; + + m_Data.SetCondHidden(bFlag); + ImplSetHiddenFlag(m_Data.IsHidden(), bFlag); +} + +// Set/remove the linked FileName +OUString const & SwSection::GetLinkFileName() const +{ + if (m_RefLink.is()) + { + OUString sTmp; + switch (m_Data.GetType()) + { + case SectionType::DdeLink: + sTmp = m_RefLink->GetLinkSourceName(); + break; + + case SectionType::FileLink: + { + OUString sRange; + OUString sFilter; + if (m_RefLink->GetLinkManager() && + sfx2::LinkManager::GetDisplayNames( + m_RefLink.get(), nullptr, &sTmp, &sRange, &sFilter )) + { + sTmp += OUStringChar(sfx2::cTokenSeparator) + sFilter + + OUStringChar(sfx2::cTokenSeparator) + sRange; + } + else if( GetFormat() && !GetFormat()->GetSectionNode() ) + { + // If the Section is in the UndoNodesArray, the LinkManager + // does not contain the Link, thus it cannot be queried for it. + // Thus return the current Name. + return m_Data.GetLinkFileName(); + } + } + break; + default: break; + } + m_Data.SetLinkFileName(sTmp); + } + return m_Data.GetLinkFileName(); +} + +void SwSection::SetLinkFileName(const OUString& rNew) +{ + if (m_RefLink.is()) + { + m_RefLink->SetLinkSourceName( rNew ); + } + m_Data.SetLinkFileName(rNew); +} + +// If it was a Linked Section, we need to make all Child Links visible +void SwSection::MakeChildLinksVisible( const SwSectionNode& rSectNd ) +{ + const SwNode* pNd; + const ::sfx2::SvBaseLinks& rLnks = rSectNd.GetDoc().getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for( auto n = rLnks.size(); n; ) + { + sfx2::SvBaseLink& rBLnk = *rLnks[--n]; + if (!rBLnk.IsVisible() && dynamic_cast<const SwBaseLink*>(&rBLnk) != nullptr + && nullptr != (pNd = static_cast<SwBaseLink&>(rBLnk).GetAnchor())) + { + pNd = pNd->StartOfSectionNode(); // If it's a SectionNode + const SwSectionNode* pParent; + while( nullptr != ( pParent = pNd->FindSectionNode() ) && + ( SectionType::Content == pParent->GetSection().GetType() + || pNd == &rSectNd )) + pNd = pParent->StartOfSectionNode(); + + // It's within a normal Section, so show again + if( !pParent ) + rBLnk.SetVisible(true); + } + } +} + +const SwTOXBase* SwSection::GetTOXBase() const +{ + const SwTOXBase* pRet = nullptr; + if( SectionType::ToxContent == GetType() ) + pRet = dynamic_cast<const SwTOXBaseSection*>(this); + return pRet; +} + +SwSectionFormat::SwSectionFormat( SwFrameFormat* pDrvdFrame, SwDoc *pDoc ) + : SwFrameFormat( pDoc->GetAttrPool(), OUString(), pDrvdFrame ) +{ + LockModify(); + SetFormatAttr( *GetDfltAttr( RES_COL ) ); + UnlockModify(); +} + +SwSectionFormat::~SwSectionFormat() +{ + if( GetDoc()->IsInDtor() ) + return; + + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent( false ).GetContentIdx(); + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwSection& rSect = pSectNd->GetSection(); + // If it was a linked Section, we need to make all Child Links + // visible again + if( rSect.IsConnected() ) + SwSection::MakeChildLinksVisible( *pSectNd ); + + // Check whether we need to be visible, before deleting the Nodes + if( rSect.IsHiddenFlag() ) + { + SwSection* pParentSect = rSect.GetParent(); + if( !pParentSect || !pParentSect->IsHiddenFlag() ) + { + // Make Nodes visible again + rSect.SetHidden(false); + } + } + // mba: test iteration; objects are removed while iterating + // use hint which allows to specify, if the content shall be saved or not + CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( true ) ); + + // Raise the Section up + SwNodeRange aRg( *pSectNd, SwNodeOffset(0), *pSectNd->EndOfSectionNode() ); + GetDoc()->GetNodes().SectionUp( &aRg ); + } + LockModify(); + ResetFormatAttr( RES_CNTNT ); + UnlockModify(); +} + +SwSection * SwSectionFormat::GetSection() const +{ + return SwIterator<SwSection,SwSectionFormat>( *this ).First(); +} + +// Do not destroy all Frames in aDepend (Frames are recognized with a dynamic_cast). +void SwSectionFormat::DelFrames() +{ + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + // First delete the <SwSectionFrame> of the <SwSectionFormat> instance + // mba: test iteration as objects are removed in iteration + // use hint which allows to specify, if the content shall be saved or not + CallSwClientNotify( SwSectionFrameMoveAndDeleteHint( false ) ); + + // Then delete frames of the nested <SwSectionFormat> instances + SwIterator<SwSectionFormat,SwSectionFormat> aIter( *this ); + SwSectionFormat *pLast = aIter.First(); + while ( pLast ) + { + pLast->DelFrames(); + pLast = aIter.Next(); + } + + SwNodeOffset nEnd = pSectNd->EndOfSectionIndex(); + SwNodeOffset nStart = pSectNd->GetIndex()+1; + sw_DeleteFootnote( pSectNd, nStart, nEnd ); + } + if( !pIdx ) + return; + + // Send Hint for PageDesc. Actually the Layout contained in the + // Paste of the Frame itself would need to do this. But that leads + // to subsequent errors, which we'd need to solve at run-time. + SwNodeIndex aNextNd( *pIdx ); + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( &aNextNd, true, false ); + if( pCNd ) + { + const SfxPoolItem& rItem = pCNd->GetSwAttrSet().Get(RES_PAGEDESC); + pCNd->CallSwClientNotify(sw::LegacyModifyHint(&rItem, &rItem)); + } +} + +// Create the Views +void SwSectionFormat::MakeFrames() +{ + SwSectionNode* pSectNd; + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + + if( pIdx && &GetDoc()->GetNodes() == &pIdx->GetNodes() && + nullptr != (pSectNd = pIdx->GetNode().GetSectionNode() )) + { + SwNodeIndex aIdx( *pIdx ); + pSectNd->MakeOwnFrames( &aIdx ); + } +} + +void SwSectionFormat::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwSectionHidden) + { + auto rSectionHidden = static_cast<const sw::SectionHidden&>(rHint); + auto pSect = GetSection(); + if(!pSect || rSectionHidden.m_isHidden == pSect->IsHiddenFlag()) // already at target state, skipping. + return; + GetNotifier().Broadcast(rSectionHidden); + return; + } else if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + sal_uInt16 nWhich = pLegacy->GetWhich(); + auto pOld = pLegacy->m_pOld; + auto pNew = pLegacy->m_pNew; + switch( nWhich ) + { + case RES_ATTRSET_CHG: + if (HasWriterListeners() && pOld && pNew) + { + SfxItemSet* pNewSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pNew))->GetChgSet(); + SfxItemSet* pOldSet = const_cast<SwAttrSetChg*>(static_cast<const SwAttrSetChg*>(pOld))->GetChgSet(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pNewSet->GetItemState( + RES_PROTECT, false, &pItem )) + { + GetNotifier().Broadcast(sw::LegacyModifyHint(pItem, pItem)); + pNewSet->ClearItem( RES_PROTECT ); + pOldSet->ClearItem( RES_PROTECT ); + } + + // --> edit in readonly sections + if( SfxItemState::SET == pNewSet->GetItemState( + RES_EDIT_IN_READONLY, false, &pItem ) ) + { + GetNotifier().Broadcast(sw::LegacyModifyHint(pItem, pItem)); + pNewSet->ClearItem( RES_EDIT_IN_READONLY ); + pOldSet->ClearItem( RES_EDIT_IN_READONLY ); + } + + if( SfxItemState::SET == pNewSet->GetItemState( + RES_FTN_AT_TXTEND, false, &pItem )) + { + GetNotifier().Broadcast(sw::LegacyModifyHint(pItem, pItem)); + pNewSet->ClearItem( RES_FTN_AT_TXTEND ); + pOldSet->ClearItem( RES_FTN_AT_TXTEND ); + } + if( SfxItemState::SET == pNewSet->GetItemState( + RES_END_AT_TXTEND, false, &pItem )) + { + GetNotifier().Broadcast(sw::LegacyModifyHint(pItem, pItem)); + pNewSet->ClearItem( RES_END_AT_TXTEND ); + pOldSet->ClearItem( RES_END_AT_TXTEND ); + } + if( !static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->Count() ) + return; + } + break; + + case RES_FTN_AT_TXTEND: + case RES_END_AT_TXTEND: + GetNotifier().Broadcast(sw::LegacyModifyHint(pOld, pNew)); + return; + case RES_PROTECT: + case RES_EDIT_IN_READONLY: // edit in readonly sections + // Pass through these Messages until the End of the tree! + GetNotifier().Broadcast(sw::LegacyModifyHint(pOld, pNew)); + return; // That's it! + + case RES_OBJECTDYING: + if( !GetDoc()->IsInDtor() && pOld && + static_cast<const SwPtrMsgPoolItem *>(pOld)->pObject == static_cast<void*>(GetRegisteredIn()) ) + { + // My Parents will be destroyed, so get the Parent's Parent + // and update + SwFrameFormat::SwClientNotify(rMod, rHint); + UpdateParent(); + return; + } + break; + + case RES_FMT_CHG: + if( !GetDoc()->IsInDtor() && + static_cast<const SwFormatChg*>(pNew)->pChangedFormat == static_cast<void*>(GetRegisteredIn()) && + dynamic_cast<const SwSectionFormat*>(static_cast<const SwFormatChg*>(pNew)->pChangedFormat) != nullptr ) + { + // My Parent will be changed, thus I need to update + SwFrameFormat::SwClientNotify(rMod, rHint); + UpdateParent(); + return; + } + break; + } + SwFrameFormat::SwClientNotify(rMod, rHint); + + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXTextSection(nullptr); + } +} + +void SwSectionFormat::SetXTextSection(rtl::Reference<SwXTextSection> const& xTextSection) +{ + m_wXTextSection = xTextSection.get(); +} + +bool SwSectionFormat::IsVisible() const +{ + if(SwFrameFormat::IsVisible()) + return true; + SwIterator<SwSectionFormat,SwSectionFormat> aFormatIter(*this); + for(SwSectionFormat* pChild = aFormatIter.First(); pChild; pChild = aFormatIter.Next()) + if(pChild->IsVisible()) + return true; + return false; +} + +// Get info from the Format +bool SwSectionFormat::GetInfo(SfxPoolItem& rInfo) const +{ + if(rInfo.Which() == RES_FINDNEARESTNODE) + { + if(GetFormatAttr( RES_PAGEDESC ).GetPageDesc()) + { + const SwSectionNode* pNd = GetSectionNode(); + if(pNd) + static_cast<SwFindNearestNode&>(rInfo).CheckNode(*pNd); + } + return true; + } + return sw::BroadcastingModify::GetInfo(rInfo); +} + +static bool lcl_SectionCmpPos( const SwSection *pFirst, const SwSection *pSecond) +{ + const SwSectionFormat* pFSectFormat = pFirst->GetFormat(); + const SwSectionFormat* pSSectFormat = pSecond->GetFormat(); + OSL_ENSURE( pFSectFormat && pSSectFormat && + pFSectFormat->GetContent(false).GetContentIdx() && + pSSectFormat->GetContent(false).GetContentIdx(), + "Invalid sections" ); + return pFSectFormat->GetContent(false).GetContentIdx()->GetIndex() < + pSSectFormat->GetContent(false).GetContentIdx()->GetIndex(); +} + +// get all Sections that have been derived from this one +void SwSectionFormat::GetChildSections( SwSections& rArr, + SectionSort eSort, + bool bAllSections ) const +{ + rArr.clear(); + + if( !HasWriterListeners() ) + return; + + SwIterator<SwSectionFormat,SwSectionFormat> aIter(*this); + const SwNodeIndex* pIdx; + for( SwSectionFormat* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + if( bAllSections || + ( nullptr != ( pIdx = pLast->GetContent(false). + GetContentIdx()) && &pIdx->GetNodes() == &GetDoc()->GetNodes() )) + { + SwSection* pDummy = pLast->GetSection(); + rArr.push_back( pDummy ); + } + + // Do we need any sorting? + if( 1 < rArr.size() ) + switch( eSort ) + { + case SectionSort::Pos: + std::sort( rArr.begin(), rArr.end(), lcl_SectionCmpPos ); + break; + case SectionSort::Not: break; + } +} + +// See whether the Section is within the Nodes or the UndoNodes array +bool SwSectionFormat::IsInNodesArr() const +{ + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + return pIdx && &pIdx->GetNodes() == &GetDoc()->GetNodes(); +} + +// Parent was changed +void SwSectionFormat::UpdateParent() +{ + if(!HasWriterListeners()) + return; + + const SwSection* pSection = GetSection(); + const SvxProtectItem* pProtect = &GetProtect(); + // edit in readonly sections + const SwFormatEditInReadonly* pEditInReadonly = &GetEditInReadonly(); + bool bIsHidden = pSection->IsHidden(); + if(GetRegisteredIn()) + { + const SwSection* pPS = GetParentSection(); + pProtect = &pPS->GetFormat()->GetProtect(); + pEditInReadonly = &pPS->GetFormat()->GetEditInReadonly(); + bIsHidden = pPS->IsHiddenFlag(); + } + if(!pProtect->IsContentProtected() != !pSection->IsProtectFlag()) + CallSwClientNotify(sw::LegacyModifyHint(pProtect, pProtect)); + + // edit in readonly sections + if(!pEditInReadonly->GetValue() != !pSection->IsEditInReadonlyFlag()) + CallSwClientNotify(sw::LegacyModifyHint(pEditInReadonly, pEditInReadonly)); + + if(bIsHidden == pSection->IsHiddenFlag()) + { + const sw::SectionHidden aHint(bIsHidden); + CallSwClientNotify(aHint); + } +} + +SwSectionNode* SwSectionFormat::GetSectionNode() +{ + const SwNodeIndex* pIdx = GetContent(false).GetContentIdx(); + if( pIdx && ( &pIdx->GetNodes() == &GetDoc()->GetNodes() )) + return pIdx->GetNode().GetSectionNode(); + return nullptr; +} + +// Is this Section valid for the GlobalDocument? +const SwSection* SwSectionFormat::GetGlobalDocSection() const +{ + const SwSectionNode* pNd = GetSectionNode(); + if( pNd && + ( SectionType::FileLink == pNd->GetSection().GetType() || + SectionType::ToxContent == pNd->GetSection().GetType() ) && + pNd->GetIndex() > pNd->GetNodes().GetEndOfExtras().GetIndex() && + !pNd->StartOfSectionNode()->IsSectionNode() && + !pNd->StartOfSectionNode()->FindSectionNode() ) + return &pNd->GetSection(); + return nullptr; +} + +// sw::Metadatable +::sfx2::IXmlIdRegistry& SwSectionFormat::GetRegistry() +{ + return GetDoc()->GetXmlIdRegistry(); +} + +bool SwSectionFormat::IsInClipboard() const +{ + return GetDoc()->IsClipBoard(); +} + +bool SwSectionFormat::IsInUndo() const +{ + return !IsInNodesArr(); +} + +bool SwSectionFormat::IsInContent() const +{ + SwNodeIndex const*const pIdx = GetContent(false).GetContentIdx(); + OSL_ENSURE(pIdx, "SwSectionFormat::IsInContent: no index?"); + return pIdx == nullptr || !GetDoc()->IsInHeaderFooter(pIdx->GetNode()); +} + +// n.b.: if the section format represents an index, then there is both a +// SwXDocumentIndex and a SwXTextSection instance for this single core object. +// these two can both implement XMetadatable and forward to the same core +// section format. but here only one UNO object can be returned, +// so always return the text section. +uno::Reference< rdf::XMetadatable > +SwSectionFormat::MakeUnoObject() +{ + uno::Reference<rdf::XMetadatable> xMeta; + SwSection *const pSection( GetSection() ); + if (pSection) + { + xMeta = SwXTextSection::CreateXTextSection(this, + SectionType::ToxHeader == pSection->GetType()); + } + return xMeta; +} + +bool SwSectionFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return false; +} + +void SwSectionFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwSectionFormat")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + GetAttrSet().dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +void SwSectionFormats::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwSectionFormats")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +// Method to break section links inside a linked section +static void lcl_BreakSectionLinksInSect( const SwSectionNode& rSectNd ) +{ + if ( !rSectNd.GetSection().IsConnected() ) + { + OSL_FAIL( "method <lcl_RemoveSectionLinksInSect(..)> - no Link at Section of SectionNode" ); + return; + } + const ::sfx2::SvBaseLink* pOwnLink( &(rSectNd.GetSection().GetBaseLink() ) ); + const ::sfx2::SvBaseLinks& rLnks = rSectNd.GetDoc().getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for ( auto n = rLnks.size(); n > 0; ) + { + SwIntrnlSectRefLink* pSectLnk = dynamic_cast<SwIntrnlSectRefLink*>(&(*rLnks[ --n ])); + if ( pSectLnk && pSectLnk != pOwnLink && + pSectLnk->IsInRange( rSectNd.GetIndex(), rSectNd.EndOfSectionIndex() ) ) + { + // break the link of the corresponding section. + // the link is also removed from the link manager + SwSectionNode* pSectNode = pSectLnk->GetSectNode(); + assert(pSectNode); + pSectNode->GetSection().BreakLink(); + + // for robustness, because link is removed from the link manager + if ( n > rLnks.size() ) + { + n = rLnks.size(); + } + } + } +} + +static void lcl_UpdateLinksInSect( const SwBaseLink& rUpdLnk, SwSectionNode& rSectNd ) +{ + SwDoc& rDoc = rSectNd.GetDoc(); + SwDocShell* pDShell = rDoc.GetDocShell(); + if( !pDShell || !pDShell->GetMedium() ) + return ; + + const OUString sName( pDShell->GetMedium()->GetName() ); + const OUString sMimeType( SotExchange::GetFormatMimeType( SotClipboardFormatId::SIMPLE_FILE )); + uno::Any aValue; + aValue <<= sName; // Arbitrary name + + const ::sfx2::SvBaseLinks& rLnks = rDoc.getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + for( auto n = rLnks.size(); n; ) + { + ::sfx2::SvBaseLink* pLnk = &(*rLnks[ --n ]); + if( pLnk == &rUpdLnk ) + continue; + if( sfx2::SvBaseLinkObjectType::ClientFile != pLnk->GetObjType() ) + continue; + SwBaseLink* pBLink = dynamic_cast<SwBaseLink*>( pLnk ); + if( pBLink && pBLink->IsInRange( rSectNd.GetIndex(), + rSectNd.EndOfSectionIndex() ) ) + { + // It's in the Section, so update. But only if it's not in the same File! + OUString sFName; + sfx2::LinkManager::GetDisplayNames( pBLink, nullptr, &sFName ); + if( sFName != sName ) + { + pBLink->DataChanged( sMimeType, aValue ); + + // If needed find the Link pointer to avoid skipping one or calling one twice + if( n >= rLnks.size() && 0 != ( n = rLnks.size() )) + --n; + + if( n && pLnk != &(*rLnks[ n ]) ) + { + // Find - it can only precede it! + while( n ) + if( pLnk == &(*rLnks[ --n ] ) ) + break; + } + } + } + } +} + +::sfx2::SvBaseLink::UpdateResult SwIntrnlSectRefLink::DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) +{ + SwSectionNode* pSectNd = m_rSectFormat.GetSectionNode(); + SwDoc* pDoc = m_rSectFormat.GetDoc(); + + SotClipboardFormatId nDataFormat = SotExchange::GetFormatIdFromMimeType( rMimeType ); + + if( !pSectNd || !pDoc || pDoc->IsInDtor() || ChkNoDataFlag() || + sfx2::LinkManager::RegisterStatusInfoId() == nDataFormat ) + { + // Should we be in the Undo already? + return SUCCESS; + } + + // #i38810# - Due to possible existing signatures, the + // document has to be modified after updating a link. + pDoc->getIDocumentState().SetModified(); + // set additional flag that links have been updated, in order to check this + // during load. + pDoc->getIDocumentLinksAdministration().SetLinksUpdated( true ); + + // Always switch off Undo + bool const bWasUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + bool bWasVisibleLinks = pDoc->getIDocumentLinksAdministration().IsVisibleLinks(); + pDoc->getIDocumentLinksAdministration().SetVisibleLinks( false ); + + SwPaM* pPam; + SwViewShell* pVSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = pDoc->GetEditShell(); + pDoc->getIDocumentFieldsAccess().LockExpFields(); + { + // Insert an empty TextNode at the Section's start + SwNodeIndex aIdx( *pSectNd, +1 ); + SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() ); + pDoc->GetNodes().MakeTextNode( aIdx.GetNode(), + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + + if( pESh ) + pESh->StartAllAction(); + else if( pVSh ) + pVSh->StartAction(); + + SwPosition aPos( aIdx, SwNodeOffset(-1) ); + SwDoc::CorrAbs( aIdx, aEndIdx, aPos, true ); + + pPam = new SwPaM( aPos ); + + // Delete everything succeeding it + --aIdx; + DelFlyInRange( aIdx.GetNode(), aEndIdx.GetNode() ); + DelBookmarks(aIdx.GetNode(), aEndIdx.GetNode()); + ++aIdx; + + pDoc->GetNodes().Delete( aIdx, aEndIdx.GetIndex() - aIdx.GetIndex() ); + } + + SwSection& rSection = pSectNd->GetSection(); + rSection.SetConnectFlag(false); + + Reader* pRead = nullptr; + switch( nDataFormat ) + { + case SotClipboardFormatId::STRING: + pRead = ReadAscii; + break; + + case SotClipboardFormatId::RICHTEXT: + case SotClipboardFormatId::RTF: + pRead = SwReaderWriter::GetRtfReader(); + break; + + case SotClipboardFormatId::SIMPLE_FILE: + if ( rValue.hasValue() ) + { + OUString sFileName; + if ( !(rValue >>= sFileName) ) + break; + OUString sFilter; + OUString sRange; + sfx2::LinkManager::GetDisplayNames( this, nullptr, &sFileName, + &sRange, &sFilter ); + + RedlineFlags eOldRedlineFlags = RedlineFlags::NONE; + SfxObjectShellRef xDocSh; + SfxObjectShellLock xLockRef; + int nRet; + if( sFileName.isEmpty() ) + { + xDocSh = pDoc->GetDocShell(); + nRet = 1; + } + else + { + nRet = SwFindDocShell( xDocSh, xLockRef, sFileName, + rSection.GetLinkFilePassword(), + sFilter, 0, pDoc->GetDocShell() ); + if( nRet ) + { + SwDoc* pSrcDoc = static_cast<SwDocShell*>( xDocSh.get() )->GetDoc(); + eOldRedlineFlags = pSrcDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pSrcDoc->getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowInsert ); + } + } + + if( nRet ) + { + rSection.SetConnectFlag(); + + SwNodeIndex aSave( pPam->GetPoint()->GetNode(), -1 ); + std::optional<SwNodeRange> oCpyRg; + + if( xDocSh->GetMedium() && + rSection.GetLinkFilePassword().isEmpty() ) + { + if( const SfxStringItem* pItem = xDocSh->GetMedium()->GetItemSet(). + GetItemIfSet( SID_PASSWORD, false ) ) + rSection.SetLinkFilePassword( pItem->GetValue() ); + } + + SwDoc* pSrcDoc = static_cast<SwDocShell*>( xDocSh.get() )->GetDoc(); + + if( !sRange.isEmpty() ) + { + // Catch recursion + bool bRecursion = false; + if( pSrcDoc == pDoc ) + { + tools::SvRef<SwServerObject> refObj( static_cast<SwServerObject*>( + pDoc->getIDocumentLinksAdministration().CreateLinkSource( sRange ))); + if( refObj.is() ) + { + bRecursion = refObj->IsLinkInServer( this ) || + ChkNoDataFlag(); + } + } + + SwNode& rInsPos = pPam->GetPoint()->GetNode(); + + SwPaM* pCpyPam = nullptr; + if( !bRecursion && + pSrcDoc->GetDocumentLinksAdministrationManager().SelectServerObj( sRange, pCpyPam, oCpyRg ) + && pCpyPam ) + { + if( pSrcDoc != pDoc || + pCpyPam->Start()->GetNode() > rInsPos || + rInsPos >= pCpyPam->End()->GetNode() ) + { + pSrcDoc->getIDocumentContentOperations().CopyRange(*pCpyPam, *pPam->GetPoint(), SwCopyFlags::CheckPosInFly); + } + delete pCpyPam; + } + if( oCpyRg && pSrcDoc == pDoc && + oCpyRg->aStart < rInsPos && rInsPos < oCpyRg->aEnd.GetNode() ) + { + oCpyRg.reset(); + } + } + else if( pSrcDoc != pDoc ) + oCpyRg.emplace( pSrcDoc->GetNodes().GetEndOfExtras(), SwNodeOffset(2), + pSrcDoc->GetNodes().GetEndOfContent() ); + + // #i81653# + // Update links of extern linked document or extern linked + // document section, if section is protected. + if ( pSrcDoc != pDoc && + rSection.IsProtectFlag() ) + { + pSrcDoc->getIDocumentLinksAdministration().GetLinkManager().UpdateAllLinks( false, false, nullptr ); + } + + if( oCpyRg ) + { + SwNode& rInsPos = pPam->GetPoint()->GetNode(); + bool bCreateFrame = rInsPos <= pDoc->GetNodes().GetEndOfExtras() || + rInsPos.FindTableNode(); + + SwTableNumFormatMerge aTNFM( *pSrcDoc, *pDoc ); + + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*oCpyRg, rInsPos, nullptr, bCreateFrame); + ++aSave; + + if( !bCreateFrame ) + ::MakeFrames( pDoc, aSave.GetNode(), rInsPos ); + + // Delete last Node, only if it was copied successfully + // (the Section contains more than one Node) + if( SwNodeOffset(2) < pSectNd->EndOfSectionIndex() - pSectNd->GetIndex() ) + { + aSave = rInsPos; + pPam->Move( fnMoveBackward, GoInNode ); + pPam->SetMark(); // Rewire both SwPositions + + pDoc->CorrAbs( aSave.GetNode(), *pPam->GetPoint(), 0, true ); + pDoc->GetNodes().Delete( aSave ); + } + oCpyRg.reset(); + } + + lcl_BreakSectionLinksInSect( *pSectNd ); + + // Update all Links in this Section + lcl_UpdateLinksInSect( *this, *pSectNd ); + } + if( xDocSh.is() ) + { + if( 2 == nRet ) + xDocSh->DoClose(); + else if( static_cast<SwDocShell*>( xDocSh.get() )->GetDoc() ) + static_cast<SwDocShell*>( xDocSh.get() )->GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( + eOldRedlineFlags ); + } + } + break; + default: break; + } + + // Only create DDE if Shell is available! + uno::Sequence< sal_Int8 > aSeq; + if( pRead && rValue.hasValue() && ( rValue >>= aSeq ) ) + { + if( pESh ) + { + pESh->Push(); + SwPaM* pCursor = pESh->GetCursor(); + *pCursor->GetPoint() = *pPam->GetPoint(); + delete pPam; + pPam = pCursor; + } + + SvMemoryStream aStrm( const_cast<sal_Int8 *>(aSeq.getConstArray()), aSeq.getLength(), + StreamMode::READ ); + aStrm.Seek( 0 ); + + // TODO/MBA: it's impossible to set a BaseURL here! + SwReader aTmpReader( aStrm, OUString(), pDoc->GetDocShell()->GetMedium()->GetBaseURL(), *pPam ); + + if( ! aTmpReader.Read( *pRead ).IsError() ) + { + rSection.SetConnectFlag(); + } + + if( pESh ) + { + pESh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pPam = nullptr; // pam was deleted earlier + } + } + + // remove all undo actions and turn undo on again + pDoc->GetIDocumentUndoRedo().DelAllUndoObj(); + pDoc->GetIDocumentUndoRedo().DoUndo(bWasUndo); + pDoc->getIDocumentLinksAdministration().SetVisibleLinks( bWasVisibleLinks ); + + pDoc->getIDocumentFieldsAccess().UnlockExpFields(); + if( !pDoc->getIDocumentFieldsAccess().IsExpFieldsLocked() ) + pDoc->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + if( pESh ) + pESh->EndAllAction(); + else if( pVSh ) + pVSh->EndAction(); + delete pPam; // Was created at the start + + return SUCCESS; +} + +void SwIntrnlSectRefLink::Closed() +{ + SwDoc* pDoc = m_rSectFormat.GetDoc(); + if( pDoc && !pDoc->IsInDtor() ) + { + // Advise says goodbye: mark the Section as not protected + // and change the Flag + const SwSectionFormats& rFormats = pDoc->GetSections(); + for( auto n = rFormats.size(); n; ) + if (rFormats[--n] == &m_rSectFormat) + { + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + SwEditShell* pESh = pDoc->GetEditShell(); + + if( pESh ) + pESh->StartAllAction(); + else + pSh->StartAction(); + + SwSectionData aSectionData(*m_rSectFormat.GetSection()); + aSectionData.SetType( SectionType::Content ); + aSectionData.SetLinkFileName( OUString() ); + aSectionData.SetProtectFlag( false ); + // edit in readonly sections + aSectionData.SetEditInReadonlyFlag( false ); + + aSectionData.SetConnectFlag( false ); + + pDoc->UpdateSection( n, aSectionData ); + + // Make all Links within the Section visible again + SwSectionNode* pSectNd = m_rSectFormat.GetSectionNode(); + if( pSectNd ) + SwSection::MakeChildLinksVisible( *pSectNd ); + + if( pESh ) + pESh->EndAllAction(); + else + pSh->EndAction(); + break; + } + } + SvBaseLink::Closed(); +} + +void SwSection::CreateLink( LinkCreateType eCreateType ) +{ + SwSectionFormat* pFormat = GetFormat(); + OSL_ENSURE(pFormat, "SwSection::CreateLink: no format?"); + if (!pFormat || (SectionType::Content == m_Data.GetType())) + return ; + + SfxLinkUpdateMode nUpdateType = SfxLinkUpdateMode::ALWAYS; + + if (!m_RefLink.is()) + { + // create BaseLink + m_RefLink = new SwIntrnlSectRefLink( *pFormat, nUpdateType ); + } + else + { + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + + SwIntrnlSectRefLink *const pLnk = + static_cast<SwIntrnlSectRefLink*>( m_RefLink.get() ); + + const OUString sCmd(m_Data.GetLinkFileName()); + pLnk->SetUpdateMode( nUpdateType ); + pLnk->SetVisible( pFormat->GetDoc()->getIDocumentLinksAdministration().IsVisibleLinks() ); + + switch (m_Data.GetType()) + { + case SectionType::DdeLink: + pLnk->SetLinkSourceName( sCmd ); + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().InsertDDELink( pLnk ); + break; + case SectionType::FileLink: + { + pLnk->SetContentType( SotClipboardFormatId::SIMPLE_FILE ); + sal_Int32 nIndex = 0; + const OUString sFile(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + const OUString sFltr(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + const OUString sRange(sCmd.getToken( 0, sfx2::cTokenSeparator, nIndex )); + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().InsertFileLink( *pLnk, + static_cast<sfx2::SvBaseLinkObjectType>(m_Data.GetType()), + sFile, + ( !sFltr.isEmpty() ? &sFltr : nullptr ), + ( !sRange.isEmpty() ? &sRange : nullptr ) ); + } + break; + default: + OSL_ENSURE( false, "What kind of Link is this?" ); + } + + switch( eCreateType ) + { + case LinkCreateType::Connect: // Connect Link right away + pLnk->Connect(); + break; + + case LinkCreateType::Update: // Connect Link and update + pLnk->Update(); + break; + case LinkCreateType::NONE: break; + } +} + +void SwSection::BreakLink() +{ + const SectionType eCurrentType( GetType() ); + if ( eCurrentType == SectionType::Content || + eCurrentType == SectionType::ToxHeader || + eCurrentType == SectionType::ToxContent ) + { + // nothing to do + return; + } + + // Release link, if it exists + if (m_RefLink.is()) + { + SwSectionFormat *const pFormat( GetFormat() ); + OSL_ENSURE(pFormat, "SwSection::BreakLink: no format?"); + if (pFormat) + { + pFormat->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( m_RefLink.get() ); + } + m_RefLink.clear(); + } + // change type + SetType( SectionType::Content ); + // reset linked file data + SetLinkFileName( OUString() ); + SetLinkFilePassword( OUString() ); +} + +void SwSection::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwSection")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("registered-in"), "%p", + GetRegisteredIn()); + m_Data.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +const SwNode* SwIntrnlSectRefLink::GetAnchor() const { return m_rSectFormat.GetSectionNode(); } + +bool SwIntrnlSectRefLink::IsInRange( SwNodeOffset nSttNd, SwNodeOffset nEndNd ) const +{ + SwStartNode* pSttNd = m_rSectFormat.GetSectionNode(); + return pSttNd && + nSttNd < pSttNd->GetIndex() && + pSttNd->EndOfSectionIndex() < nEndNd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swbaslnk.cxx b/sw/source/core/docnode/swbaslnk.cxx new file mode 100644 index 0000000000..287bc80795 --- /dev/null +++ b/sw/source/core/docnode/swbaslnk.cxx @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/svapp.hxx> + +#include <osl/diagnose.h> +#include <sfx2/lnkbase.hxx> +#include <editeng/boxitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/event.hxx> +#include <sot/exchange.hxx> +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <frmatr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <swevent.hxx> +#include <swbaslnk.hxx> +#include <swserv.hxx> +#include <viewsh.hxx> +#include <ndgrf.hxx> +#include <htmltbl.hxx> +#include <dialoghelp.hxx> +#include <memory> + +using namespace com::sun::star; + +static bool SetGrfFlySize( const Size& rGrfSz, SwGrfNode* pGrfNd, const Size &rOrigGrfSize ); + + +::sfx2::SvBaseLink::UpdateResult SwBaseLink::DataChanged( + const OUString& rMimeType, const uno::Any & rValue ) +{ + if( !m_pContentNode ) + { + OSL_ENSURE(false, "DataChanged without ContentNode" ); + return ERROR_GENERAL; + } + + SwDoc& rDoc = m_pContentNode->GetDoc(); + if( rDoc.IsInDtor() || ChkNoDataFlag() ) + { + return SUCCESS; + } + + SotClipboardFormatId nFormat = SotExchange::GetFormatIdFromMimeType( rMimeType ); + + if( m_pContentNode->IsNoTextNode() && + nFormat == sfx2::LinkManager::RegisterStatusInfoId() ) + { + // Only a status change - serve Events? + OUString sState; + + if( rValue.hasValue() && ( rValue >>= sState )) + { + SvMacroItemId nEvent = SvMacroItemId::NONE; + switch( sState.toInt32() ) + { + case sfx2::LinkManager::STATE_LOAD_OK: nEvent = SvMacroItemId::OnImageLoadDone; break; + case sfx2::LinkManager::STATE_LOAD_ERROR: nEvent = SvMacroItemId::OnImageLoadError; break; + case sfx2::LinkManager::STATE_LOAD_ABORT: nEvent = SvMacroItemId::OnImageLoadCancel; break; + } + + SwFrameFormat* pFormat; + if( nEvent != SvMacroItemId::NONE && nullptr != ( pFormat = m_pContentNode->GetFlyFormat() )) + { + SwCallMouseEvent aCallEvent; + aCallEvent.Set( EVENT_OBJECT_IMAGE, pFormat ); + rDoc.CallEvent( nEvent, aCallEvent ); + } + } + return SUCCESS; // That's it! + } + + bool bUpdate = false; + bool bFrameInPaint = false; + Size aGrfSz, aOldSz; + + SwGrfNode* pSwGrfNode = nullptr; + + if (m_pContentNode->IsGrfNode()) + { + pSwGrfNode = m_pContentNode->GetGrfNode(); + assert(pSwGrfNode && "Error, pSwGrfNode expected when node answers IsGrfNode() with true (!)"); + aOldSz = pSwGrfNode->GetTwipSize(); + const GraphicObject& rGrfObj = pSwGrfNode->GetGrfObj(); + + bFrameInPaint = pSwGrfNode->IsFrameInPaint(); + + Graphic aGrf; + + // tdf#124698 if any auth dialog is needed, find what the parent window should be + weld::Window* pDlgParent = GetFrameWeld(&rDoc); + + if (rDoc.getIDocumentLinksAdministration().GetLinkManager().GetGraphicFromAny(rMimeType, rValue, aGrf, pDlgParent) && + ( GraphicType::Default != aGrf.GetType() || + GraphicType::Default != rGrfObj.GetType() ) ) + { + aGrfSz = ::GetGraphicSizeTwip( aGrf, nullptr ); + + pSwGrfNode->SetGraphic(aGrf); + bUpdate = true; + + // Always use the correct graphic size + if( aGrfSz.Height() && aGrfSz.Width() && + aOldSz.Height() && aOldSz.Width() && + aGrfSz != aOldSz ) + { + pSwGrfNode->SetTwipSize(aGrfSz); + aOldSz = aGrfSz; + } + } + } + else if( m_pContentNode->IsOLENode() ) + bUpdate = true; + + if ( !bUpdate || bFrameInPaint ) + return SUCCESS; + + if(pSwGrfNode && !SetGrfFlySize(aGrfSz, pSwGrfNode, aOldSz)) + pSwGrfNode->TriggerGraphicArrived(); + + return SUCCESS; +} + +static bool SetGrfFlySize( const Size& rGrfSz, SwGrfNode* pGrfNd, const Size& rOrigGrfSize ) +{ + bool bRet = false; + SwViewShell *pSh = pGrfNd->GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + std::unique_ptr<CurrShell> pCurr; + if ( pGrfNd->GetDoc().GetEditShell() ) + pCurr.reset(new CurrShell( pSh )); + + Size aSz = rOrigGrfSize; + if ( !(aSz.Width() && aSz.Height()) && + rGrfSz.Width() && rGrfSz.Height() ) + { + SwFrameFormat* pFormat = nullptr; + if (pGrfNd->IsChgTwipSize()) + pFormat = pGrfNd->GetFlyFormat(); + if (nullptr != pFormat) + { + Size aCalcSz( aSz ); + if ( !aSz.Height() && aSz.Width() ) + // Calculate the right height + aCalcSz.setHeight( rGrfSz.Height() * + aSz.Width() / rGrfSz.Width() ); + else if ( !aSz.Width() && aSz.Height() ) + // Calculate the right width + aCalcSz.setWidth( rGrfSz.Width() * + aSz.Height() / rGrfSz.Height() ); + else + // Take over height and width + aCalcSz = rGrfSz; + + const SvxBoxItem &rBox = pFormat->GetBox(); + aCalcSz.AdjustWidth(rBox.CalcLineSpace(SvxBoxItemLine::LEFT) + + rBox.CalcLineSpace(SvxBoxItemLine::RIGHT) ); + aCalcSz.AdjustHeight(rBox.CalcLineSpace(SvxBoxItemLine::TOP) + + rBox.CalcLineSpace(SvxBoxItemLine::BOTTOM) ); + const SwFormatFrameSize& rOldAttr = pFormat->GetFrameSize(); + if( rOldAttr.GetSize() != aCalcSz ) + { + SwFormatFrameSize aAttr( rOldAttr ); + aAttr.SetSize( aCalcSz ); + pFormat->SetFormatAttr( aAttr ); + bRet = true; + } + + if( !aSz.Width() ) + { + // If the graphic is anchored in a table, we need to recalculate + // the table rows + const SwDoc& rDoc = pGrfNd->GetDoc(); + SwNode* pAnchorNode = pFormat->GetAnchor().GetAnchorNode(); + SwTableNode *pTableNd; + if (pAnchorNode && nullptr != (pTableNd = pAnchorNode->FindTableNode())) + { + const bool bLastGrf = !pTableNd->GetTable().DecGrfsThatResize(); + SwHTMLTableLayout *pLayout = + pTableNd->GetTable().GetHTMLTableLayout(); + if( pLayout ) + { + const sal_uInt16 nBrowseWidth = + pLayout->GetBrowseWidthByTable( rDoc ); + if ( nBrowseWidth ) + { + pLayout->Resize( nBrowseWidth, true, true, + bLastGrf ? HTMLTABLE_RESIZE_NOW + : 500 ); + } + } + } + } + } + + // SetTwipSize rescales an ImageMap if needed for which + // it requires the Frame Format + pGrfNd->SetTwipSize( rGrfSz ); + } + + return bRet; +} + +bool SwBaseLink::SwapIn( bool bWaitForData, bool bNativFormat ) +{ + if( !GetObj() && ( bNativFormat || ( !IsSynchron() && bWaitForData ) )) + { + AddNextRef(); + GetRealObject_(); + ReleaseRef(); + } + + bool bRes = false; + + if( GetObj() ) + { + OUString aMimeType( SotExchange::GetFormatMimeType( GetContentType() )); + uno::Any aValue; + (void)GetObj()->GetData( aValue, aMimeType, !IsSynchron() && bWaitForData ); + + if( bWaitForData && !GetObj() ) + { + OSL_ENSURE( false, "The SvxFileObject was deleted in a GetData!" ); + } + else + { + bRes = aValue.hasValue(); + if ( bRes ) + { + DataChanged( aMimeType, aValue ); + } + } + } + else if( !IsSynchron() && bWaitForData ) + { + SetSynchron( true ); + bRes = Update(); + SetSynchron( false ); + } + else + bRes = Update(); + + return bRes; +} + +void SwBaseLink::Closed() +{ + if( m_pContentNode && !m_pContentNode->GetDoc().IsInDtor() ) + { + // Delete the connection + if( m_pContentNode->IsGrfNode() ) + static_cast<SwGrfNode*>(m_pContentNode)->ReleaseLink(); + } + SvBaseLink::Closed(); +} + +const SwNode* SwBaseLink::GetAnchor() const +{ + if (m_pContentNode) + { + SwFrameFormat *const pFormat = m_pContentNode->GetFlyFormat(); + if (pFormat) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + SwNode const*const pAnchorNode = rAnchor.GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()))) + { + return pAnchorNode; + } + return nullptr; + } + } + + OSL_ENSURE( false, "GetAnchor is not shadowed" ); + return nullptr; +} + +bool SwBaseLink::IsRecursion( const SwBaseLink* pChkLnk ) const +{ + tools::SvRef<SwServerObject> aRef( static_cast<SwServerObject*>(GetObj()) ); + if( aRef.is() ) + { + // As it's a ServerObject, we query all contained Links + // if we are contained in them. Else we have a recursion. + return aRef->IsLinkInServer( pChkLnk ); + } + return false; +} + +bool SwBaseLink::IsInRange( SwNodeOffset, SwNodeOffset ) const +{ + // Not Graphic or OLE Links + // Fields or Sections have their own derivation! + return false; +} + +SwBaseLink::~SwBaseLink() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swthreadjoiner.cxx b/sw/source/core/docnode/swthreadjoiner.cxx new file mode 100644 index 0000000000..6ff198d7dd --- /dev/null +++ b/sw/source/core/docnode/swthreadjoiner.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <swthreadjoiner.hxx> +#include <com/sun/star/util/JobManager.hpp> +#include <comphelper/processfactory.hxx> +#include <mutex> + +// Testing + +using namespace ::com::sun::star; + +namespace +{ +uno::Reference<util::XJobManager> pThreadJoiner; +} + +uno::Reference<util::XJobManager>& SwThreadJoiner::GetThreadJoiner() +{ + static std::mutex theJoinerMutex; + std::unique_lock aGuard(theJoinerMutex); + + if (!pThreadJoiner.is()) + { + pThreadJoiner = util::JobManager::create(comphelper::getProcessComponentContext()); + } + + return pThreadJoiner; +} + +void SwThreadJoiner::ReleaseThreadJoiner() { pThreadJoiner.clear(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/swthreadmanager.cxx b/sw/source/core/docnode/swthreadmanager.cxx new file mode 100644 index 0000000000..3c81ff5701 --- /dev/null +++ b/sw/source/core/docnode/swthreadmanager.cxx @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <swthreadmanager.hxx> +#include <swthreadjoiner.hxx> +#include <observablethread.hxx> +#include "threadmanager.hxx" + +/** class to manage threads in Writer - it conforms the singleton pattern + + #i73788# +*/ +bool SwThreadManager::sbThreadManagerInstantiated = false; + +SwThreadManager::SwThreadManager() + : mpThreadManagerImpl( new ThreadManager( SwThreadJoiner::GetThreadJoiner() ) ) +{ + mpThreadManagerImpl->Init(); + sbThreadManagerInstantiated = true; +} + +SwThreadManager::~SwThreadManager() +{ +} + +SwThreadManager& SwThreadManager::GetThreadManager() +{ + static SwThreadManager gThreadManager; + return gThreadManager; +} + +bool SwThreadManager::ExistsThreadManager() +{ + return sbThreadManagerInstantiated; +} + +oslInterlockedCount SwThreadManager::AddThread( const rtl::Reference< ObservableThread >& rThread ) +{ + return mpThreadManagerImpl->AddThread( rThread ); +} + +void SwThreadManager::RemoveThread( const oslInterlockedCount nThreadID ) +{ + mpThreadManagerImpl->RemoveThread( nThreadID ); +} + +void SwThreadManager::SuspendStartingOfThreads() +{ + mpThreadManagerImpl->SuspendStartingOfThreads(); +} + +void SwThreadManager::ResumeStartingOfThreads() +{ + mpThreadManagerImpl->ResumeStartingOfThreads(); +} + +bool SwThreadManager::StartingOfThreadsSuspended() +{ + return mpThreadManagerImpl->StartingOfThreadsSuspended(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadlistener.cxx b/sw/source/core/docnode/threadlistener.cxx new file mode 100644 index 0000000000..cba118e46f --- /dev/null +++ b/sw/source/core/docnode/threadlistener.cxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include <threadlistener.hxx> +#include "threadmanager.hxx" + +/** helper class to observe threads + + #i73788# +*/ +ThreadListener::ThreadListener( ThreadManager& rThreadListenerOwner ) + : mrThreadListenerOwner( rThreadListenerOwner ) +{ +} + +ThreadListener::~ThreadListener() +{ +} + +void ThreadListener::ListenToThread( const oslInterlockedCount nThreadID, + ObservableThread& rThread ) +{ + rThread.SetListener( mrThreadListenerOwner.GetThreadListenerWeakRef(), + nThreadID ); +} + +void ThreadListener::NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) +{ + mrThreadListenerOwner.NotifyAboutFinishedThread( nThreadID ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadmanager.cxx b/sw/source/core/docnode/threadmanager.cxx new file mode 100644 index 0000000000..20d71e746a --- /dev/null +++ b/sw/source/core/docnode/threadmanager.cxx @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "cancellablejob.hxx" +#include "threadmanager.hxx" +#include <threadlistener.hxx> + +#include <osl/diagnose.h> + +#include <algorithm> + +#include <com/sun/star/util/XJobManager.hpp> + +using namespace ::com::sun::star; + +/** class to manage threads + + #i73788# +*/ +const std::deque< ThreadManager::tThreadData >::size_type ThreadManager::snStartedSize = 10; + +ThreadManager::ThreadManager( uno::Reference< util::XJobManager > const & rThreadJoiner ) + : mrThreadJoiner( rThreadJoiner ), + mnThreadIDCounter( 0 ), + maStartNewThreadIdle("SW ThreadManager StartNewThreadIdle"), + mbStartingOfThreadsSuspended( false ) +{ +} + +void ThreadManager::Init() +{ + mpThreadListener = std::make_shared<ThreadListener>( *this ); + + maStartNewThreadIdle.SetPriority( TaskPriority::LOWEST ); + maStartNewThreadIdle.SetInvokeHandler( LINK( this, ThreadManager, TryToStartNewThread ) ); +} + +ThreadManager::~ThreadManager() +{ + maWaitingForStartThreads.clear(); + maStartedThreads.clear(); +} + +std::weak_ptr< IFinishedThreadListener > ThreadManager::GetThreadListenerWeakRef() const +{ + return mpThreadListener; +} + +void ThreadManager::NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ) +{ + RemoveThread( nThreadID, true ); +} + +oslInterlockedCount ThreadManager::AddThread( + const rtl::Reference< ObservableThread >& rThread ) + +{ + std::unique_lock aGuard(maMutex); + + // create new thread + tThreadData aThreadData; + oslInterlockedCount nNewThreadID( osl_atomic_increment( &mnThreadIDCounter ) ); + { + aThreadData.nThreadID = nNewThreadID; + + aThreadData.pThread = rThread; + aThreadData.aJob = new CancellableJob( aThreadData.pThread ); + + aThreadData.pThread->setPriority( osl_Thread_PriorityBelowNormal ); + mpThreadListener->ListenToThread( aThreadData.nThreadID, + *(aThreadData.pThread) ); + } + + // add thread to manager + if ( maStartedThreads.size() < snStartedSize && + !mbStartingOfThreadsSuspended ) + { + // Try to start thread + if ( !StartThread( aThreadData ) ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + } + } + } + else + { + // Thread will be started later + maWaitingForStartThreads.push_back( aThreadData ); + } + + return nNewThreadID; +} + +void ThreadManager::RemoveThread( const oslInterlockedCount nThreadID, + const bool bThreadFinished ) +{ + // --> SAFE ---- + std::unique_lock aGuard(maMutex); + + std::deque< tThreadData >::iterator aIter = + std::find_if( maStartedThreads.begin(), maStartedThreads.end(), + ThreadPred( nThreadID ) ); + + if ( aIter != maStartedThreads.end() ) + { + tThreadData aTmpThreadData( *aIter ); + + maStartedThreads.erase( aIter ); + + if ( bThreadFinished ) + { + // release thread as job from thread joiner instance + css::uno::Reference< css::util::XJobManager > rThreadJoiner( mrThreadJoiner ); + if ( rThreadJoiner.is() ) + { + rThreadJoiner->releaseJob( aTmpThreadData.aJob ); + } + else + { + OSL_FAIL( "<ThreadManager::RemoveThread(..)> - ThreadJoiner already gone!" ); + } + } + + // Try to start thread from waiting ones + aGuard.unlock(); + TryToStartNewThread( nullptr ); + } + else + { + aIter = std::find_if( maWaitingForStartThreads.begin(), + maWaitingForStartThreads.end(), ThreadPred( nThreadID ) ); + + if ( aIter != maWaitingForStartThreads.end() ) + { + maWaitingForStartThreads.erase( aIter ); + } + } + // <-- SAFE ---- +} + +bool ThreadManager::StartWaitingThread() +{ + if ( !maWaitingForStartThreads.empty() ) + { + tThreadData aThreadData( maWaitingForStartThreads.front() ); + maWaitingForStartThreads.pop_front(); + return StartThread( aThreadData ); + } + else + { + return false; + } +} + +bool ThreadManager::StartThread( const tThreadData& rThreadData ) +{ + bool bThreadStarted( false ); + + if ( rThreadData.pThread->create() ) + { + // start of thread successful. + bThreadStarted = true; + + maStartedThreads.push_back( rThreadData ); + + // register thread as job at thread joiner instance + css::uno::Reference< css::util::XJobManager > rThreadJoiner( mrThreadJoiner ); + if ( rThreadJoiner.is() ) + { + rThreadJoiner->registerJob( rThreadData.aJob ); + } + else + { + OSL_FAIL( "<ThreadManager::StartThread(..)> - ThreadJoiner already gone!" ); + } + } + else + { + // thread couldn't be started. + maWaitingForStartThreads.push_front( rThreadData ); + } + + return bThreadStarted; +} + +IMPL_LINK_NOARG(ThreadManager, TryToStartNewThread, Timer *, void) +{ + std::unique_lock aGuard(maMutex); + + if ( mbStartingOfThreadsSuspended ) + return; + + // Try to start thread from waiting ones + if ( !StartWaitingThread() ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + } + } +} + +void ThreadManager::ResumeStartingOfThreads() +{ + std::unique_lock aGuard(maMutex); + + mbStartingOfThreadsSuspended = false; + + while ( maStartedThreads.size() < snStartedSize && + !maWaitingForStartThreads.empty() ) + { + if ( !StartWaitingThread() ) + { + // No success on starting thread + // If no more started threads exist, but still threads are waiting, + // setup Timer to start thread from waiting ones + if ( maStartedThreads.empty() && !maWaitingForStartThreads.empty() ) + { + maStartNewThreadIdle.Start(); + break; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/docnode/threadmanager.hxx b/sw/source/core/docnode/threadmanager.hxx new file mode 100644 index 0000000000..ace0bc0316 --- /dev/null +++ b/sw/source/core/docnode/threadmanager.hxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <vcl/idle.hxx> +#include <mutex> +#include <osl/interlck.h> +#include <rtl/ref.hxx> + +#include <deque> +#include <cppuhelper/weakref.hxx> +#include <observablethread.hxx> + +#include <memory> + +namespace com::sun::star::util { class XCancellable; } +namespace com::sun::star::util { class XJobManager; } + +class IFinishedThreadListener; +class ThreadListener; +class Timer; + +/** class to manage threads + + OD 2007-01-29 #i73788# + An instance of this class takes care of the starting of threads. + It assures that not more than <mnStartedSize> threads + are started. +*/ +class ThreadManager final +{ + public: + + explicit ThreadManager( css::uno::Reference< css::util::XJobManager > const & rThreadJoiner ); + ~ThreadManager(); + + std::weak_ptr< IFinishedThreadListener > GetThreadListenerWeakRef() const; + void NotifyAboutFinishedThread( const oslInterlockedCount nThreadID ); + + /** initialization + + IMPORTANT NOTE: Needs to be called directly after construction + */ + void Init(); + + /** add thread to the thread manager and taking ownership for the thread + + @return unique ID for added thread + */ + oslInterlockedCount AddThread( + const ::rtl::Reference< ObservableThread >& rThread ); + + void RemoveThread( const oslInterlockedCount nThreadID, + const bool bThreadFinished = false ); + + DECL_LINK( TryToStartNewThread, Timer*, void ); + + /** suspend the starting of threads + + Suspending the starting of further threads is sensible during the + destruction of a Writer document. + */ + void SuspendStartingOfThreads() + { + std::unique_lock aGuard(maMutex); + + mbStartingOfThreadsSuspended = true; + } + + /** continues the starting of threads after it has been suspended + */ + void ResumeStartingOfThreads(); + + bool StartingOfThreadsSuspended() + { + std::unique_lock aGuard(maMutex); + + return mbStartingOfThreadsSuspended; + } + + struct tThreadData + { + oslInterlockedCount nThreadID; + ::rtl::Reference< ObservableThread > pThread; + css::uno::Reference< css::util::XCancellable > aJob; + + tThreadData() + : nThreadID( 0 ), + aJob() + {} + }; + + private: + + static const std::deque< tThreadData >::size_type snStartedSize; + + std::mutex maMutex; + + css::uno::WeakReference< css::util::XJobManager > mrThreadJoiner; + + std::shared_ptr< ThreadListener > mpThreadListener; + + oslInterlockedCount mnThreadIDCounter; + + std::deque< tThreadData > maWaitingForStartThreads; + std::deque< tThreadData > maStartedThreads; + + Idle maStartNewThreadIdle; + + bool mbStartingOfThreadsSuspended; + + struct ThreadPred + { + oslInterlockedCount mnThreadID; + explicit ThreadPred( oslInterlockedCount nThreadID ) + : mnThreadID( nThreadID ) + {} + + bool operator() ( const tThreadData& rThreadData ) const + { + return rThreadData.nThreadID == mnThreadID; + } + }; + + bool StartWaitingThread(); + + bool StartThread( const tThreadData& aThreadData ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |