diff options
Diffstat (limited to 'sw/source/core/doc')
66 files changed, 60614 insertions, 0 deletions
diff --git a/sw/source/core/doc/CntntIdxStore.cxx b/sw/source/core/doc/CntntIdxStore.cxx new file mode 100644 index 000000000..1fe119f2e --- /dev/null +++ b/sw/source/core/doc/CntntIdxStore.cxx @@ -0,0 +1,477 @@ +/* -*- 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 <bookmrk.hxx> +#include <cntfrm.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <editsh.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <functional> +#include <mvsave.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <redline.hxx> +#include <sal/types.h> +#include <unocrsr.hxx> +#include <txtfrm.hxx> +#include <frameformats.hxx> +#include <memory> + +using namespace ::boost; +using namespace ::sw::mark; + +namespace +{ + // #i59534: If a paragraph will be split we have to restore some redline positions + // This help function checks a position compared with a node and a content index + + static const int BEFORE_NODE = 0; // Position before the given node index + static const int BEFORE_SAME_NODE = 1; // Same node index but content index before given content index + static const int SAME_POSITION = 2; // Same node index and samecontent index + static const int BEHIND_SAME_NODE = 3; // Same node index but content index behind given content index + static const int BEHIND_NODE = 4; // Position behind the given node index + + int lcl_RelativePosition( const SwPosition& rPos, sal_uLong nNode, sal_Int32 nContent ) + { + sal_uLong nIndex = rPos.nNode.GetIndex(); + int nReturn = BEFORE_NODE; + if( nIndex == nNode ) + { + const sal_Int32 nCntIdx = rPos.nContent.GetIndex(); + if( nCntIdx < nContent ) + nReturn = BEFORE_SAME_NODE; + else if( nCntIdx == nContent ) + nReturn = SAME_POSITION; + else + nReturn = BEHIND_SAME_NODE; + } + else if( nIndex > nNode ) + nReturn = BEHIND_NODE; + return nReturn; + } + struct MarkEntry + { + long int m_nIdx; + bool m_bOther; + sal_Int32 m_nContent; +#if 0 +#include <sal/log.hxx> + void Dump() + { + SAL_INFO("sw.core", "Index: " << m_nIdx << "\tOther: " << m_bOther << "\tContent: " << m_nContent); + } +#endif + }; + struct PaMEntry + { + SwPaM* m_pPaM; + bool m_isMark; + sal_Int32 m_nContent; + }; + struct OffsetUpdater + { + const SwContentNode* m_pNewContentNode; + const sal_Int32 m_nOffset; + OffsetUpdater(SwContentNode const * pNewContentNode, sal_Int32 nOffset) + : m_pNewContentNode(pNewContentNode), m_nOffset(nOffset) {}; + void operator()(SwPosition& rPos, sal_Int32 nContent) const + { + rPos.nNode = *m_pNewContentNode; + rPos.nContent.Assign(const_cast<SwContentNode*>(m_pNewContentNode), nContent + m_nOffset); + }; + }; + struct LimitUpdater + { + const SwContentNode* m_pNewContentNode; + const sal_uLong m_nLen; + const sal_Int32 m_nCorrLen; + LimitUpdater(SwContentNode const * pNewContentNode, sal_uLong nLen, sal_Int32 nCorrLen) + : m_pNewContentNode(pNewContentNode), m_nLen(nLen), m_nCorrLen(nCorrLen) {}; + void operator()(SwPosition& rPos, sal_Int32 nContent) const + { + rPos.nNode = *m_pNewContentNode; + if( nContent < m_nCorrLen ) + { + rPos.nContent.Assign(const_cast<SwContentNode*>(m_pNewContentNode), std::min( nContent, static_cast<sal_Int32>(m_nLen) ) ); + } + else + { + rPos.nContent -= m_nCorrLen; + } + }; + }; + struct ContentIdxStoreImpl : sw::mark::ContentIdxStore + { + std::vector<MarkEntry> m_aBkmkEntries; + std::vector<MarkEntry> m_aRedlineEntries; + std::vector<MarkEntry> m_aFlyEntries; + std::vector<PaMEntry> m_aUnoCursorEntries; + std::vector<PaMEntry> m_aShellCursorEntries; + typedef std::function<void (SwPosition& rPos, sal_Int32 nContent)> updater_t; + virtual void Clear() override + { + m_aBkmkEntries.clear(); + m_aRedlineEntries.clear(); + m_aFlyEntries.clear(); + m_aUnoCursorEntries.clear(); + m_aShellCursorEntries.clear(); + } + virtual bool Empty() override + { + return m_aBkmkEntries.empty() && m_aRedlineEntries.empty() && m_aFlyEntries.empty() && m_aUnoCursorEntries.empty() && m_aShellCursorEntries.empty(); + } + virtual void Save(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit=false) override + { + SaveBkmks(pDoc, nNode, nContent); + SaveRedlines(pDoc, nNode, nContent); + SaveFlys(pDoc, nNode, nContent, bSaveFlySplit); + SaveUnoCursors(pDoc, nNode, nContent); + SaveShellCursors(pDoc, nNode, nContent); + } + virtual void Restore(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nOffset=0, bool bAuto = false, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = pDoc->GetNodes()[ nNode ]->GetContentNode(); + updater_t aUpdater = OffsetUpdater(pCNd, nOffset); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(pDoc, aUpdater); + RestoreRedlines(pDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(pDoc, aUpdater, bAuto); + } + } + virtual void Restore(SwNode& rNd, sal_Int32 nLen, sal_Int32 nCorrLen, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = rNd.GetContentNode(); + SwDoc* pDoc = rNd.GetDoc(); + updater_t aUpdater = LimitUpdater(pCNd, nLen, nCorrLen); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(pDoc, aUpdater); + RestoreRedlines(pDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(pDoc, aUpdater, false); + } + } + + private: + void SaveBkmks(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreBkmks(SwDoc* pDoc, updater_t const & rUpdater); + void SaveRedlines(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreRedlines(SwDoc* pDoc, updater_t const & rUpdater); + void SaveFlys(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit); + void RestoreFlys(SwDoc* pDoc, updater_t const & rUpdater, bool bAuto); + void SaveUnoCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreUnoCursors(updater_t const & rUpdater); + void SaveShellCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent); + void RestoreShellCursors(updater_t const & rUpdater); + static const SwPosition& GetRightMarkPos(::sw::mark::IMark const * pMark, bool bOther) + { return bOther ? pMark->GetOtherMarkPos() : pMark->GetMarkPos(); }; + static void SetRightMarkPos(MarkBase* pMark, bool bOther, const SwPosition* const pPos) + { bOther ? pMark->SetOtherMarkPos(*pPos) : pMark->SetMarkPos(*pPos); }; + }; + void lcl_ChkPaM( std::vector<PaMEntry>& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM, const bool bGetPoint, bool bSetMark) + { + const SwPosition* pPos = &rPaM.GetBound(bGetPoint); + if( pPos->nNode.GetIndex() == nNode && pPos->nContent.GetIndex() < nContent ) + { + const PaMEntry aEntry = { &rPaM, bSetMark, pPos->nContent.GetIndex() }; + rPaMEntries.push_back(aEntry); + } + } + void lcl_ChkPaMBoth( std::vector<PaMEntry>& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM) + { + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, true, true); + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, false, false); + } + void lcl_ChkUnoCrsrPaMBoth(std::vector<PaMEntry>& rPaMEntries, const sal_uLong nNode, const sal_Int32 nContent, SwPaM& rPaM) + { + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, true, false); + lcl_ChkPaM(rPaMEntries, nNode, nContent, rPaM, false, true); + } + +#if 0 + static void DumpEntries(std::vector<MarkEntry>* pEntries) + { + for (MarkEntry& aEntry : *pEntries) + aEntry.Dump(); + } +#endif +} + +void ContentIdxStoreImpl::SaveBkmks(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + const IDocumentMarkAccess::const_iterator_t ppBkmkEnd = pMarkAccess->getAllMarksEnd(); + for( + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->getAllMarksBegin(); + ppBkmk != ppBkmkEnd; + ++ppBkmk) + { + const ::sw::mark::IMark* pBkmk = *ppBkmk; + bool bMarkPosEqual = false; + if(pBkmk->GetMarkPos().nNode.GetIndex() == nNode + && pBkmk->GetMarkPos().nContent.GetIndex() <= nContent) + { + if(pBkmk->GetMarkPos().nContent.GetIndex() < nContent) + { + const MarkEntry aEntry = { static_cast<long>(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + else // if a bookmark position is equal nContent, the other position + bMarkPosEqual = true; // has to decide if it is added to the array + } + if(pBkmk->IsExpanded() + && pBkmk->GetOtherMarkPos().nNode.GetIndex() == nNode + && pBkmk->GetOtherMarkPos().nContent.GetIndex() <= nContent) + { + if(bMarkPosEqual) + { // the other position is before, the (main) position is equal + const MarkEntry aEntry = { static_cast<long>(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + const MarkEntry aEntry = { static_cast<long>(ppBkmk - pMarkAccess->getAllMarksBegin()), true, pBkmk->GetOtherMarkPos().nContent.GetIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + } +} + +void ContentIdxStoreImpl::RestoreBkmks(SwDoc* pDoc, updater_t const & rUpdater) +{ + IDocumentMarkAccess* const pMarkAccess = pDoc->getIDocumentMarkAccess(); + for (const MarkEntry& aEntry : m_aBkmkEntries) + { + if (MarkBase *const pMark = pMarkAccess->getAllMarksBegin().get()[aEntry.m_nIdx]) + { + SwPosition aNewPos(GetRightMarkPos(pMark, aEntry.m_bOther)); + rUpdater(aNewPos, aEntry.m_nContent); + SetRightMarkPos(pMark, aEntry.m_bOther, &aNewPos); + } + } + if (!m_aBkmkEntries.empty()) + { // tdf#105705 sort bookmarks because SaveBkmks special handling of + // "bMarkPosEqual" may destroy sort order + pMarkAccess->assureSortedMarkContainers(); + } +} + +void ContentIdxStoreImpl::SaveRedlines(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + SwRedlineTable const & rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + long int nIdx = 0; + for (const SwRangeRedline* pRdl : rRedlineTable) + { + int nPointPos = lcl_RelativePosition( *pRdl->GetPoint(), nNode, nContent ); + int nMarkPos = pRdl->HasMark() ? lcl_RelativePosition( *pRdl->GetMark(), nNode, nContent ) : + nPointPos; + // #i59534: We have to store the positions inside the same node before the insert position + // and the one at the insert position if the corresponding Point/Mark position is before + // the insert position. + if( nPointPos == BEFORE_SAME_NODE || + ( nPointPos == SAME_POSITION && nMarkPos < SAME_POSITION ) ) + { + const MarkEntry aEntry = { nIdx, false, pRdl->GetPoint()->nContent.GetIndex() }; + m_aRedlineEntries.push_back(aEntry); + } + if( pRdl->HasMark() && ( nMarkPos == BEFORE_SAME_NODE || + ( nMarkPos == SAME_POSITION && nPointPos < SAME_POSITION ) ) ) + { + const MarkEntry aEntry = { nIdx, true, pRdl->GetMark()->nContent.GetIndex() }; + m_aRedlineEntries.push_back(aEntry); + } + ++nIdx; + } +} + +void ContentIdxStoreImpl::RestoreRedlines(SwDoc* pDoc, updater_t const & rUpdater) +{ + const SwRedlineTable& rRedlTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for (const MarkEntry& aEntry : m_aRedlineEntries) + { + SwPosition* const pPos = aEntry.m_bOther + ? rRedlTable[ aEntry.m_nIdx ]->GetMark() + : rRedlTable[ aEntry.m_nIdx ]->GetPoint(); + rUpdater(*pPos, aEntry.m_nContent); + } +} + +void ContentIdxStoreImpl::SaveFlys(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent, bool bSaveFlySplit) +{ + SwContentNode *pNode = pDoc->GetNodes()[nNode]->GetContentNode(); + if( !pNode ) + return; + SwFrame* pFrame = pNode->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pFrame ) + { + // sw_redlinehide: this looks like an invalid optimisation if merged, + // assuming that flys in deleted redlines should be saved here too. + if ((!pFrame->IsTextFrame() || !static_cast<SwTextFrame const*>(pFrame)->GetMergedPara()) + && !pFrame->GetDrawObjs()) + return; // if we have a layout and no DrawObjs, we can skip this + } + MarkEntry aSave = { 0, false, 0 }; + for (const SwFrameFormat* pFrameFormat : *pDoc->GetSpzFrameFormats()) + { + if ( RES_FLYFRMFMT == pFrameFormat->Which() || RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if ( pAPos && ( nNode == pAPos->nNode.GetIndex() ) && + ( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId() || + RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) ) + { + bool bSkip = false; + aSave.m_bOther = false; + aSave.m_nContent = pAPos->nContent.GetIndex(); + if ( RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) + { + if( nContent <= aSave.m_nContent ) + { + if( bSaveFlySplit ) + aSave.m_bOther = true; + else + bSkip = true; + } + } + if(!bSkip) + m_aFlyEntries.push_back(aSave); + } + } + ++aSave.m_nIdx; + } +} + +void ContentIdxStoreImpl::RestoreFlys(SwDoc* pDoc, updater_t const & rUpdater, bool bAuto) +{ + SwFrameFormats* pSpz = pDoc->GetSpzFrameFormats(); + for (const MarkEntry& aEntry : m_aFlyEntries) + { + if(!aEntry.m_bOther) + { + SwFrameFormat *pFrameFormat = (*pSpz)[ aEntry.m_nIdx ]; + const SwFormatAnchor& rFlyAnchor = pFrameFormat->GetAnchor(); + if( rFlyAnchor.GetContentAnchor() ) + { + SwFormatAnchor aNew( rFlyAnchor ); + SwPosition aNewPos( *rFlyAnchor.GetContentAnchor() ); + rUpdater(aNewPos, aEntry.m_nContent); + if ( RndStdIds::FLY_AT_CHAR != rFlyAnchor.GetAnchorId() ) + { + aNewPos.nContent.Assign( nullptr, 0 ); + } + aNew.SetAnchor( &aNewPos ); + pFrameFormat->SetFormatAttr( aNew ); + } + } + else if( bAuto ) + { + SwFrameFormat *pFrameFormat = (*pSpz)[ aEntry.m_nIdx ]; + SfxPoolItem const *pAnchor = &pFrameFormat->GetAnchor(); + pFrameFormat->NotifyClients( pAnchor, pAnchor ); + } + } +} + +void ContentIdxStoreImpl::SaveUnoCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + pDoc->cleanupUnoCursorTable(); + for (const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + lcl_ChkUnoCrsrPaMBoth(m_aUnoCursorEntries, nNode, nContent, rPaM); + } + const SwUnoTableCursor* pUnoTableCursor = dynamic_cast<const SwUnoTableCursor*>(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : const_cast<SwUnoTableCursor*>(pUnoTableCursor)->GetSelRing().GetRingContainer()) + { + lcl_ChkUnoCrsrPaMBoth(m_aUnoCursorEntries, nNode, nContent, rPaM); + } + } + } +} + +void ContentIdxStoreImpl::RestoreUnoCursors(updater_t const & rUpdater) +{ + for (const PaMEntry& aEntry : m_aUnoCursorEntries) + { + rUpdater(aEntry.m_pPaM->GetBound(!aEntry.m_isMark), aEntry.m_nContent); + } +} + +void ContentIdxStoreImpl::SaveShellCursors(SwDoc* pDoc, sal_uLong nNode, sal_Int32 nContent) +{ + SwCursorShell* pShell = pDoc->GetEditShell(); + if( !pShell ) + return; + for(SwViewShell& rCurShell : pShell->GetRingContainer()) + { + if( dynamic_cast<const SwCursorShell *>(&rCurShell) != nullptr ) + { + SwPaM *_pStackCursor = static_cast<SwCursorShell*>(&rCurShell)->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_ChkPaMBoth( m_aShellCursorEntries, nNode, nContent, *_pStackCursor); + if (!_pStackCursor) + break; + _pStackCursor = _pStackCursor->GetNext(); + if (_pStackCursor == static_cast<SwCursorShell*>(&rCurShell)->GetStackCursor()) + break; + } + + for(SwPaM& rPaM : static_cast<SwCursorShell*>(&rCurShell)->GetCursor_()->GetRingContainer()) + { + lcl_ChkPaMBoth( m_aShellCursorEntries, nNode, nContent, rPaM); + } + } + } +} + +void ContentIdxStoreImpl::RestoreShellCursors(updater_t const & rUpdater) +{ + for (const PaMEntry& aEntry : m_aShellCursorEntries) + { + rUpdater(aEntry.m_pPaM->GetBound(aEntry.m_isMark), aEntry.m_nContent); + } +} + +namespace sw::mark { + std::shared_ptr<ContentIdxStore> ContentIdxStore::Create() + { + return std::make_shared<ContentIdxStoreImpl>(); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentChartDataProviderManager.cxx b/sw/source/core/doc/DocumentChartDataProviderManager.cxx new file mode 100644 index 000000000..90785725e --- /dev/null +++ b/sw/source/core/doc/DocumentChartDataProviderManager.cxx @@ -0,0 +1,106 @@ +/* -*- 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 <DocumentChartDataProviderManager.hxx> + +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <vcl/svapp.hxx> +#include <swtable.hxx> +#include <unochart.hxx> +#include <ndole.hxx> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +namespace sw { + +DocumentChartDataProviderManager::DocumentChartDataProviderManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + maChartDataProviderImplRef() +{ + +} + +SwChartDataProvider * DocumentChartDataProviderManager::GetChartDataProvider( bool bCreate ) const +{ + // since there must be only one instance of this object per document + // we need a mutex here + SolarMutexGuard aGuard; + + if (bCreate && !maChartDataProviderImplRef.is()) + { + maChartDataProviderImplRef = new SwChartDataProvider( & m_rDoc ); + } + return maChartDataProviderImplRef.get(); +} + +void DocumentChartDataProviderManager::CreateChartInternalDataProviders( const SwTable *pTable ) +{ + if (pTable) + { + OUString aName( pTable->GetFrameFormat()->GetName() ); + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *m_rDoc.GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while (nullptr != (pStNd = aIdx.GetNode().GetStartNode())) + { + ++aIdx; + pONd = aIdx.GetNode().GetOLENode(); + if( pONd && + aName == pONd->GetChartTableName() /* OLE node is chart? */ && + nullptr != (pONd->getLayoutFrame( m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout() )) /* chart frame is not hidden */ ) + { + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + uno::Reference< chart2::XChartDocument > xChart( xIP->getComponent(), UNO_QUERY ); + if (xChart.is()) + xChart->createInternalDataProvider( true ); + + // there may be more than one chart for each table thus we need to continue the loop... + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + } +} + +SwChartLockController_Helper & DocumentChartDataProviderManager::GetChartControllerHelper() +{ + if (!mpChartControllerHelper) + { + mpChartControllerHelper.reset(new SwChartLockController_Helper( & m_rDoc )); + } + return *mpChartControllerHelper; +} + +DocumentChartDataProviderManager::~DocumentChartDataProviderManager() +{ + // clean up chart related structures... + // Note: the chart data provider gets already disposed in ~SwDocShell + // since all UNO API related functionality requires an existing SwDocShell + // this assures that dispose gets called if there is need for it. + maChartDataProviderImplRef.clear(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx new file mode 100644 index 000000000..17bbd2b56 --- /dev/null +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -0,0 +1,5138 @@ +/* -*- 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 <DocumentContentOperationsManager.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <UndoManager.hxx> +#include <docary.hxx> +#include <textboxhelper.hxx> +#include <dcontact.hxx> +#include <grfatr.hxx> +#include <numrule.hxx> +#include <charfmt.hxx> +#include <ndgrf.hxx> +#include <ndnotxt.hxx> +#include <ndole.hxx> +#include <breakit.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <fmtinfmt.hxx> +#include <fmtpdsc.hxx> +#include <fmtcnct.hxx> +#include <SwStyleNameMapper.hxx> +#include <redline.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <frmtool.hxx> +#include <unocrsr.hxx> +#include <mvsave.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <paratr.hxx> +#include <txatbase.hxx> +#include <UndoRedline.hxx> +#include <undobj.hxx> +#include <UndoBookmark.hxx> +#include <UndoDelete.hxx> +#include <UndoSplitMove.hxx> +#include <UndoOverwrite.hxx> +#include <UndoInsert.hxx> +#include <UndoAttribute.hxx> +#include <rolbck.hxx> +#include <acorrect.hxx> +#include <bookmrk.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <hints.hxx> +#include <fmtflcnt.hxx> +#include <docedt.hxx> +#include <frameformats.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <sfx2/Metadatable.hxx> +#include <sot/exchange.hxx> +#include <svl/stritem.hxx> +#include <svl/itemiter.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdouno.hxx> +#include <tools/globname.hxx> +#include <editeng/formatbreakitem.hxx> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <tuple> +#include <memory> + + +using namespace ::com::sun::star::i18n; + +namespace +{ + // Copy method from SwDoc + // Prevent copying into Flys that are anchored in the range + bool lcl_ChkFlyFly( SwDoc* pDoc, sal_uLong nSttNd, sal_uLong nEndNd, + sal_uLong nInsNd ) + { + const SwFrameFormats& rFrameFormatTable = *pDoc->GetSpzFrameFormats(); + + for( size_t n = 0; n < rFrameFormatTable.size(); ++n ) + { + SwFrameFormat const*const pFormat = rFrameFormatTable[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) && + nSttNd <= pAPos->nNode.GetIndex() && + pAPos->nNode.GetIndex() < nEndNd ) + { + const SwFormatContent& rContent = pFormat->GetContent(); + SwStartNode* pSNd; + if( !rContent.GetContentIdx() || + nullptr == ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() )) + continue; + + if( pSNd->GetIndex() < nInsNd && + nInsNd < pSNd->EndOfSectionIndex() ) + // Do not copy ! + return true; + + if( lcl_ChkFlyFly( pDoc, pSNd->GetIndex(), + pSNd->EndOfSectionIndex(), nInsNd ) ) + // Do not copy ! + return true; + } + } + + return false; + } + + SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, sal_uLong & rDelCount) + { + SwNodeIndex const& rStart(rSourcePaM.Start()->nNode); + // Special handling for SwDoc::AppendDoc + if (rSourcePaM.GetDoc()->GetNodes().GetEndOfExtras().GetIndex() + 1 + == rStart.GetIndex()) + { + rDelCount = 1; + return SwNodeIndex(rStart, +1); + } + else + { + rDelCount = 0; + return rStart; + } + } + + /* + The CopyBookmarks function has to copy bookmarks from the source to the destination nodes + array. It is called after a call of the CopyNodes(..) function. But this function does not copy + every node (at least at the moment: 2/08/2006 ), section start and end nodes will not be copied + if the corresponding end/start node is outside the copied pam. + The lcl_NonCopyCount function counts the number of these nodes, given the copied pam and a node + index inside the pam. + rPam is the original source pam, rLastIdx is the last calculated position, rDelCount the number + of "non-copy" nodes between rPam.Start() and rLastIdx. + nNewIdx is the new position of interest. + */ + void lcl_NonCopyCount( const SwPaM& rPam, SwNodeIndex& rLastIdx, const sal_uLong nNewIdx, sal_uLong& rDelCount ) + { + sal_uLong nStart = rPam.Start()->nNode.GetIndex(); + sal_uLong nEnd = rPam.End()->nNode.GetIndex(); + if( rLastIdx.GetIndex() < nNewIdx ) // Moving forward? + { + // We never copy the StartOfContent node + do // count "non-copy" nodes + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + ++rDelCount; + } + ++rLastIdx; + } + while( rLastIdx.GetIndex() < nNewIdx ); + } + else if( rDelCount ) // optimization: if there are no "non-copy" nodes until now, + // no move backward needed + { + while( rLastIdx.GetIndex() > nNewIdx ) + { + SwNode& rNode = rLastIdx.GetNode(); + if( ( rNode.IsSectionNode() && rNode.EndOfSectionIndex() >= nEnd ) + || ( rNode.IsEndNode() && rNode.StartOfSectionNode()->GetIndex() < nStart ) ) + { + --rDelCount; + } + rLastIdx--; + } + } + } + + void lcl_SetCpyPos( const SwPosition& rOrigPos, + const SwPosition& rOrigStt, + const SwPosition& rCpyStt, + SwPosition& rChgPos, + sal_uLong nDelCount ) + { + sal_uLong nNdOff = rOrigPos.nNode.GetIndex(); + nNdOff -= rOrigStt.nNode.GetIndex(); + nNdOff -= nDelCount; + sal_Int32 nContentPos = rOrigPos.nContent.GetIndex(); + + // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos> + rChgPos.nNode = nNdOff + rCpyStt.nNode.GetIndex(); + if( !nNdOff ) + { + // just adapt the content index + if( nContentPos > rOrigStt.nContent.GetIndex() ) + nContentPos -= rOrigStt.nContent.GetIndex(); + else + nContentPos = 0; + nContentPos += rCpyStt.nContent.GetIndex(); + } + rChgPos.nContent.Assign( rChgPos.nNode.GetNode().GetContentNode(), nContentPos ); + } + +} + +namespace sw +{ + // TODO: use SaveBookmark (from DelBookmarks) + void CopyBookmarks(const SwPaM& rPam, SwPosition& rCpyPam) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + SwDoc* pDestDoc = rCpyPam.GetDoc(); + const IDocumentMarkAccess* const pSrcMarkAccess = pSrcDoc->getIDocumentMarkAccess(); + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + SwPosition const*const pCpyStt = &rCpyPam; + + std::vector< const ::sw::mark::IMark* > vMarksToCopy; + for ( IDocumentMarkAccess::const_iterator_t ppMark = pSrcMarkAccess->getAllMarksBegin(); + ppMark != pSrcMarkAccess->getAllMarksEnd(); + ++ppMark ) + { + const ::sw::mark::IMark* const pMark = *ppMark; + + const SwPosition& rMarkStart = pMark->GetMarkStart(); + const SwPosition& rMarkEnd = pMark->GetMarkEnd(); + // only include marks that are in the range and not touching both start and end + // - not for annotation or checkbox marks. + const bool bIsNotOnBoundary = + pMark->IsExpanded() + ? (rMarkStart != rStt || rMarkEnd != rEnd) // rMarkStart != rMarkEnd + : (rMarkStart != rStt && rMarkEnd != rEnd); // rMarkStart == rMarkEnd + const IDocumentMarkAccess::MarkType aMarkType = IDocumentMarkAccess::GetType(*pMark); + if ( rMarkStart >= rStt && rMarkEnd <= rEnd + && ( bIsNotOnBoundary + || aMarkType == IDocumentMarkAccess::MarkType::ANNOTATIONMARK + || aMarkType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK + || aMarkType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + vMarksToCopy.push_back(pMark); + } + } + // We have to count the "non-copied" nodes... + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + for(const sw::mark::IMark* const pMark : vMarksToCopy) + { + SwPaM aTmpPam(*pCpyStt); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount); + if(pMark->IsExpanded()) + { + aTmpPam.SetMark(); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().nNode.GetIndex(), nDelCount); + lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount); + } + + ::sw::mark::IMark* const pNewMark = pDestDoc->getIDocumentMarkAccess()->makeMark( + aTmpPam, + pMark->GetName(), + IDocumentMarkAccess::GetType(*pMark), + ::sw::mark::InsertMode::CopyText); + // Explicitly try to get exactly the same name as in the source + // because NavigatorReminders, DdeBookmarks etc. ignore the proposed name + pDestDoc->getIDocumentMarkAccess()->renameMark(pNewMark, pMark->GetName()); + + // copying additional attributes for bookmarks or fieldmarks + ::sw::mark::IBookmark* const pNewBookmark = + dynamic_cast< ::sw::mark::IBookmark* const >(pNewMark); + const ::sw::mark::IBookmark* const pOldBookmark = + dynamic_cast< const ::sw::mark::IBookmark* >(pMark); + if (pNewBookmark && pOldBookmark) + { + pNewBookmark->SetKeyCode(pOldBookmark->GetKeyCode()); + pNewBookmark->SetShortName(pOldBookmark->GetShortName()); + } + ::sw::mark::IFieldmark* const pNewFieldmark = + dynamic_cast< ::sw::mark::IFieldmark* const >(pNewMark); + const ::sw::mark::IFieldmark* const pOldFieldmark = + dynamic_cast< const ::sw::mark::IFieldmark* >(pMark); + if (pNewFieldmark && pOldFieldmark) + { + pNewFieldmark->SetFieldname(pOldFieldmark->GetFieldname()); + pNewFieldmark->SetFieldHelptext(pOldFieldmark->GetFieldHelptext()); + ::sw::mark::IFieldmark::parameter_map_t* pNewParams = pNewFieldmark->GetParameters(); + const ::sw::mark::IFieldmark::parameter_map_t* pOldParams = pOldFieldmark->GetParameters(); + for (const auto& rEntry : *pOldParams ) + { + pNewParams->insert( rEntry ); + } + } + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pMark)); + ::sfx2::Metadatable *const pNewMetadatable( + dynamic_cast< ::sfx2::Metadatable * >(pNewMark)); + if (pMetadatable && pNewMetadatable) + { + pNewMetadatable->RegisterAsCopyOf(*pMetadatable); + } + } + } +} // namespace sw + +namespace +{ + void lcl_DeleteRedlines( const SwPaM& rPam, SwPaM& rCpyPam ) + { + const SwDoc* pSrcDoc = rPam.GetDoc(); + const SwRedlineTable& rTable = pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable(); + if( !rTable.empty() ) + { + SwDoc* pDestDoc = rCpyPam.GetDoc(); + SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End(); + std::unique_ptr<SwPaM> pDelPam; + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + // We have to count the "non-copied" nodes + sal_uLong nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + + SwRedlineTable::size_type n = 0; + pSrcDoc->getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for( ; n < rTable.size(); ++n ) + { + const SwRangeRedline* pRedl = rTable[ n ]; + if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() ) + { + const SwPosition *pRStt = pRedl->Start(), *pREnd = pRedl->End(); + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + switch( eCmpPos ) + { + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + // Pos1 is before Pos2 + break; + + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: + // Pos1 is after Pos2 + n = rTable.size(); + break; + + default: + { + pDelPam.reset(new SwPaM( *pCpyStt, pDelPam.release() )); + if( *pStt < *pRStt ) + { + lcl_NonCopyCount( rPam, aCorrIdx, pRStt->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + pDelPam->SetMark(); + + if( *pEnd < *pREnd ) + *pDelPam->GetPoint() = *pCpyEnd; + else + { + lcl_NonCopyCount( rPam, aCorrIdx, pREnd->nNode.GetIndex(), nDelCount ); + lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + + if (pDelPam->GetNext() && *pDelPam->GetNext()->End() == *pDelPam->Start()) + { + *pDelPam->GetNext()->End() = *pDelPam->End(); + pDelPam.reset(pDelPam->GetNext()); + } + } + } + } + } + + if( pDelPam ) + { + RedlineFlags eOld = pDestDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + + ::sw::UndoGuard const undoGuard(pDestDoc->GetIDocumentUndoRedo()); + + do { + pDestDoc->getIDocumentContentOperations().DeleteAndJoin( *pDelPam->GetNext() ); + if( !pDelPam->IsMultiSelection() ) + break; + delete pDelPam->GetNext(); + } while( true ); + + pDestDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + } + } + + void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg ) + { + SwDoc* pSrcDoc = rRg.aStart.GetNode().GetDoc(); + if( !pSrcDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyTmp( rCpyRg.aStart, rCpyRg.aEnd ); + lcl_DeleteRedlines( aRgTmp, aCpyTmp ); + } + } + + void lcl_ChainFormats( SwFlyFrameFormat *pSrc, SwFlyFrameFormat *pDest ) + { + SwFormatChain aSrc( pSrc->GetChain() ); + if ( !aSrc.GetNext() ) + { + aSrc.SetNext( pDest ); + pSrc->SetFormatAttr( aSrc ); + } + SwFormatChain aDest( pDest->GetChain() ); + if ( !aDest.GetPrev() ) + { + aDest.SetPrev( pSrc ); + pDest->SetFormatAttr( aDest ); + } + } + + // #i86492# + bool lcl_ContainsOnlyParagraphsInList( const SwPaM& rPam ) + { + bool bRet = false; + + const SwTextNode* pTextNd = rPam.Start()->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndTextNd = rPam.End()->nNode.GetNode().GetTextNode(); + if ( pTextNd && pTextNd->IsInList() && + pEndTextNd && pEndTextNd->IsInList() ) + { + bRet = true; + SwNodeIndex aIdx(rPam.Start()->nNode); + + do + { + ++aIdx; + pTextNd = aIdx.GetNode().GetTextNode(); + + if ( !pTextNd || !pTextNd->IsInList() ) + { + bRet = false; + break; + } + } while (pTextNd != pEndTextNd); + } + + return bRet; + } + + bool lcl_MarksWholeNode(const SwPaM & rPam) + { + bool bResult = false; + const SwPosition* pStt = rPam.Start(); + const SwPosition* pEnd = rPam.End(); + + if (nullptr != pStt && nullptr != pEnd) + { + const SwTextNode* pSttNd = pStt->nNode.GetNode().GetTextNode(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + + if (nullptr != pSttNd && nullptr != pEndNd && + pStt->nContent.GetIndex() == 0 && + pEnd->nContent.GetIndex() == pEndNd->Len()) + { + bResult = true; + } + } + + return bResult; + } +} + +//local functions originally from sw/source/core/doc/docedt.cxx +namespace sw +{ + void CalcBreaks(std::vector<std::pair<sal_uLong, sal_Int32>> & rBreaks, + SwPaM const & rPam, bool const isOnlyFieldmarks) + { + sal_uLong const nStartNode(rPam.Start()->nNode.GetIndex()); + sal_uLong const nEndNode(rPam.End()->nNode.GetIndex()); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + IDocumentMarkAccess const& rIDMA(*rPam.GetDoc()->getIDocumentMarkAccess()); + + std::stack<std::tuple<sw::mark::IFieldmark const*, bool, sal_uLong, sal_Int32>> startedFields; + + for (sal_uLong n = nStartNode; n <= nEndNode; ++n) + { + SwNode *const pNode(rNodes[n]); + if (pNode->IsTextNode()) + { + SwTextNode & rTextNode(*pNode->GetTextNode()); + sal_Int32 const nStart(n == nStartNode + ? rPam.Start()->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(n == nEndNode + ? rPam.End()->nContent.GetIndex() + : rTextNode.Len()); + for (sal_Int32 i = nStart; i < nEnd; ++i) + { + const sal_Unicode c(rTextNode.GetText()[i]); + switch (c) + { + // note: CH_TXT_ATR_FORMELEMENT does not need handling + // not sure how CH_TXT_ATR_INPUTFIELDSTART/END are currently handled + case CH_TXTATR_INWORD: + case CH_TXTATR_BREAKWORD: + { + // META hints only have dummy char at the start, not + // at the end, so no need to check in nStartNode + if (n == nEndNode && !isOnlyFieldmarks) + { + SwTextAttr const*const pAttr(rTextNode.GetTextAttrForCharAt(i)); + if (pAttr && pAttr->End() && (nEnd < *pAttr->End())) + { + assert(pAttr->HasDummyChar()); + rBreaks.emplace_back(n, i); + } + } + break; + } + case CH_TXT_ATR_FIELDSTART: + { + auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.emplace(pFieldMark, false, 0, 0); + break; + } + case CH_TXT_ATR_FIELDSEP: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // no way to find the field via MarkManager... + assert(std::get<0>(startedFields.top())->IsCoveringPosition(SwPosition(rTextNode, i))); + std::get<1>(startedFields.top()) = true; + std::get<2>(startedFields.top()) = n; + std::get<3>(startedFields.top()) = i; + } + break; + } + case CH_TXT_ATR_FIELDEND: + { + if (startedFields.empty()) + { + rBreaks.emplace_back(n, i); + } + else + { // fieldmarks must not overlap => stack + assert(std::get<0>(startedFields.top()) == rIDMA.getFieldmarkAt(SwPosition(rTextNode, i))); + startedFields.pop(); + } + break; + } + } + } + } + else if (pNode->IsStartNode()) + { + if (pNode->EndOfSectionIndex() <= nEndNode) + { // fieldmark cannot overlap node section + n = pNode->EndOfSectionIndex(); + } + } + else + { // EndNode can actually happen with sections :( + assert(pNode->IsEndNode() || pNode->IsNoTextNode()); + } + } + while (!startedFields.empty()) + { + SwPosition const& rStart(std::get<0>(startedFields.top())->GetMarkStart()); + std::pair<sal_uLong, sal_Int32> const pos( + rStart.nNode.GetIndex(), rStart.nContent.GetIndex()); + auto it = std::lower_bound(rBreaks.begin(), rBreaks.end(), pos); + assert(it == rBreaks.end() || *it != pos); + rBreaks.insert(it, pos); + if (std::get<1>(startedFields.top())) + { + std::pair<sal_uLong, sal_Int32> const posSep( + std::get<2>(startedFields.top()), + std::get<3>(startedFields.top())); + it = std::lower_bound(rBreaks.begin(), rBreaks.end(), posSep); + assert(it == rBreaks.end() || *it != posSep); + rBreaks.insert(it, posSep); + } + startedFields.pop(); + } + } +} + +namespace +{ + + bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations, SwPaM & rPam, + bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, bool), const bool bForceJoinNext = false) + { + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + sw::CalcBreaks(Breaks, rPam); + + if (Breaks.empty()) + { + return (rDocumentContentOperations.*pFunc)(rPam, bForceJoinNext); + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + } + + return bRet; + } + + bool lcl_StrLenOverflow( const SwPaM& rPam ) + { + // If we try to merge two paragraphs we have to test if afterwards + // the string doesn't exceed the allowed string length + if( rPam.GetPoint()->nNode != rPam.GetMark()->nNode ) + { + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + const SwTextNode* pEndNd = pEnd->nNode.GetNode().GetTextNode(); + if( (nullptr != pEndNd) && pStt->nNode.GetNode().IsTextNode() ) + { + const sal_uInt64 nSum = pStt->nContent.GetIndex() + + pEndNd->GetText().getLength() - pEnd->nContent.GetIndex(); + return nSum > o3tl::make_unsigned(SAL_MAX_INT32); + } + } + return false; + } + + struct SaveRedline + { + SwRangeRedline* pRedl; + sal_uInt32 nStt, nEnd; + sal_Int32 nSttCnt; + sal_Int32 nEndCnt; + + SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rSttIdx.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + SaveRedline( SwRangeRedline* pR, const SwPosition& rPos ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + const SwPosition* pStt = pR->Start(), + * pEnd = pR->GetMark() == pStt ? pR->GetPoint() : pR->GetMark(); + sal_uInt32 nSttIdx = rPos.nNode.GetIndex(); + nStt = pStt->nNode.GetIndex() - nSttIdx; + nSttCnt = pStt->nContent.GetIndex(); + if( nStt == 0 ) + nSttCnt = nSttCnt - rPos.nContent.GetIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->nNode.GetIndex() - nSttIdx; + nEndCnt = pEnd->nContent.GetIndex(); + if( nEnd == 0 ) + nEndCnt = nEndCnt - rPos.nContent.GetIndex(); + } + + pRedl->GetPoint()->nNode = 0; + pRedl->GetPoint()->nContent.Assign( nullptr, 0 ); + pRedl->GetMark()->nNode = 0; + pRedl->GetMark()->nContent.Assign( nullptr, 0 ); + } + + void SetPos( sal_uInt32 nInsPos ) + { + pRedl->GetPoint()->nNode = nInsPos + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = nInsPos + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt ); + } + } + + void SetPos( const SwPosition& aPos ) + { + pRedl->GetPoint()->nNode = aPos.nNode.GetIndex() + nStt; + pRedl->GetPoint()->nContent.Assign( pRedl->GetContentNode(), nSttCnt + ( nStt == 0 ? aPos.nContent.GetIndex() : 0 ) ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->nNode = aPos.nNode.GetIndex() + nEnd; + pRedl->GetMark()->nContent.Assign( pRedl->GetContentNode(false), nEndCnt + ( nEnd == 0 ? aPos.nContent.GetIndex() : 0 ) ); + } + } + }; + + typedef std::vector< SaveRedline > SaveRedlines_t; + + void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr) + { + SwDoc* pDoc = aPam.GetNode().GetDoc(); + + const SwPosition* pStart = aPam.Start(); + const SwPosition* pEnd = aPam.End(); + + // get first relevant redline + SwRedlineTable::size_type nCurrentRedline; + pDoc->getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline ); + if( nCurrentRedline > 0) + nCurrentRedline--; + + // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + // iterate over relevant redlines and decide for each whether it should + // be saved, or split + saved + SwRedlineTable& rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( ; nCurrentRedline < rRedlineTable.size(); nCurrentRedline++ ) + { + SwRangeRedline* pCurrent = rRedlineTable[ nCurrentRedline ]; + SwComparePosition eCompare = + ComparePosition( *pCurrent->Start(), *pCurrent->End(), + *pStart, *pEnd); + + // we must save this redline if it overlaps aPam + // (we may have to split it, too) + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside || + eCompare == SwComparePosition::Inside || + eCompare == SwComparePosition::Equal ) + { + rRedlineTable.Remove( nCurrentRedline-- ); + + // split beginning, if necessary + if( eCompare == SwComparePosition::OverlapBefore || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->End() = *pStart; + *pCurrent->Start() = *pStart; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // split end, if necessary + if( eCompare == SwComparePosition::OverlapBehind || + eCompare == SwComparePosition::Outside ) + { + SwRangeRedline* pNewRedline = new SwRangeRedline( *pCurrent ); + *pNewRedline->Start() = *pEnd; + *pCurrent->End() = *pEnd; + pDoc->getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // save the current redline + rArr.emplace_back( pCurrent, *pStart ); + } + } + + // restore old redline mode + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc* pDoc, const SwPosition& rPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( rPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr) + { + SwDoc* pDoc = rRg.aStart.GetNode().GetDoc(); + SwRedlineTable::size_type nRedlPos; + SwPosition aSrchPos( rRg.aStart ); aSrchPos.nNode--; + aSrchPos.nContent.Assign( aSrchPos.nNode.GetNode().GetContentNode(), 0 ); + if( pDoc->getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos ) + --nRedlPos; + else if( nRedlPos >= pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ) + return ; + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwRedlineTable& rRedlTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + + do { + SwRangeRedline* pTmp = rRedlTable[ nRedlPos ]; + + const SwPosition* pRStt = pTmp->Start(), + * pREnd = pTmp->GetMark() == pRStt + ? pTmp->GetPoint() : pTmp->GetMark(); + + if( pRStt->nNode < rRg.aStart ) + { + if( pREnd->nNode > rRg.aStart && pREnd->nNode < rRg.aEnd ) + { + // Create a copy and set the end of the original to the end of the MoveArea. + // The copy is moved too. + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->Start(); + pTmpPos->nNode = rRg.aStart; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back(pNewRedl, rRg.aStart); + + pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + else if( pREnd->nNode == rRg.aStart ) + { + SwPosition* pTmpPos = pTmp->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + } + } + else if( pRStt->nNode < rRg.aEnd ) + { + rRedlTable.Remove( nRedlPos-- ); + if( pREnd->nNode < rRg.aEnd || + ( pREnd->nNode == rRg.aEnd && !pREnd->nContent.GetIndex()) ) + { + // move everything + rArr.emplace_back( pTmp, rRg.aStart ); + } + else + { + // split + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->End(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + + rArr.emplace_back( pNewRedl, rRg.aStart ); + + pTmpPos = pTmp->Start(); + pTmpPos->nNode = rRg.aEnd; + pTmpPos->nContent.Assign( + pTmpPos->nNode.GetNode().GetContentNode(), 0 ); + pDoc->getIDocumentRedlineAccess().AppendRedline( pTmp, true ); + } + } + else + break; + + } while( ++nRedlPos < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() ); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc *const pDoc, sal_uInt32 const nInsPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( nInsPos ); + pDoc->getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + if (rSvRedLine.pRedl->GetType() == RedlineType::Delete) + { + UpdateFramesForAddDeleteRedline(*pDoc, *rSvRedLine.pRedl); + } + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + bool lcl_SaveFootnote( const SwNodeIndex& rSttNd, const SwNodeIndex& rEndNd, + const SwNodeIndex& rInsPos, + SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr, + const SwIndex* pSttCnt = nullptr, const SwIndex* pEndCnt = nullptr ) + { + bool bUpdateFootnote = false; + const SwNodes& rNds = rInsPos.GetNodes(); + const bool bDelFootnote = rInsPos.GetIndex() < rNds.GetEndOfAutotext().GetIndex() && + rSttNd.GetIndex() >= rNds.GetEndOfAutotext().GetIndex(); + const bool bSaveFootnote = !bDelFootnote && + rInsPos.GetIndex() >= rNds.GetEndOfExtras().GetIndex(); + if( !rFootnoteArr.empty() ) + { + + size_t nPos = 0; + rFootnoteArr.SeekEntry( rSttNd, &nPos ); + SwTextFootnote* pSrch; + const SwNode* pFootnoteNd; + + // Delete/save all that come after it + while( nPos < rFootnoteArr.size() && ( pFootnoteNd = + &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex() + <= rEndNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( ( pEndCnt && pSttCnt ) + ? (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) + : ( &rEndNd.GetNode() == pFootnoteNd )) + { + ++nPos; // continue searching + } + else + { + // delete it + if( bDelFootnote ) + { + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + + while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )-> + GetTextNode())->GetIndex() >= rSttNd.GetIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( !pEndCnt || !pSttCnt || + ! (( &rSttNd.GetNode() == pFootnoteNd && + pSttCnt->GetIndex() > nFootnoteSttIdx ) || + ( &rEndNd.GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEndCnt->GetIndex() )) ) + { + if( bDelFootnote ) + { + // delete it + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwIndex aIdx( &rTextNd, nFootnoteSttIdx ); + rTextNd.EraseText( aIdx, 1 ); + } + else + { + pSrch->DelFrames(nullptr); + rFootnoteArr.erase( rFootnoteArr.begin() + nPos ); + if( bSaveFootnote ) + rSaveArr.insert( pSrch ); + } + bUpdateFootnote = true; + } + } + } + // When moving from redline section into document content section, e.g. + // after loading a document with (delete-)redlines, the footnote array + // has to be adjusted... (#i70572) + if( bSaveFootnote ) + { + SwNodeIndex aIdx( rSttNd ); + while( aIdx < rEndNd ) // Check the moved section + { + SwNode* pNode = &aIdx.GetNode(); + if( pNode->IsTextNode() ) // Looking for text nodes... + { + SwpHints *pHints = pNode->GetTextNode()->GetpSwpHints(); + if( pHints && pHints->HasFootnote() ) //...with footnotes + { + bUpdateFootnote = true; // Heureka + const size_t nCount = pHints->Count(); + for( size_t i = 0; i < nCount; ++i ) + { + SwTextAttr *pAttr = pHints->Get( i ); + if ( pAttr->Which() == RES_TXTATR_FTN ) + { + rSaveArr.insert( static_cast<SwTextFootnote*>(pAttr) ); + } + } + } + } + ++aIdx; + } + } + return bUpdateFootnote; + } + + bool lcl_MayOverwrite( const SwTextNode *pNode, const sal_Int32 nPos ) + { + sal_Unicode const cChr = pNode->GetText()[nPos]; + switch (cChr) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + return !pNode->GetTextAttrForCharAt(nPos);// how could there be none? + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + case CH_TXT_ATR_FORMELEMENT: + return false; + default: + return true; + } + } + + void lcl_SkipAttr( const SwTextNode *pNode, SwIndex &rIdx, sal_Int32 &rStart ) + { + if( !lcl_MayOverwrite( pNode, rStart ) ) + { + // skip all special attributes + do { + ++rIdx; + rStart = rIdx.GetIndex(); + } while (rStart < pNode->GetText().getLength() + && !lcl_MayOverwrite(pNode, rStart) ); + } + } + + bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc ) + { + if( bRegExpRplc ) + { + sal_Int32 nPos = 0; + const OUString sPara("\\n"); + for (;;) + { + nPos = rStr.indexOf( sPara, nPos ); + if (nPos<0) + { + break; + } + // Has this been escaped? + if( nPos && '\\' == rStr[nPos-1]) + { + ++nPos; + if( nPos >= rStr.getLength() ) + { + break; + } + } + else + { + rRet = rStr.copy( 0, nPos ); + rStr = rStr.copy( nPos + sPara.getLength() ); + return true; + } + } + } + rRet = rStr; + rStr.clear(); + return false; + } +} + +namespace //local functions originally from docfmt.cxx +{ + + bool lcl_ApplyOtherSet( + SwContentNode & rNode, + SwHistory *const pHistory, + SfxItemSet const& rOtherSet, + SfxItemSet const& rFirstSet, + SfxItemSet const& rPropsSet, + SwRootFrame const*const pLayout, + SwNodeIndex *const o_pIndex = nullptr) + { + assert(rOtherSet.Count()); + + bool ret(false); + SwTextNode *const pTNd = rNode.GetTextNode(); + sw::MergedPara const* pMerged(nullptr); + if (pLayout && pLayout->IsHideRedlines() && pTNd) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + pTNd->getLayoutFrame(pLayout))); + if (pTextFrame) + { + pMerged = pTextFrame->GetMergedPara(); + } + if (pMerged) + { + if (rFirstSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pFirstNode, *pMerged->pFirstNode, pHistory); + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + else + { + ret = pMerged->pFirstNode->SetAttr(rFirstSet); + } + } + if (rPropsSet.Count()) + { + if (pHistory) + { + SwRegHistory aRegH(pMerged->pParaPropsNode, *pMerged->pParaPropsNode, pHistory); + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + else + { + ret = pMerged->pParaPropsNode->SetAttr(rPropsSet) || ret; + } + } + if (o_pIndex) + { + *o_pIndex = *pMerged->pLastNode; // skip hidden + } + } + } + + // input cursor can't be on hidden node, and iteration skips them + assert(!pLayout || !pLayout->IsHideRedlines() + || rNode.GetRedlineMergeFlag() != SwNode::Merge::Hidden); + + if (!pMerged) + { + if (pHistory) + { + SwRegHistory aRegH(&rNode, rNode, pHistory); + ret = rNode.SetAttr( rOtherSet ); + } + else + { + ret = rNode.SetAttr( rOtherSet ); + } + } + return ret; + } + + #define DELETECHARSETS if ( bDelete ) { delete pCharSet; delete pOtherSet; } + + /// Insert Hints according to content types; + // Is used in SwDoc::Insert(..., SwFormatHint &rHt) + + bool lcl_InsAttr( + SwDoc *const pDoc, + const SwPaM &rRg, + const SfxItemSet& rChgSet, + const SetAttrMode nFlags, + SwUndoAttr *const pUndo, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) + { + // Divide the Sets (for selections in Nodes) + const SfxItemSet* pCharSet = nullptr; + const SfxItemSet* pOtherSet = nullptr; + bool bDelete = false; + bool bCharAttr = false; + bool bOtherAttr = false; + + // Check, if we can work with rChgSet or if we have to create additional SfxItemSets + if ( 1 == rChgSet.Count() ) + { + SfxItemIter aIter( rChgSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + if (pItem && !IsInvalidItem(pItem)) + { + const sal_uInt16 nWhich = pItem->Which(); + + if ( isCHRATR(nWhich) || + (RES_TXTATR_CHARFMT == nWhich) || + (RES_TXTATR_INETFMT == nWhich) || + (RES_TXTATR_AUTOFMT == nWhich) || + (RES_TXTATR_UNKNOWN_CONTAINER == nWhich) ) + { + pCharSet = &rChgSet; + bCharAttr = true; + } + + if ( isPARATR(nWhich) + || isPARATR_LIST(nWhich) + || isFRMATR(nWhich) + || isGRFATR(nWhich) + || isUNKNOWNATR(nWhich) + || isDrawingLayerAttribute(nWhich) ) + { + pOtherSet = &rChgSet; + bOtherAttr = true; + } + } + } + + // Build new itemset if either + // - rChgSet.Count() > 1 or + // - The attribute in rChgSet does not belong to one of the above categories + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet* pTmpCharItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT, + RES_TXTATR_UNKNOWN_CONTAINER, + RES_TXTATR_UNKNOWN_CONTAINER>{}); + + SfxItemSet* pTmpOtherItemSet = new SfxItemSet( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_BEGIN, RES_GRFATR_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1, + // FillAttribute support: + XATTR_FILL_FIRST, XATTR_FILL_LAST>{}); + + pTmpCharItemSet->Put( rChgSet ); + pTmpOtherItemSet->Put( rChgSet ); + + pCharSet = pTmpCharItemSet; + pOtherSet = pTmpOtherItemSet; + + bDelete = true; + } + + SwHistory* pHistory = pUndo ? &pUndo->GetHistory() : nullptr; + bool bRet = false; + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + SwContentNode* pNode = pStt->nNode.GetNode().GetContentNode(); + + if( pNode && pNode->IsTextNode() ) + { + // tdf#127606 at editing, remove different formatting of DOCX-like numbering symbol + if (pLayout && pNode->GetTextNode()->getIDocumentSettingAccess()-> + get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING )) + { + SwContentNode* pEndNode = pEnd->nNode.GetNode().GetContentNode(); + SwContentNode* pCurrentNode = pEndNode; + auto nStartIndex = pNode->GetIndex(); + auto nEndIndex = pEndNode->GetIndex(); + SwNodeIndex aIdx( pEnd->nNode.GetNode() ); + while ( pCurrentNode != nullptr && nStartIndex <= pCurrentNode->GetIndex() ) + { + if (pCurrentNode->GetSwAttrSet().HasItem(RES_PARATR_LIST_AUTOFMT) && + // remove character formatting only on wholly selected paragraphs + (nStartIndex < pCurrentNode->GetIndex() || pStt->nContent.GetIndex() == 0) && + (pCurrentNode->GetIndex() < nEndIndex || pEnd->nContent.GetIndex() == pEndNode->Len())) + { + pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT); + // reset also paragraph marker + SwIndex nIdx( pCurrentNode, pCurrentNode->Len() ); + pCurrentNode->GetTextNode()->RstTextAttr(nIdx, 1); + } + pCurrentNode = SwNodes::GoPrevious( &aIdx ); + } + } + // #i27615# + if (rRg.IsInFrontOfLabel()) + { + SwTextNode * pTextNd = pNode->GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd); + } + SwNumRule * pNumRule = pTextNd->GetNumRule(); + + if ( !pNumRule ) + { + OSL_FAIL( "<InsAttr(..)> - PaM in front of label, but text node has no numbering rule set. This is a serious defect." ); + DELETECHARSETS + return false; + } + + int nLevel = pTextNd->GetActualListLevel(); + + if (nLevel < 0) + nLevel = 0; + + if (nLevel >= MAXLEVEL) + nLevel = MAXLEVEL - 1; + + SwNumFormat aNumFormat = pNumRule->Get(static_cast<sal_uInt16>(nLevel)); + SwCharFormat * pCharFormat = + pDoc->FindCharFormatByName(aNumFormat.GetCharFormatName()); + + if (pCharFormat) + { + if (pHistory) + pHistory->Add(pCharFormat->GetAttrSet(), *pCharFormat); + + if ( pCharSet ) + pCharFormat->SetFormatAttr(*pCharSet); + } + + DELETECHARSETS + return true; + } + + const SwIndex& rSt = pStt->nContent; + + // Attributes without an end do not have a range + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSet aTextSet( pDoc->GetAttrPool(), + svl::Items<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1>{} ); + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( + aTextSet, rSt.GetIndex(), rSt.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + SwPaM aPam( pStt->nNode, pStt->nContent.GetIndex()-1, + pStt->nNode, pStt->nContent.GetIndex() ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, true ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + + // TextAttributes with an end never expand their range + if ( !bCharAttr && !bOtherAttr ) + { + // CharFormat and URL attributes are treated separately! + // TEST_TEMP ToDo: AutoFormat! + SfxItemSet aTextSet( + pDoc->GetAttrPool(), + svl::Items< + RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD, + RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, + RES_TXTATR_INPUTFIELD, RES_TXTATR_INPUTFIELD>{}); + + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + const sal_Int32 nInsCnt = rSt.GetIndex(); + const sal_Int32 nEnd = pStt->nNode == pEnd->nNode + ? pEnd->nContent.GetIndex() + : pNode->Len(); + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr ) + || bRet; + + if (bRet && (pDoc->getIDocumentRedlineAccess().IsRedlineOn() || (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() + && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + // Was text content inserted? (RefMark/TOXMarks without an end) + bool bTextIns = nInsCnt != rSt.GetIndex(); + // Was content inserted or set over the selection? + SwPaM aPam( pStt->nNode, bTextIns ? nInsCnt + 1 : nEnd, + pStt->nNode, nInsCnt ); + if( pUndo ) + pUndo->SaveRedlineData( aPam, bTextIns ); + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( + bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ), + true); + else if( bTextIns ) + pDoc->getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + } + } + + // We always have to set the auto flag for PageDescs that are set at the Node! + if( pOtherSet && pOtherSet->Count() ) + { + SwTableNode* pTableNd; + const SwFormatPageDesc* pDesc; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PAGEDESC, + false, reinterpret_cast<const SfxPoolItem**>(&pDesc) )) + { + if( pNode ) + { + // Set auto flag. Only in the template it's without auto! + SwFormatPageDesc aNew( *pDesc ); + + // Tables now also know line breaks + if( !(nFlags & SetAttrMode::APICALL) && + nullptr != ( pTableNd = pNode->FindTableNode() ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( aNew ); + bRet = true; + } + else + { + SwContentNode * pFirstNode(pNode); + if (pLayout && pLayout->IsHideRedlines()) + { + pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->nNode).first; + } + SwRegHistory aRegH( pFirstNode, *pFirstNode, pHistory ); + bRet = pFirstNode->SetAttr( aNew ) || bRet; + } + } + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_PAGEDESC ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + // Tables now also know line breaks + const SvxFormatBreakItem* pBreak; + if( pNode && !(nFlags & SetAttrMode::APICALL) && + nullptr != (pTableNd = pNode->FindTableNode() ) && + SfxItemState::SET == pOtherSet->GetItemState( RES_BREAK, + false, reinterpret_cast<const SfxPoolItem**>(&pBreak) ) ) + { + SwTableNode* pCurTableNd = pTableNd; + while ( nullptr != ( pCurTableNd = pCurTableNd->StartOfSectionNode()->FindTableNode() ) ) + pTableNd = pCurTableNd; + + // set the table format + SwFrameFormat* pFormat = pTableNd->GetTable().GetFrameFormat(); + SwRegHistory aRegH( pFormat, *pTableNd, pHistory ); + pFormat->SetFormatAttr( *pBreak ); + bRet = true; + + // bOtherAttr = true means that pOtherSet == rChgSet. In this case + // we know, that there is only one attribute in pOtherSet. We cannot + // perform the following operations, instead we return: + if ( bOtherAttr ) + return bRet; + + const_cast<SfxItemSet*>(pOtherSet)->ClearItem( RES_BREAK ); + if( !pOtherSet->Count() ) + { + DELETECHARSETS + return bRet; + } + } + + { + // If we have a PoolNumRule, create it if needed + const SwNumRuleItem* pRule; + sal_uInt16 nPoolId=0; + if( SfxItemState::SET == pOtherSet->GetItemState( RES_PARATR_NUMRULE, + false, reinterpret_cast<const SfxPoolItem**>(&pRule) ) && + !pDoc->FindNumRulePtr( pRule->GetValue() ) && + USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(), + SwGetPoolIdFromName::NumRule )) ) + pDoc->getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId ); + } + } + + SfxItemSet firstSet(pDoc->GetAttrPool(), + svl::Items<RES_PAGEDESC, RES_BREAK>{}); + if (pOtherSet && pOtherSet->Count()) + { // actually only RES_BREAK is possible here... + firstSet.Put(*pOtherSet); + } + SfxItemSet propsSet(pDoc->GetAttrPool(), + svl::Items<RES_PARATR_BEGIN, RES_PAGEDESC, + RES_BREAK+1, RES_FRMATR_END, + XATTR_FILL_FIRST, XATTR_FILL_LAST+1>{}); + if (pOtherSet && pOtherSet->Count()) + { + propsSet.Put(*pOtherSet); + } + + if( !rRg.HasMark() ) // no range + { + if( !pNode ) + { + DELETECHARSETS + return bRet; + } + + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwTextNode* pTextNd = pNode->GetTextNode(); + const SwIndex& rSt = pStt->nContent; + sal_Int32 nMkPos, nPtPos = rSt.GetIndex(); + const OUString& rStr = pTextNd->GetText(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetIndex(), RES_TXTATR_INETFMT)); + if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty()) + { + nMkPos = pURLAttr->GetStart(); + nPtPos = *pURLAttr->End(); + } + else + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTextNd->GetText(), nPtPos, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos ) + { + nMkPos = aBndry.startPos; + nPtPos = aBndry.endPos; + } + else + nPtPos = nMkPos = rSt.GetIndex(); + } + + // Remove the overriding attributes from the SwpHintsArray, + // if the selection spans across the whole paragraph. + // These attributes are inserted as FormatAttributes and + // never override the TextAttributes! + if( !(nFlags & SetAttrMode::DONTREPLACE ) && + pTextNd->HasHints() && !nMkPos && nPtPos == rStr.getLength()) + { + SwIndex aSt( pTextNd ); + if( pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNd, pHistory ); + pTextNd->GetpSwpHints()->Register( &aRHst ); + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + if( pTextNd->GetpSwpHints() ) + pTextNd->GetpSwpHints()->DeRegister(); + } + else + pTextNd->RstTextAttr( aSt, nPtPos, 0, pCharSet ); + } + + // the SwRegHistory inserts the attribute into the TextNode! + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr ) + || bRet; + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, aPam ), true); + } + } + if( pOtherSet && pOtherSet->Count() ) + { + // Need to check for unique item for DrawingLayer items of type NameOrIndex + // and evtl. correct that item to ensure unique names for that type. This call may + // modify/correct entries inside of the given SfxItemSet + SfxItemSet aTempLocalCopy(*pOtherSet); + + pDoc->CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy); + bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet; + } + + DELETECHARSETS + return bRet; + } + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() ) + { + if( pUndo ) + pUndo->SaveRedlineData( rRg, false ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Format, rRg ), true); + } + + /* now if range */ + sal_uLong nNodes = 0; + + SwNodeIndex aSt( pDoc->GetNodes() ); + SwNodeIndex aEnd( pDoc->GetNodes() ); + SwIndex aCntEnd( pEnd->nContent ); + + if( pNode ) + { + const sal_Int32 nLen = pNode->Len(); + if( pStt->nNode != pEnd->nNode ) + aCntEnd.Assign( pNode, nLen ); + + if( pStt->nContent.GetIndex() != 0 || aCntEnd.GetIndex() != nLen ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems(*pCharSet, + pStt->nContent.GetIndex(), aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr) + || bRet; + } + + if( pOtherSet && pOtherSet->Count() ) + { + bRet = lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout) || bRet; + } + + // Only selection in a Node. + if( pStt->nNode == pEnd->nNode ) + { + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1 ) + { + SwTextNode* pCurrentNd = pStt->nNode.GetNode().GetTextNode(); + + if (pCurrentNd) + { + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + + } + } + DELETECHARSETS + return bRet; + } + ++nNodes; + aSt.Assign( pStt->nNode.GetNode(), +1 ); + } + else + aSt = pStt->nNode; + aCntEnd = pEnd->nContent; // aEnd was changed! + } + else + aSt.Assign( pStt->nNode.GetNode(), +1 ); + + // aSt points to the first full Node now + + /* + * The selection spans more than one Node. + */ + if( pStt->nNode < pEnd->nNode ) + { + pNode = pEnd->nNode.GetNode().GetContentNode(); + if(pNode) + { + if( aCntEnd.GetIndex() != pNode->Len() ) + { + // the SwRegHistory inserts the attribute into the TextNode! + if( pNode->IsTextNode() && pCharSet && pCharSet->Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + (void)history.InsertItems(*pCharSet, + 0, aCntEnd.GetIndex(), nFlags, /*ppNewTextAttr*/nullptr); + } + + if( pOtherSet && pOtherSet->Count() ) + { + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout); + } + + ++nNodes; + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + } + else + aEnd = pEnd->nNode; + } + else + aEnd.Assign( pEnd->nNode.GetNode(), +1 ); + + // aEnd points BEHIND the last full node now + + /* Edit the fully selected Nodes. */ + // Reset all attributes from the set! + if( pCharSet && pCharSet->Count() && !( SetAttrMode::DONTREPLACE & nFlags ) ) + { + ::sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHistory, pCharSet, pLayout); + pDoc->GetNodes().ForEach( aSt, aEnd, ::sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + bool bCreateSwpHints = pCharSet && ( + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_CHARFMT, false ) || + SfxItemState::SET == pCharSet->GetItemState( RES_TXTATR_INETFMT, false ) ); + + for (SwNodeIndex current = aSt; current < aEnd; ++current) + { + SwTextNode *const pTNd = current.GetNode().GetTextNode(); + if (!pTNd) + continue; + + if (pLayout && pLayout->IsHideRedlines() + && pTNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { // not really sure what to do here, but applying to hidden + continue; // nodes doesn't make sense... + } + + if( pHistory ) + { + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + + if (pCharSet && pCharSet->Count()) + { + SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints() + : pTNd->GetpSwpHints(); + if( pSwpHints ) + pSwpHints->Register( &aRegH ); + + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + if( pSwpHints ) + pSwpHints->DeRegister(); + } + } + else + { + if (pCharSet && pCharSet->Count()) + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + } + ++nNodes; + } + + if (pOtherSet && pOtherSet->Count()) + { + for (; aSt < aEnd; ++aSt) + { + pNode = aSt.GetNode().GetContentNode(); + if (!pNode) + continue; + + lcl_ApplyOtherSet(*pNode, pHistory, *pOtherSet, firstSet, propsSet, pLayout, &aSt); + ++nNodes; + } + } + + //The data parameter flag: bExpandCharToPara, comes from the data member of SwDoc, + //which is set in SW MS Word Binary filter WW8ImplRreader. With this flag on, means that + //current setting attribute set is a character range properties set and comes from a MS Word + //binary file, and the setting range include a paragraph end position (0X0D); + //more specifications, as such property inside the character range properties set recorded in + //MS Word binary file are dealt and inserted into data model (SwDoc) one by one, so we + //only dealing the scenario that the char properties set with 1 item inside; + if (bExpandCharToPara && pCharSet && pCharSet->Count() ==1) + { + SwPosition aStartPos (*rRg.Start()); + SwPosition aEndPos (*rRg.End()); + + if (aEndPos.nNode.GetNode().GetTextNode() && aEndPos.nContent != aEndPos.nNode.GetNode().GetTextNode()->Len()) + aEndPos.nNode--; + + sal_uLong nStart = aStartPos.nNode.GetIndex(); + sal_uLong nEnd = aEndPos.nNode.GetIndex(); + for(; nStart <= nEnd; ++nStart) + { + SwNode* pNd = pDoc->GetNodes()[ nStart ]; + if (!pNd || !pNd->IsTextNode()) + continue; + SwTextNode *pCurrentNd = pNd->GetTextNode(); + pCurrentNd->TryCharSetExpandToNum(*pCharSet); + } + } + + DELETECHARSETS + return (nNodes != 0) || bRet; + } +} + +namespace sw +{ + +namespace mark +{ + bool IsFieldmarkOverlap(SwPaM const& rPaM) + { + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + sw::CalcBreaks(Breaks, rPaM); + return !Breaks.empty(); + } +} + +DocumentContentOperationsManager::DocumentContentOperationsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +/** + * Checks if rStart..rEnd mark a range that makes sense to copy. + * + * IsMoveToFly means the copy is a move to create a fly + * and so existing flys at the edge must not be copied. + */ +static bool IsEmptyRange(const SwPosition& rStart, const SwPosition& rEnd, + SwCopyFlags const flags) +{ + if (rStart == rEnd) + { // check if a fly anchored there would be copied - then copy... + return !IsDestroyFrameAnchoredAtChar(rStart, rStart, rEnd, + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::WriterfilterHack|DelContentType::AllMask + : DelContentType::AllMask); + } + else + { + return rEnd < rStart; + } +} + +// Copy an area into this document or into another document +bool +DocumentContentOperationsManager::CopyRange( SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags) const +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + // Catch if there's no copy to do + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel)) + return false; + + // Prevent copying into Flys that are anchored in the source range + if (pDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly)) + { + // Correct the Start-/EndNode + sal_uLong nStt = pStt->nNode.GetIndex(), + nEnd = pEnd->nNode.GetIndex(), + nDiff = nEnd - nStt +1; + SwNode* pNd = m_rDoc.GetNodes()[ nStt ]; + if( pNd->IsContentNode() && pStt->nContent.GetIndex() ) + { + ++nStt; + --nDiff; + } + if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() && + static_cast<SwContentNode*>(pNd)->Len() != pEnd->nContent.GetIndex() ) + { + --nEnd; + --nDiff; + } + if( nDiff && + lcl_ChkFlyFly( pDoc, nStt, nEnd, rPos.nNode.GetIndex() ) ) + { + return false; + } + } + + SwPaM* pRedlineRange = nullptr; + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() || + (!pDoc->getIDocumentRedlineAccess().IsIgnoreRedline() && !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) ) + pRedlineRange = new SwPaM( rPos ); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + + bool bRet = false; + + if( pDoc != &m_rDoc ) + { // ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else if( ! ( *pStt <= rPos && rPos < *pEnd && + ( pStt->nNode != pEnd->nNode || + !pStt->nNode.GetNode().IsTextNode() )) ) + { + // Copy to a position outside of the area, or copy a single TextNode + // Do an ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else + { + // Copy the range in itself + assert(!"mst: this is assumed to be dead code"); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pRedlineRange ) + { + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlineRange ), true); + else + pDoc->getIDocumentRedlineAccess().SplitRedline( *pRedlineRange ); + delete pRedlineRange; + } + + return bRet; +} + +/// Delete a full Section of the NodeArray. +/// The passed Node is located somewhere in the designated Section. +void DocumentContentOperationsManager::DeleteSection( SwNode *pNode ) +{ + assert(pNode && "Didn't pass a Node."); + + SwStartNode* pSttNd = pNode->IsStartNode() ? static_cast<SwStartNode*>(pNode) + : pNode->StartOfSectionNode(); + SwNodeIndex aSttIdx( *pSttNd ), aEndIdx( *pNode->EndOfSectionNode() ); + + // delete all Flys, Bookmarks, ... + DelFlyInRange( aSttIdx, aEndIdx ); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any ); + DelBookmarks(aSttIdx, aEndIdx); + + { + // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area + SwNodeIndex aMvStt( aSttIdx, 1 ); + SwDoc::CorrAbs( aMvStt, aEndIdx, SwPosition( aSttIdx ), true ); + } + + m_rDoc.GetNodes().DelNodes( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() + 1 ); +} + +void DocumentContentOperationsManager::DeleteDummyChar( + SwPosition const& rPos, sal_Unicode const cDummy) +{ + SwPaM aPam(rPos, rPos); + ++aPam.GetPoint()->nContent; + assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy); + (void) cDummy; + + DeleteRangeImpl(aPam); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam ) +{ + lcl_DoWithBreaks( *this, rPam, &DocumentContentOperationsManager::DeleteRangeImpl ); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) +{ + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + const SwNode* pNd = &rStt.nNode.GetNode(); + sal_uInt32 nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() - + pNd->StartOfSectionIndex(); + sal_uInt32 nNodeDiff = rEnd.nNode.GetIndex() - rStt.nNode.GetIndex(); + + if ( nSectDiff-2 <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + /* #i9185# Prevent getting the node after the end node (see below) */ + rEnd.nNode.GetIndex() + 1 == m_rDoc.GetNodes().Count() ) + { + return false; + } + + { + SwPaM temp(rPam, nullptr); + if (!temp.HasMark()) + { + temp.SetMark(); + } + if (SwTextNode *const pNode = temp.Start()->nNode.GetNode().GetTextNode()) + { // rPam may not have nContent set but IsFieldmarkOverlap requires it + pNode->MakeStartIndex(&temp.Start()->nContent); + } + if (SwTextNode *const pNode = temp.End()->nNode.GetNode().GetTextNode()) + { + pNode->MakeEndIndex(&temp.End()->nContent); + } + if (sw::mark::IsFieldmarkOverlap(temp)) + { // a bit of a problem: we want to completely remove the nodes + // but then how can the CH_TXT_ATR survive? + return false; + } + } + + // Move hard page brakes to the following Node. + bool bSavePageBreak = false, bSavePageDesc = false; + + /* #i9185# This would lead to a segmentation fault if not caught above. */ + sal_uLong nNextNd = rEnd.nNode.GetIndex() + 1; + SwTableNode *const pTableNd = m_rDoc.GetNodes()[ nNextNd ]->GetTableNode(); + + if( pTableNd && pNd->IsContentNode() ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + { + const SfxPoolItem *pItem; + const SfxItemSet* pSet = static_cast<const SwContentNode*>(pNd)->GetpSwAttrSet(); + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageDesc = true; + } + + if( pSet && SfxItemState::SET == pSet->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTableFormat->SetFormatAttr( *pItem ); + bSavePageBreak = true; + } + } + } + + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if( bDoesUndo ) + { + if( !rPam.HasMark() ) + rPam.SetMark(); + else if( rPam.GetPoint() == &rStt ) + rPam.Exchange(); + rPam.GetPoint()->nNode++; + + SwContentNode *pTmpNode = rPam.GetPoint()->nNode.GetNode().GetContentNode(); + rPam.GetPoint()->nContent.Assign( pTmpNode, 0 ); + bool bGoNext = (nullptr == pTmpNode); + pTmpNode = rPam.GetMark()->nNode.GetNode().GetContentNode(); + rPam.GetMark()->nContent.Assign( pTmpNode, 0 ); + + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + { + SwPosition aTmpPos( *aDelPam.GetPoint() ); + if( bGoNext ) + { + pTmpNode = m_rDoc.GetNodes().GoNext( &aTmpPos.nNode ); + aTmpPos.nContent.Assign( pTmpNode, 0 ); + } + ::PaMCorrAbs( aDelPam, aTmpPos ); + } + + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete( aDelPam, true )); + + *rPam.GetPoint() = *aDelPam.GetPoint(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + rPam.DeleteMark(); + } + else + { + SwNodeRange aRg( rStt.nNode, rEnd.nNode ); + rPam.Normalize(false); + + // Try to move past the End + if( !rPam.Move( fnMoveForward, GoInNode ) ) + { + // Fair enough, at the Beginning then + rPam.Exchange(); + if( !rPam.Move( fnMoveBackward, GoInNode )) + { + SAL_WARN("sw.core", "DelFullPara: no more Nodes"); + return false; + } + } + // move bookmarks, redlines etc. + if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this + { + m_rDoc.CorrAbs( aRg.aStart, *rPam.GetPoint(), 0, true ); + } + else + { + SwDoc::CorrAbs( aRg.aStart, aRg.aEnd, *rPam.GetPoint(), true ); + } + + // What's with Flys? + { + // If there are FlyFrames left, delete these too + for( size_t n = 0; n < m_rDoc.GetSpzFrameFormats()->size(); ++n ) + { + SwFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // note: here use <= not < like in + // IsDestroyFrameAnchoredAtChar() because of the increment + // of rPam in the bDoesUndo path above! + aRg.aStart <= pAPos->nNode && pAPos->nNode <= aRg.aEnd ) + { + m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + } + + rPam.DeleteMark(); + m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 ); + } + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// #i100466# Add handling of new optional parameter <bForceJoinNext> +bool DocumentContentOperationsManager::DeleteAndJoin( SwPaM & rPam, + const bool bForceJoinNext ) +{ + if ( lcl_StrLenOverflow( rPam ) ) + return false; + + bool const ret = lcl_DoWithBreaks( *this, rPam, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl + : &DocumentContentOperationsManager::DeleteAndJoinImpl, + bForceJoinNext ); + + return ret; +} + +// It seems that this is mostly used by SwDoc internals; the only +// way to call this from the outside seems to be the special case in +// SwDoc::CopyRange (but I have not managed to actually hit that case). +bool DocumentContentOperationsManager::MoveRange( SwPaM& rPaM, SwPosition& rPos, SwMoveFlags eMvFlags ) +{ + // nothing moved: return + const SwPosition *pStt = rPaM.Start(), *pEnd = rPaM.End(); + if( !rPaM.HasMark() || *pStt >= *pEnd || (*pStt <= rPos && rPos < *pEnd)) + return false; + + assert(!sw::mark::IsFieldmarkOverlap(rPaM)); // probably an invalid redline was created? + + // Save the paragraph anchored Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + SaveFlyInRange( rPaM, rPos, aSaveFlyArr, bool( SwMoveFlags::ALLFLYS & eMvFlags ) ); + + // save redlines (if DOC_MOVEREDLINES is used) + SaveRedlines_t aSaveRedl; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rPaM, aSaveRedl ); + + // #i17764# unfortunately, code below relies on undos being + // in a particular order, and presence of bookmarks + // will change this order. Hence, we delete bookmarks + // here without undo. + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + } + + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr<SwUndoMove> pUndoMove; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoMove.reset(new SwUndoMove( rPaM, rPos )); + pUndoMove->SetMoveRedlines( eMvFlags == SwMoveFlags::REDLINES ); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( pStt->nNode, pEnd->nNode, rPos.nNode, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx, + &pStt->nContent, &pEnd->nContent ); + } + + bool bSplit = false; + SwPaM aSavePam( rPos, rPos ); + + // Move the SPoint to the beginning of the range + if( rPaM.GetPoint() == pEnd ) + rPaM.Exchange(); + + // If there is a TextNode before and after the Move, create a JoinNext in the EditShell. + SwTextNode* pSrcNd = rPaM.GetPoint()->nNode.GetNode().GetTextNode(); + bool bCorrSavePam = pSrcNd && pStt->nNode != pEnd->nNode; + + // If one ore more TextNodes are moved, SwNodes::Move will do a SplitNode. + // However, this does not update the cursor. So we create a TextNode to keep + // updating the indices. After the Move the Node is optionally deleted. + SwTextNode * pTNd = rPos.nNode.GetNode().GetTextNode(); + if( pTNd && rPaM.GetPoint()->nNode != rPaM.GetMark()->nNode && + ( rPos.nContent.GetIndex() || ( pTNd->Len() && bCorrSavePam )) ) + { + bSplit = true; + const sal_Int32 nMkContent = rPaM.GetMark()->nContent.GetIndex(); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + + SwTextNode * pOrigNode = pTNd; + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(&m_rDoc, pOrigNode->GetIndex()-1, 0, true, eMode); + } + }); + pTNd = pTNd->SplitContentNode(rPos, &restoreFunc)->GetTextNode(); + + //A new node was inserted before the orig pTNd and the content up to + //rPos moved into it. The old node is returned with the remainder + //of the content in it. + // + //aSavePam was created with rPos, it continues to point to the + //old node, but with the *original* content index into the node. + //Seeing as all the orignode content before that index has + //been removed, the new index into the original node should now be set + //to 0 and the content index of rPos should also be adapted to the + //truncated node + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->nContent.GetIdxReg() == pOrigNode); + assert(aSavePam.GetPoint()->nNode == rPos.nNode.GetIndex()); + assert(rPos.nNode.GetIndex() == pOrigNode->GetIndex()); + aSavePam.GetPoint()->nContent.Assign(pOrigNode, 0); + rPos = *aSavePam.GetMark() = *aSavePam.GetPoint(); + + // correct the PaM! + if( rPos.nNode == rPaM.GetMark()->nNode ) + { + rPaM.GetMark()->nNode = rPos.nNode.GetIndex()-1; + rPaM.GetMark()->nContent.Assign( pTNd, nMkContent ); + } + } + + // Put back the Pam by one "content"; so that it's always outside of + // the manipulated range. + // tdf#99692 don't Move() back if that would end up in another node + // because moving backward is not necessarily the inverse of forward then. + // (but do Move() back if we have split the node) + const bool bNullContent = !bSplit && aSavePam.GetPoint()->nContent == 0; + if( bNullContent ) + { + aSavePam.GetPoint()->nNode--; + aSavePam.GetPoint()->nContent.Assign(aSavePam.GetContentNode(), 0); + } + else + { + bool const success(aSavePam.Move(fnMoveBackward, GoInContent)); + assert(success); + (void) success; + } + + // Copy all Bookmarks that are within the Move range into an array, + // that saves the position as an offset. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks( + pStt->nNode, + pEnd->nNode, + &aSaveBkmks, + &pStt->nContent, + &pEnd->nContent); + + // If there is no range anymore due to the above deletions (e.g. the + // footnotes got deleted), it's still a valid Move! + if( *rPaM.GetPoint() != *rPaM.GetMark() ) + { + // now do the actual move + m_rDoc.GetNodes().MoveRange( rPaM, rPos, m_rDoc.GetNodes() ); + + // after a MoveRange() the Mark is deleted + if ( rPaM.HasMark() ) // => no Move occurred! + { + return false; + } + } + else + rPaM.DeleteMark(); + + OSL_ENSURE( *aSavePam.GetMark() == rPos || + ( aSavePam.GetMark()->nNode.GetNode().GetContentNode() == nullptr ), + "PaM was not moved. Aren't there ContentNodes at the beginning/end?" ); + *aSavePam.GetMark() = rPos; + + rPaM.SetMark(); // create a Sel. around the new range + pTNd = aSavePam.GetNode().GetTextNode(); + assert(!m_rDoc.GetIDocumentUndoRedo().DoesUndo()); + bool bRemove = true; + // Do two Nodes have to be joined at the SavePam? + if (bSplit && pTNd) + { + if (pTNd->CanJoinNext()) + { + // Always join next, because <pTNd> has to stay as it is. + // A join previous from its next would more or less delete <pTNd> + pTNd->JoinNext(); + bRemove = false; + } + } + if (bNullContent) + { + aSavePam.GetPoint()->nNode++; + aSavePam.GetPoint()->nContent.Assign( aSavePam.GetContentNode(), 0 ); + } + else if (bRemove) // No move forward after joining with next paragraph + { + aSavePam.Move( fnMoveForward, GoInContent ); + } + + // Insert the Bookmarks back into the Document. + *rPaM.GetMark() = *aSavePam.Start(); + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc( + &m_rDoc, + rPaM.GetMark()->nNode, + &rPaM.GetMark()->nContent); + *rPaM.GetPoint() = *aSavePam.End(); + + // Move the Flys to the new position. + // note: rPos is at the end here; can't really tell flys that used to be + // at the start of rPam from flys that used to be at the end of rPam + // unfortunately, so some of them are going to end up with wrong anchor... + RestFlyInRange( aSaveFlyArr, *rPaM.Start(), &(rPos.nNode) ); + + // restore redlines (if DOC_MOVEREDLINES is used) + if( !aSaveRedl.empty() ) + { + lcl_RestoreRedlines( &m_rDoc, *aSavePam.Start(), aSaveRedl ); + } + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveNodeRange( SwNodeRange& rRange, SwNodeIndex& rPos, + SwMoveFlags eMvFlags ) +{ + // Moves all Nodes to the new position. + // Bookmarks are moved too (currently without Undo support). + + // If footnotes are being moved to the special section, remove them now. + + // Or else delete the Frames for all footnotes that are being moved + // and have it rebuild after the Move (footnotes can change pages). + // Additionally we have to correct the FootnoteIdx array's sorting. + bool bUpdateFootnote = false; + SwFootnoteIdxs aTmpFntIdx; + + std::unique_ptr<SwUndoMove> pUndo; + if ((SwMoveFlags::CREATEUNDOOBJ & eMvFlags ) && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMove( &m_rDoc, rRange, rPos )); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( rRange.aStart, rRange.aEnd, rPos, + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx ); + } + + SaveRedlines_t aSaveRedl; + std::vector<SwRangeRedline*> aSavRedlInsPosArr; + if( SwMoveFlags::REDLINES & eMvFlags && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + lcl_SaveRedlines( rRange, aSaveRedl ); + + // Find all RedLines that end at the InsPos. + // These have to be moved back to the "old" position after the Move. + SwRedlineTable::size_type nRedlPos = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( rPos.GetNode(), RedlineType::Any ); + if( SwRedlineTable::npos != nRedlPos ) + { + const SwPosition *pRStt, *pREnd; + do { + SwRangeRedline* pTmp = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + pRStt = pTmp->Start(); + pREnd = pTmp->End(); + if( pREnd->nNode == rPos && pRStt->nNode < rPos ) + { + aSavRedlInsPosArr.push_back( pTmp ); + } + } while( pRStt->nNode < rPos && ++nRedlPos < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()); + } + } + + // Copy all Bookmarks that are within the Move range into an array + // that stores all references to positions as an offset. + // The final mapping happens after the Move. + std::vector< ::sw::mark::SaveBookmark> aSaveBkmks; + DelBookmarks(rRange.aStart, rRange.aEnd, &aSaveBkmks); + + // Save the paragraph-bound Flys, so that they can be moved. + SaveFlyArr aSaveFlyArr; + if( !m_rDoc.GetSpzFrameFormats()->empty() ) + SaveFlyInRange( rRange, aSaveFlyArr ); + + // Set it to before the Position, so that it cannot be moved further. + SwNodeIndex aIdx( rPos, -1 ); + + std::unique_ptr<SwNodeIndex> pSaveInsPos; + if( pUndo ) + pSaveInsPos.reset(new SwNodeIndex( rRange.aStart, -1 )); + + // move the Nodes + bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags); + if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rPos, !bNoDelFrames ) ) + { + ++aIdx; // again back to old position + if( pSaveInsPos ) + ++(*pSaveInsPos); + } + else + { + aIdx = rRange.aStart; + pUndo.reset(); + } + + // move the Flys to the new position + if( !aSaveFlyArr.empty() ) + { + SwPosition const tmp(aIdx); + RestFlyInRange(aSaveFlyArr, tmp, nullptr); + } + + // Add the Bookmarks back to the Document + for(auto& rBkmk : aSaveBkmks) + rBkmk.SetInDoc(&m_rDoc, aIdx); + + if( !aSavRedlInsPosArr.empty() ) + { + SwNode* pNewNd = &aIdx.GetNode(); + for(SwRangeRedline* pTmp : aSavRedlInsPosArr) + { + if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) ) + { + SwPosition* pEnd = pTmp->End(); + pEnd->nNode = aIdx; + pEnd->nContent.Assign( pNewNd->GetContentNode(), 0 ); + } + } + } + + if( !aSaveRedl.empty() ) + lcl_RestoreRedlines( &m_rDoc, aIdx.GetIndex(), aSaveRedl ); + + if( pUndo ) + { + pUndo->SetDestRange( aIdx, rPos, *pSaveInsPos ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + pSaveInsPos.reset(); + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos ) +{ + SwNodeIndex aIdx( rPaM.Start()->nNode ); + bool bJoinText = aIdx.GetNode().IsTextNode(); + bool bOneNode = rPaM.GetPoint()->nNode == rPaM.GetMark()->nNode; + aIdx--; // in front of the move area! + + bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT ); + if( bRet && !bOneNode ) + { + if( bJoinText ) + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aNxtIdx( aIdx ); + if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) ) + { + { // Block so SwIndex into node is deleted before Join + m_rDoc.CorrRel( aNxtIdx, SwPosition( aIdx, SwIndex(pTextNd, + pTextNd->GetText().getLength()) ), 0, true ); + } + pTextNd->JoinNext(); + } + } + return bRet; +} + +// Overwrite only uses the point of the PaM, the mark is ignored; characters +// are replaced from point until the end of the node; at the end of the node, +// characters are inserted. +bool DocumentContentOperationsManager::Overwrite( const SwPaM &rRg, const OUString &rStr ) +{ + assert(rStr.getLength()); + SwPosition& rPt = *const_cast<SwPosition*>(rRg.GetPoint()); + if( m_rDoc.GetAutoCorrExceptWord() ) // Add to AutoCorrect + { + if( 1 == rStr.getLength() ) + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPt, rStr[ 0 ] ); + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *pNode = rPt.nNode.GetNode().GetTextNode(); + if (!pNode || rStr.getLength() > pNode->GetSpaceLeft()) // worst case: no erase + { + return false; + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called + } + + const size_t nOldAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + SwDataChanged aTmp( rRg ); + SwIndex& rIdx = rPt.nContent; + sal_Int32 const nActualStart(rIdx.GetIndex()); + sal_Int32 nStart = 0; + + bool bOldExpFlg = pNode->IsIgnoreDontExpand(); + pNode->SetIgnoreDontExpand( true ); + + for( sal_Int32 nCnt = 0; nCnt < rStr.getLength(); ++nCnt ) + { + // start behind the characters (to fix the attributes!) + nStart = rIdx.GetIndex(); + if (nStart < pNode->GetText().getLength()) + { + lcl_SkipAttr( pNode, rIdx, nStart ); + } + sal_Unicode c = rStr[ nCnt ]; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoOverwrite *const pUndoOW( + dynamic_cast<SwUndoOverwrite *>(pUndo) ); + if (pUndoOW) + { + // if CanGrouping() returns true it's already merged + bMerged = pUndoOW->CanGrouping( &m_rDoc, rPt, c ); + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoOverwrite>(&m_rDoc, rPt, c) ); + } + } + else + { + // start behind the characters (to fix the attributes!) + if (nStart < pNode->GetText().getLength()) + ++rIdx; + pNode->InsertText( OUString(c), rIdx, SwInsertFlags::EMPTYEXPAND ); + if( nStart+1 < rIdx.GetIndex() ) + { + rIdx = nStart; + pNode->EraseText( rIdx, 1 ); + ++rIdx; + } + } + } + pNode->SetIgnoreDontExpand( bOldExpFlg ); + + const size_t nNewAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + if( nOldAttrCnt != nNewAttrCnt ) + { + SwUpdateAttr aHint(0,0,0); + pNode->ModifyBroadcast(nullptr, &aHint); + } + + if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any ); + } + else if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + // FIXME: this redline is WRONG: there is no DELETE, and the skipped + // characters are also included in aPam + SwPaM aPam(rPt.nNode, nActualStart, rPt.nNode, rPt.nContent.GetIndex()); + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::InsertString( const SwPaM &rRg, const OUString &rStr, + const SwInsertFlags nInsertMode ) +{ + // tdf#119019 accept tracked paragraph formatting to do not hide new insertions + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting( rRg ); + if (eOld != m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags()) + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + // fetching DoesUndo is surprisingly expensive + bool bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); // AppendUndo not always called! + + const SwPosition& rPos = *rRg.GetPoint(); + + if( m_rDoc.GetAutoCorrExceptWord() ) // add to auto correction + { + if( 1 == rStr.getLength() && m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) + { + m_rDoc.GetAutoCorrExceptWord()->CheckChar( rPos, rStr[ 0 ] ); + } + m_rDoc.DeleteAutoCorrExceptWord(); + } + + SwTextNode *const pNode = rPos.nNode.GetNode().GetTextNode(); + if(!pNode) + return false; + + SwDataChanged aTmp( rRg ); + + if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + if (bDoesUndo) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsert>(rPos.nNode, + rPos.nContent.GetIndex(), ins.getLength(), nInsertMode)); + } + } + else + { // if Undo and grouping is enabled, everything changes! + SwUndoInsert * pUndo = nullptr; + + // don't group the start if hints at the start should be expanded + if (!(nInsertMode & SwInsertFlags::FORCEHINTEXPAND)) + { + SwUndo *const pLastUndo = m_rDoc.GetUndoManager().GetLastUndo(); + SwUndoInsert *const pUndoInsert( + dynamic_cast<SwUndoInsert *>(pLastUndo) ); + if (pUndoInsert && pUndoInsert->CanGrouping(rPos)) + { + pUndo = pUndoInsert; + } + } + + CharClass const& rCC = GetAppCharClass(); + sal_Int32 nInsPos = rPos.nContent.GetIndex(); + + if (!pUndo) + { + pUndo = new SwUndoInsert( rPos.nNode, nInsPos, 0, nInsertMode, + !rCC.isLetterNumeric( rStr, 0 ) ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + + OUString const ins(pNode->InsertText(rStr, rPos.nContent, nInsertMode)); + + for (sal_Int32 i = 0; i < ins.getLength(); ++i) + { + nInsPos++; + // if CanGrouping() returns true, everything has already been done + if (!pUndo->CanGrouping(ins[i])) + { + pUndo = new SwUndoInsert(rPos.nNode, nInsPos, 1, nInsertMode, + !rCC.isLetterNumeric(ins, i)); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + } + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos.nNode, aTmp.GetContent(), + rPos.nNode, rPos.nContent.GetIndex()); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( RedlineType::Insert, aPam ), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +void DocumentContentOperationsManager::TransliterateText( + const SwPaM& rPaM, + utl::TransliterationWrapper& rTrans ) +{ + std::unique_ptr<SwUndoTransliterate> pUndo; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoTransliterate( rPaM, rTrans )); + + const SwPosition* pStt = rPaM.Start(), + * pEnd = rPaM.End(); + sal_uLong nSttNd = pStt->nNode.GetIndex(), + nEndNd = pEnd->nNode.GetIndex(); + sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no selection? + { + // set current word as 'area of effect' + + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTNd->GetText(), nSttCnt, + g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos ) + { + nSttCnt = aBndry.startPos; + nEndCnt = aBndry.endPos; + } + } + + if( nSttNd != nEndNd ) // is more than one text node involved? + { + // iterate over all effected text nodes, the first and the last one + // may be incomplete because the selection starts and/or ends there + + SwNodeIndex aIdx( pStt->nNode ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + pTNd->TransliterateText( + rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get()); + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + { + pTNd = aIdx.GetNode().GetTextNode(); + if (pTNd) + { + pTNd->TransliterateText( + rTrans, 0, pTNd->GetText().getLength(), pUndo.get()); + } + } + + if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() )) + pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get() ); + } + else if( pTNd && nSttCnt < nEndCnt ) + pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get() ); + + if( pUndo && pUndo->HasData() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + m_rDoc.getIDocumentState().SetModified(); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphic( + const SwPaM &rRg, + const OUString& rGrfName, + const OUString& rFltName, + const Graphic* pGraphic, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat ) +{ + if( !pFrameFormat ) + pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfName, rFltName, pGraphic, + m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertGraphicObject( + const SwPaM &rRg, const GraphicObject& rGrfObj, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet ) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_GRAPHIC ); + SwGrfNode* pSwGrfNode = SwNodes::MakeGrfNode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rGrfObj, m_rDoc.GetDfltGrfFormatColl() ); + SwFlyFrameFormat* pSwFlyFrameFormat = InsNoTextNode( *rRg.GetPoint(), pSwGrfNode, + pFlyAttrSet, pGrfAttrSet, pFrameFormat ); + return pSwFlyFrameFormat; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertEmbObject( + const SwPaM &rRg, const svt::EmbeddedObjectRef& xObj, + SfxItemSet* pFlyAttrSet) +{ + sal_uInt16 nId = RES_POOLFRM_OLE; + if (xObj.is()) + { + SvGlobalName aClassName( xObj->getClassID() ); + if (SotExchange::IsMath(aClassName)) + { + nId = RES_POOLFRM_FORMEL; + } + } + + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( nId ); + + return InsNoTextNode( *rRg.GetPoint(), m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + xObj, + m_rDoc.GetDfltGrfFormatColl() ), + pFlyAttrSet, nullptr, + pFrameFormat ); +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsertOLE(const SwPaM &rRg, const OUString& rObjName, + sal_Int64 nAspect, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet) +{ + SwFrameFormat* pFrameFormat = m_rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_OLE ); + + return InsNoTextNode( *rRg.GetPoint(), + m_rDoc.GetNodes().MakeOLENode( + SwNodeIndex( m_rDoc.GetNodes().GetEndOfAutotext() ), + rObjName, + nAspect, + m_rDoc.GetDfltGrfFormatColl(), + nullptr ), + pFlyAttrSet, pGrfAttrSet, + pFrameFormat ); +} + +void DocumentContentOperationsManager::ReRead( SwPaM& rPam, const OUString& rGrfName, + const OUString& rFltName, const Graphic* pGraphic ) +{ + SwGrfNode *pGrfNd; + if( ( !rPam.HasMark() + || rPam.GetPoint()->nNode.GetIndex() == rPam.GetMark()->nNode.GetIndex() ) + && nullptr != ( pGrfNd = rPam.GetPoint()->nNode.GetNode().GetGrfNode() ) ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoReRead>(rPam, *pGrfNd)); + } + + // Because we don't know if we can mirror the graphic, the mirror attribute is always reset + if( MirrorGraph::Dont != pGrfNd->GetSwAttrSet(). + GetMirrorGrf().GetValue() ) + pGrfNd->SetAttr( SwMirrorGrf() ); + + pGrfNd->ReRead( rGrfName, rFltName, pGraphic ); + m_rDoc.getIDocumentState().SetModified(); + } +} + +// Insert drawing object, which has to be already inserted in the DrawModel +SwDrawFrameFormat* DocumentContentOperationsManager::InsertDrawObj( + const SwPaM &rRg, + SdrObject& rDrawObj, + const SfxItemSet& rFlyAttrSet ) +{ + SwDrawFrameFormat* pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() ); + + const SwFormatAnchor* pAnchor = nullptr; + rFlyAttrSet.GetItemState( RES_ANCHOR, false, reinterpret_cast<const SfxPoolItem**>(&pAnchor) ); + pFormat->SetFormatAttr( rFlyAttrSet ); + + // Didn't set the Anchor yet? + // DrawObjecte must never end up in the Header/Footer! + RndStdIds eAnchorId = pAnchor != nullptr ? pAnchor->GetAnchorId() : pFormat->GetAnchor().GetAnchorId(); + const bool bIsAtContent = (RndStdIds::FLY_AT_PAGE != eAnchorId); + + const SwNodeIndex* pChkIdx = nullptr; + if ( pAnchor == nullptr ) + { + pChkIdx = &rRg.GetPoint()->nNode; + } + else if ( bIsAtContent ) + { + pChkIdx = + pAnchor->GetContentAnchor() ? &pAnchor->GetContentAnchor()->nNode : &rRg.GetPoint()->nNode; + } + + // allow drawing objects in header/footer, but control objects aren't allowed in header/footer. + if( pChkIdx != nullptr + && ::CheckControlLayer( &rDrawObj ) + && m_rDoc.IsInHeaderFooter( *pChkIdx ) ) + { + // apply at-page anchor format + eAnchorId = RndStdIds::FLY_AT_PAGE; + pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) ); + } + else if( pAnchor == nullptr + || ( bIsAtContent + && pAnchor->GetContentAnchor() == nullptr ) ) + { + // apply anchor format + SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() ); + eAnchorId = aAnch.GetAnchorId(); + if ( eAnchorId == RndStdIds::FLY_AT_FLY ) + { + SwPosition aPos( *rRg.GetNode().FindFlyStartNode() ); + aAnch.SetAnchor( &aPos ); + } + else + { + aAnch.SetAnchor( rRg.GetPoint() ); + if ( eAnchorId == RndStdIds::FLY_AT_PAGE ) + { + eAnchorId = dynamic_cast<const SdrUnoObj*>( &rDrawObj) != nullptr ? RndStdIds::FLY_AS_CHAR : RndStdIds::FLY_AT_PARA; + aAnch.SetType( eAnchorId ); + } + } + pFormat->SetFormatAttr( aAnch ); + } + + // insert text attribute for as-character anchored drawing object + if ( eAnchorId == RndStdIds::FLY_AS_CHAR ) + { + bool bAnchorAtPageAsFallback = true; + const SwFormatAnchor& rDrawObjAnchorFormat = pFormat->GetAnchor(); + if ( rDrawObjAnchorFormat.GetContentAnchor() != nullptr ) + { + SwTextNode* pAnchorTextNode = + rDrawObjAnchorFormat.GetContentAnchor()->nNode.GetNode().GetTextNode(); + if ( pAnchorTextNode != nullptr ) + { + const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->nContent.GetIndex(); + SwFormatFlyCnt aFormat( pFormat ); + pAnchorTextNode->InsertItem( aFormat, nStt, nStt ); + bAnchorAtPageAsFallback = false; + } + } + + if ( bAnchorAtPageAsFallback ) + { + OSL_ENSURE( false, "DocumentContentOperationsManager::InsertDrawObj(..) - missing content anchor for as-character anchored drawing object --> anchor at-page" ); + pFormat->SetFormatAttr( SwFormatAnchor( RndStdIds::FLY_AT_PAGE ) ); + } + } + + SwDrawContact* pContact = new SwDrawContact( pFormat, &rDrawObj ); + + // Create Frames if necessary + if( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // create layout representation + pFormat->MakeFrames(); + // #i42319# - follow-up of #i35635# + // move object to visible layer + // #i79391# + if ( pContact->GetAnchorFrame() ) + { + pContact->MoveObjToVisibleLayer( &rDrawObj ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsLayFormat>(pFormat, 0, 0) ); + } + + m_rDoc.getIDocumentState().SetModified(); + return pFormat; +} + +bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart ) +{ + SwContentNode *pNode = rPos.nNode.GetNode().GetContentNode(); + if(nullptr == pNode) + return false; + + { + // BUG 26675: Send DataChanged before deleting, so that we notice which objects are in scope. + // After that they can be before/after the position. + SwDataChanged aTmp( &m_rDoc, rPos ); + } + + SwUndoSplitNode* pUndo = nullptr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + // insert the Undo object (currently only for TextNode) + if( pNode->IsTextNode() ) + { + pUndo = new SwUndoSplitNode( &m_rDoc, rPos, bChkTableStart ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + } + + // Update the rsid of the old and the new node unless + // the old node is split at the beginning or at the end + SwTextNode *pTextNode = rPos.nNode.GetNode().GetTextNode(); + const sal_Int32 nPos = rPos.nContent.GetIndex(); + if( pTextNode && nPos && nPos != pTextNode->Len() ) + { + m_rDoc.UpdateParRsid( pTextNode ); + } + + //JP 28.01.97: Special case for SplitNode at table start: + // If it is at the beginning of a Doc/Fly/Footer/... or right at after a table + // then insert a paragraph before it. + if( bChkTableStart && !rPos.nContent.GetIndex() && pNode->IsTextNode() ) + { + sal_uLong nPrevPos = rPos.nNode.GetIndex() - 1; + const SwTableNode* pTableNd; + const SwNode* pNd = m_rDoc.GetNodes()[ nPrevPos ]; + if( pNd->IsStartNode() && + SwTableBoxStartNode == static_cast<const SwStartNode*>(pNd)->GetStartNodeType() && + nullptr != ( pTableNd = m_rDoc.GetNodes()[ --nPrevPos ]->GetTableNode() ) && + ((( pNd = m_rDoc.GetNodes()[ --nPrevPos ])->IsStartNode() && + SwTableBoxStartNode != static_cast<const SwStartNode*>(pNd)->GetStartNodeType() ) + || ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsTableNode() ) + || pNd->IsContentNode() )) + { + if( pNd->IsContentNode() ) + { + //JP 30.04.99 Bug 65660: + // There are no page breaks outside of the normal body area, + // so this is not a valid condition to insert a paragraph. + if( nPrevPos < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + pNd = nullptr; + else + { + // Only if the table has page breaks! + const SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + if( SfxItemState::SET != pFrameFormat->GetItemState(RES_PAGEDESC, false) && + SfxItemState::SET != pFrameFormat->GetItemState( RES_BREAK, false ) ) + pNd = nullptr; + } + } + + if( pNd ) + { + SwTextNode* pTextNd = m_rDoc.GetNodes().MakeTextNode( + SwNodeIndex( *pTableNd ), + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + if( pTextNd ) + { + const_cast<SwPosition&>(rPos).nNode = pTableNd->GetIndex()-1; + const_cast<SwPosition&>(rPos).nContent.Assign( pTextNd, 0 ); + + // only add page breaks/styles to the body area + if( nPrevPos > m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + SwFrameFormat* pFrameFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_PAGEDESC ); + } + if( SfxItemState::SET == pFrameFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pTextNd->SetAttr( *pItem ); + pFrameFormat->ResetFormatAttr( RES_BREAK ); + } + } + + if( pUndo ) + pUndo->SetTableFlag(); + m_rDoc.getIDocumentState().SetModified(); + return true; + } + } + } + } + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( &m_rDoc, rPos.nNode.GetIndex(), rPos.nContent.GetIndex(), true ); + assert(pNode->IsTextNode()); + std::function<void (SwTextNode *, sw::mark::RestoreMode)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode) + { + if (!pContentStore->Empty()) + { // move all bookmarks, TOXMarks, FlyAtCnt + pContentStore->Restore(&m_rDoc, rPos.nNode.GetIndex()-1, 0, true, eMode); + } + if (eMode & sw::mark::RestoreMode::NonFlys) + { + // To-Do - add 'SwExtraRedlineTable' also ? + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && + !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty())) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + { + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(RedlineType::Insert, aPam), true); + } + else + { + m_rDoc.getIDocumentRedlineAccess().SplitRedline(aPam); + } + } + } + }); + pNode->GetTextNode()->SplitContentNode(rPos, &restoreFunc); + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::AppendTextNode( SwPosition& rPos ) +{ + // create new node before EndOfContent + SwTextNode * pCurNode = rPos.nNode.GetNode().GetTextNode(); + if( !pCurNode ) + { + // so then one can be created! + SwNodeIndex aIdx( rPos.nNode, 1 ); + pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + } + else + pCurNode = pCurNode->AppendNode( rPos )->GetTextNode(); + + rPos.nNode++; + rPos.nContent.Assign( pCurNode, 0 ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.nNode ) ); + } + + // To-Do - add 'SwExtraRedlineTable' also ? + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + SwPaM aPam( rPos ); + aPam.SetMark(); + aPam.Move( fnMoveBackward ); + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + m_rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + // unfortunately replace works slightly differently from delete, + // so we cannot use lcl_DoWithBreaks here... + + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + aPam.Normalize(false); + if (aPam.GetPoint()->nNode != aPam.GetMark()->nNode) + { + aPam.Move(fnMoveBackward); + } + OSL_ENSURE((aPam.GetPoint()->nNode == aPam.GetMark()->nNode), "invalid pam?"); + + sw::CalcBreaks(Breaks, aPam); + + while (!Breaks.empty() // skip over prefix of dummy chars + && (aPam.GetMark()->nNode.GetIndex() == Breaks.begin()->first) + && (aPam.GetMark()->nContent.GetIndex() == Breaks.begin()->second)) + { + // skip! + ++aPam.GetMark()->nContent; // always in bounds if Breaks valid + Breaks.erase(Breaks.begin()); + } + *rPam.Start() = *aPam.GetMark(); // update start of original pam w/ prefix + + if (Breaks.empty()) + { + // park aPam somewhere so it does not point to node that is deleted + aPam.DeleteMark(); + *aPam.GetPoint() = SwPosition(m_rDoc.GetNodes().GetEndOfContent()); + return ReplaceRangeImpl(rPam, rStr, bRegExReplace); // original pam! + } + + // Deletion must be split into several parts if the text node + // contains a text attribute with end and with dummy character + // and the selection does not contain the text attribute completely, + // but overlaps its start (left), where the dummy character is. + + bool bRet( true ); + // iterate from end to start, to avoid invalidating the offsets! + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + OSL_ENSURE(aPam.GetPoint() == aPam.End(), "wrong!"); + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + // set end of temp pam to original end (undo Move backward above) + rEnd = *rPam.End(); + // after first deletion, rEnd will point into the original text node again! + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? DeleteAndJoinWithRedlineImpl(aPam) + : DeleteAndJoinImpl(aPam, false); + nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + assert(rStart < rEnd && "replace part empty!"); + if (rStart < rEnd) // check if part is empty + { + bRet &= ReplaceRangeImpl(aPam, rStr, bRegExReplace); + } + + rPam = aPam; // update original pam (is this required?) + + return bRet; +} + +///Add a para for the char attribute exp... +bool DocumentContentOperationsManager::InsertPoolItem( + const SwPaM &rRg, + const SfxPoolItem &rHt, + const SetAttrMode nFlags, + SwRootFrame const*const pLayout, + const bool bExpandCharToPara, + SwTextAttr **ppNewTextAttr) +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + + SwDataChanged aTmp( rRg ); + std::unique_ptr<SwUndoAttr> pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rHt, nFlags )); + } + + SfxItemSet aSet( m_rDoc.GetAttrPool(), {{rHt.Which(), rHt.Which()}} ); + aSet.Put( rHt ); + const bool bRet = lcl_InsAttr(&m_rDoc, rRg, aSet, nFlags, pUndoAttr.get(), pLayout, bExpandCharToPara, ppNewTextAttr); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + { + m_rDoc.getIDocumentState().SetModified(); + } + return bRet; +} + +void DocumentContentOperationsManager::InsertItemSet ( const SwPaM &rRg, const SfxItemSet &rSet, + const SetAttrMode nFlags, SwRootFrame const*const pLayout) +{ + SwDataChanged aTmp( rRg ); + std::unique_ptr<SwUndoAttr> pUndoAttr; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + pUndoAttr.reset(new SwUndoAttr( rRg, rSet, nFlags )); + } + + bool bRet = lcl_InsAttr(&m_rDoc, rRg, rSet, nFlags, pUndoAttr.get(), pLayout, /*bExpandCharToPara*/false, /*ppNewTextAttr*/nullptr ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::move(pUndoAttr) ); + } + + if( bRet ) + m_rDoc.getIDocumentState().SetModified(); +} + +void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(const SwPosition & rPos ) +{ + const SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + if ( pTNd ) + { + const OUString& rText = pTNd->GetText(); + sal_Int32 nIdx = 0; + while (nIdx < rText.getLength()) + { + sal_Unicode const cCh = rText[nIdx]; + if (('\t' != cCh) && (' ' != cCh)) + { + break; + } + ++nIdx; + } + + if ( nIdx > 0 ) + { + SwPaM aPam(rPos); + aPam.GetPoint()->nContent = 0; + aPam.SetMark(); + aPam.GetMark()->nContent = nIdx; + DeleteRange( aPam ); + } + } +} + +// Copy method from SwDoc - "copy Flys in Flys" +/// note: rRg/rInsPos *exclude* a partially selected start text node; +/// pCopiedPaM *includes* a partially selected start text node +void DocumentContentOperationsManager::CopyWithFlyInFly( + const SwNodeRange& rRg, + const SwNodeIndex& rInsPos, + const std::pair<const SwPaM&, const SwPosition&>* pCopiedPaM /*and real insert pos*/, + const bool bMakeNewFrames, + const bool bDelRedlines, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->first.End()->nNode == rRg.aEnd); + assert(!pCopiedPaM || pCopiedPaM->second.nNode <= rInsPos); + + SwDoc* pDest = rInsPos.GetNode().GetDoc(); + SwNodeIndex aSavePos( rInsPos ); + + if (rRg.aStart != rRg.aEnd) + { + bool bEndIsEqualEndPos = rInsPos == rRg.aEnd; + bool isRecreateEndNode(false); + --aSavePos; + SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); + + // insert behind the already copied start node + m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true ); + aRedlRest.Restore(); + if (bMakeNewFrames) // tdf#130685 only after aRedlRest + { // recreate from previous node (could be merged now) + if (SwTextNode *const pNode = aSavePos.GetNode().GetTextNode()) + { + std::unordered_set<SwTextFrame*> frames; + SwTextNode *const pEndNode = rInsPos.GetNode().GetTextNode(); + if (pEndNode) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.insert(pFrame); + } + } + } + sw::RecreateStartTextFrames(*pNode); + if (!frames.empty()) + { // tdf#132187 check if the end node needs new frames + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + auto const it = frames.find(pFrame); + if (it != frames.end()) + { + frames.erase(it); + } + } + } + if (!frames.empty()) // existing frame was deleted + { // all layouts because MakeFrames recreates all layouts + pEndNode->DelFrames(nullptr); + isRecreateEndNode = true; + } + } + } + } + bool const isAtStartOfSection(aSavePos.GetNode().IsStartNode()); + ++aSavePos; + if (bMakeNewFrames) + { + // it's possible that CheckParaRedlineMerge() deleted frames + // on rInsPos so have to include it, but it must not be included + // if it was the first node in the document so that MakeFrames() + // will find the existing (wasn't deleted) frame on it + SwNodeIndex const end(rInsPos, + (!isRecreateEndNode || isAtStartOfSection) + ? 0 : +1); + ::MakeFrames(pDest, aSavePos, end); + } + if (bEndIsEqualEndPos) + { + const_cast<SwNodeIndex&>(rRg.aEnd) = aSavePos; + } + } + +#if OSL_DEBUG_LEVEL > 0 + { + //JP 17.06.99: Bug 66973 - check count only if the selection is in + // the same section or there's no section, because sections that are + // not fully selected are not copied. + const SwSectionNode* pSSectNd = rRg.aStart.GetNode().FindSectionNode(); + SwNodeIndex aTmpI( rRg.aEnd, -1 ); + const SwSectionNode* pESectNd = aTmpI.GetNode().FindSectionNode(); + if( pSSectNd == pESectNd && + !rRg.aStart.GetNode().IsSectionNode() && + !aTmpI.GetNode().IsEndNode() ) + { + // If the range starts with a SwStartNode, it isn't copied + sal_uInt16 offset = (rRg.aStart.GetNode().GetNodeType() != SwNodeType::Start) ? 1 : 0; + OSL_ENSURE( rInsPos.GetIndex() - aSavePos.GetIndex() == + rRg.aEnd.GetIndex() - rRg.aStart.GetIndex() - 1 + offset, + "An insufficient number of nodes were copied!" ); + } + } +#endif + + { + ::sw::UndoGuard const undoGuard(pDest->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr, + // see comment below regarding use of pCopiedPaM->second + (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + ? pCopiedPaM->second.nNode + : aSavePos, + bCopyFlyAtFly, + flags); + } + + SwNodeRange aCpyRange( aSavePos, rInsPos ); + + // Also copy all bookmarks + // guess this must be done before the DelDummyNodes below as that + // deletes nodes so would mess up the index arithmetic + if( m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPaM aCpyPaM(aCpyRange.aStart, aCpyRange.aEnd); + if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->nNode) + { + // there is 1 (partially selected, maybe) paragraph before + assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->nNode); + // only use the passed in target SwPosition if the source PaM point + // is on a different node; if it was the same node then the target + // position was likely moved along by the copy operation and now + // points to the end of the range! + *aCpyPaM.GetPoint() = pCopiedPaM->second; + } + + sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, *aCpyPaM.Start()); + } + + if( bDelRedlines && ( RedlineFlags::DeleteRedlines & pDest->getIDocumentRedlineAccess().GetRedlineFlags() )) + lcl_DeleteRedlines( rRg, aCpyRange ); + + pDest->GetNodes().DelDummyNodes( aCpyRange ); +} + +// note: for the redline Show/Hide this must be in sync with +// SwRangeRedline::CopyToSection()/DelCopyOfSection()/MoveFromSection() +void DocumentContentOperationsManager::CopyFlyInFlyImpl( + const SwNodeRange& rRg, + SwPaM const*const pCopiedPaM, + const SwNodeIndex& rStartIdx, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->End()->nNode == rRg.aEnd); + + // First collect all Flys, sort them according to their ordering number, + // and then only copy them. This maintains the ordering numbers (which are only + // managed in the DrawModel). + SwDoc *const pDest = rStartIdx.GetNode().GetDoc(); + std::set< ZSortFly > aSet; + const size_t nArrLen = m_rDoc.GetSpzFrameFormats()->size(); + + SwTextBoxHelper::SavedLink aOldTextBoxes; + SwTextBoxHelper::saveLinks(*m_rDoc.GetSpzFrameFormats(), aOldTextBoxes); + + for ( size_t n = 0; n < nArrLen; ++n ) + { + SwFrameFormat* pFormat = (*m_rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if ( !pAPos ) + continue; + bool bAdd = false; + sal_uLong nSkipAfter = pAPos->nNode.GetIndex(); + sal_uLong nStart = rRg.aStart.GetIndex(); + switch ( pAnchor->GetAnchorId() ) + { + case RndStdIds::FLY_AT_FLY: + if(bCopyFlyAtFly) + ++nSkipAfter; + else if(m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) + ++nStart; + break; + case RndStdIds::FLY_AT_PARA: + { + bAdd = IsSelectFrameAnchoredAtPara(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + case RndStdIds::FLY_AT_CHAR: + { + bAdd = IsDestroyFrameAnchoredAtChar(*pAPos, + pCopiedPaM ? *pCopiedPaM->Start() : SwPosition(rRg.aStart), + pCopiedPaM ? *pCopiedPaM->End() : SwPosition(rRg.aEnd), + (flags & SwCopyFlags::IsMoveToFly) + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask); + } + break; + default: + continue; + } + if (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId()) + { + if (nStart > nSkipAfter) + continue; + if (pAPos->nNode > rRg.aEnd) + continue; + //frames at the last source node are not always copied: + //- if the node is empty and is the last node of the document or a table cell + // or a text frame then they have to be copied + //- if the content index in this node is > 0 then paragraph and frame bound objects are copied + //- to-character bound objects are copied if their index is <= nEndContentIndex + if (pAPos->nNode < rRg.aEnd) + bAdd = true; + if (!bAdd && !m_rDoc.getIDocumentRedlineAccess().IsRedlineMove()) // fdo#40599: not for redline move + { + if (!bAdd) + { + // technically old code checked nContent of AT_FLY which is pointless + bAdd = pCopiedPaM && 0 < pCopiedPaM->End()->nContent.GetIndex(); + } + } + } + if( bAdd ) + { + aSet.insert( ZSortFly( pFormat, pAnchor, nArrLen + aSet.size() )); + } + } + + // Store all copied (and also the newly created) frames in another array. + // They are stored as matching the originals, so that we will be later + // able to build the chains accordingly. + std::vector< SwFrameFormat* > aVecSwFrameFormat; + std::set< ZSortFly >::const_iterator it=aSet.begin(); + + while (it != aSet.end()) + { + // #i59964# + // correct determination of new anchor position + SwFormatAnchor aAnchor( *(*it).GetAnchor() ); + assert( aAnchor.GetContentAnchor() != nullptr ); + SwPosition newPos = *aAnchor.GetContentAnchor(); + // for at-paragraph and at-character anchored objects the new anchor + // position can *not* be determined by the difference of the current + // anchor position to the start of the copied range, because not + // complete selected sections in the copied range aren't copied - see + // method <SwNodes::CopyNodes(..)>. + // Thus, the new anchor position in the destination document is found + // by counting the text nodes. + if ((aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) || + (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) ) + { + // First, determine number of anchor text node in the copied range. + // Note: The anchor text node *have* to be inside the copied range. + sal_uLong nAnchorTextNdNumInRange( 0 ); + bool bAnchorTextNdFound( false ); + // start at the first node for which flys are copied + SwNodeIndex aIdx(pCopiedPaM ? pCopiedPaM->Start()->nNode : rRg.aStart); + while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + ++nAnchorTextNdNumInRange; + bAnchorTextNdFound = aAnchor.GetContentAnchor()->nNode == aIdx; + } + + ++aIdx; + } + + if ( !bAnchorTextNdFound ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - anchor text node in copied range not found" ); + nAnchorTextNdNumInRange = 1; + } + // Second, search corresponding text node in destination document + // by counting forward from start insert position <rStartIdx> the + // determined number of text nodes. + aIdx = rStartIdx; + SwNodeIndex aAnchorNdIdx( rStartIdx ); + const SwNode& aEndOfContentNd = + aIdx.GetNode().GetNodes().GetEndOfContent(); + while ( nAnchorTextNdNumInRange > 0 && + &(aIdx.GetNode()) != &aEndOfContentNd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + --nAnchorTextNdNumInRange; + aAnchorNdIdx = aIdx; + } + + ++aIdx; + } + if ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + // This case can *not* happen, but to be robust take the first + // text node in the destination document. + OSL_FAIL( "<SwDoc::_CopyFlyInFly(..)> - found anchor node index isn't a text node" ); + aAnchorNdIdx = rStartIdx; + while ( !aAnchorNdIdx.GetNode().IsTextNode() ) + { + ++aAnchorNdIdx; + } + } + // apply found anchor text node as new anchor position + newPos.nNode = aAnchorNdIdx; + } + else + { + long nOffset = newPos.nNode.GetIndex() - rRg.aStart.GetIndex(); + SwNodeIndex aIdx( rStartIdx, nOffset ); + newPos.nNode = aIdx; + } + // Set the character bound Flys back at the original character + if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) && + newPos.nNode.GetNode().IsTextNode() ) + { + // only if pCopiedPaM: care about partially selected start node + sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->nNode == aAnchor.GetContentAnchor()->nNode + ? newPos.nContent.GetIndex() - pCopiedPaM->Start()->nContent.GetIndex() + : newPos.nContent.GetIndex(); + newPos.nContent.Assign(newPos.nNode.GetNode().GetTextNode(), nContent); + } + else + { + newPos.nContent.Assign( nullptr, 0 ); + } + aAnchor.SetAnchor( &newPos ); + + // Check recursion: if copying content inside the same frame, then don't copy the format. + if( pDest == &m_rDoc ) + { + const SwFormatContent& rContent = (*it).GetFormat()->GetContent(); + const SwStartNode* pSNd; + if( rContent.GetContentIdx() && + nullptr != ( pSNd = rContent.GetContentIdx()->GetNode().GetStartNode() ) && + pSNd->GetIndex() < rStartIdx.GetIndex() && + rStartIdx.GetIndex() < pSNd->EndOfSectionIndex() ) + { + it = aSet.erase(it); + continue; + } + } + + // Ignore TextBoxes, they are already handled in + // sw::DocumentLayoutManager::CopyLayoutFormat(). + if (SwTextBoxHelper::isTextBox(it->GetFormat(), RES_FLYFRMFMT)) + { + it = aSet.erase(it); + continue; + } + + // Copy the format and set the new anchor + aVecSwFrameFormat.push_back( pDest->getIDocumentLayoutAccess().CopyLayoutFormat( *(*it).GetFormat(), + aAnchor, false, true ) ); + ++it; + } + + // Rebuild as much as possible of all chains that are available in the original, + OSL_ENSURE( aSet.size() == aVecSwFrameFormat.size(), "Missing new Flys" ); + if ( aSet.size() == aVecSwFrameFormat.size() ) + { + size_t n = 0; + for (const auto& rFlyN : aSet) + { + const SwFrameFormat *pFormatN = rFlyN.GetFormat(); + const SwFormatChain &rChain = pFormatN->GetChain(); + int nCnt = int(nullptr != rChain.GetPrev()); + nCnt += rChain.GetNext() ? 1: 0; + size_t k = 0; + for (const auto& rFlyK : aSet) + { + const SwFrameFormat *pFormatK = rFlyK.GetFormat(); + if ( rChain.GetPrev() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]) ); + --nCnt; + } + else if ( rChain.GetNext() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) ); + --nCnt; + } + ++k; + } + ++n; + } + + // Re-create content property of draw formats, knowing how old shapes + // were paired with old fly formats (aOldTextBoxes) and that aSet is + // parallel with aVecSwFrameFormat. + SwTextBoxHelper::restoreLinks(aSet, aVecSwFrameFormat, aOldTextBoxes); + } +} + +/* + * Reset the text's hard formatting + */ +/** @params pArgs contains the document's ChrFormatTable + * Is need for selections at the beginning/end and with no SSelection. + */ +bool DocumentContentOperationsManager::lcl_RstTextAttr( const SwNodePtr& rpNd, void* pArgs ) +{ + ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs); + if (pPara->pLayout && pPara->pLayout->IsHideRedlines() + && rpNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; // skip hidden, since new items aren't applied + } + SwTextNode * pTextNode = rpNd->GetTextNode(); + if( pTextNode && pTextNode->GetpSwpHints() ) + { + SwIndex aSt( pTextNode, 0 ); + sal_Int32 nEnd = pTextNode->Len(); + + if( &pPara->pSttNd->nNode.GetNode() == pTextNode && + pPara->pSttNd->nContent.GetIndex() ) + aSt = pPara->pSttNd->nContent.GetIndex(); + + if( &pPara->pEndNd->nNode.GetNode() == rpNd ) + nEnd = pPara->pEndNd->nContent.GetIndex(); + + if( pPara->pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNode, pPara->pHistory ); + pTextNode->GetpSwpHints()->Register( &aRHst ); + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + if( pTextNode->GetpSwpHints() ) + pTextNode->GetpSwpHints()->DeRegister(); + } + else + pTextNode->RstTextAttr( aSt, nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + } + return true; +} + +DocumentContentOperationsManager::~DocumentContentOperationsManager() +{ +} +//Private methods + +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl( SwPaM & rPam, const bool ) +{ + assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()); + + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + + if (*rPam.GetPoint() == *rPam.GetMark()) + { + return false; // do not add empty redlines + } + + std::vector<SwRangeRedline*> redlines; + { + auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam)); + if (pRedline->HasValidRange()) + { + redlines.push_back(pRedline.release()); + } + else // sigh ... why is such a selection even possible... + { // split it up so we get one SwUndoRedlineDelete per inserted RL + redlines = GetAllValidRanges(std::move(pRedline)); + } + } + + if (redlines.empty()) + { + return false; + } + + // tdf#54819 current redlining needs also modification of paragraph style and + // attributes added to the same grouped Undo + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + auto & rDMA(*m_rDoc.getIDocumentMarkAccess()); + std::vector<std::unique_ptr<SwUndo>> MarkUndos; + for (auto iter = rDMA.getAnnotationMarksBegin(); + iter != rDMA.getAnnotationMarksEnd(); ) + { + // tdf#111524 remove annotation marks that have their field + // characters deleted + SwPosition const& rEndPos((**iter).GetMarkEnd()); + if (*rPam.Start() < rEndPos && rEndPos <= *rPam.End()) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + MarkUndos.emplace_back(std::make_unique<SwUndoDeleteBookmark>(**iter)); + } + // iter is into annotation mark vector so must be dereferenced! + rDMA.deleteMark(&**iter); + // this invalidates iter, have to start over... + iter = rDMA.getAnnotationMarksBegin(); + } + else + { // marks are sorted by start + if (*rPam.End() < (**iter).GetMarkStart()) + { + break; + } + ++iter; + } + } + + // tdf#119019 accept tracked paragraph formatting to do not hide new deletions + if (*rPam.GetPoint() != *rPam.GetMark()) + m_rDoc.getIDocumentRedlineAccess().AcceptRedlineParagraphFormatting(rPam); + + std::vector<std::unique_ptr<SwUndoRedlineDelete>> undos; + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + // (randomTest and testTdf54819 triggers it) + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in DeleteAndJoin"); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + for (SwRangeRedline * pRedline : redlines) + { + assert(pRedline->HasValidRange()); + undos.emplace_back(std::make_unique<SwUndoRedlineDelete>( + *pRedline, SwUndoId::DELETE)); + } + const SwRewriter aRewriter = undos.front()->GetRewriter(); + // can only group a single undo action + if (MarkUndos.empty() && undos.size() == 1 + && m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo * const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoRedlineDelete *const pUndoRedlineDel(dynamic_cast<SwUndoRedlineDelete*>(pLastUndo)); + bool const bMerged = pUndoRedlineDel + && pUndoRedlineDel->CanGrouping(*undos.front()); + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(undos.front())); + } + undos.clear(); // prevent unmatched EndUndo + } + else + { + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + for (auto& it : MarkUndos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + for (auto & it : undos) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(it)); + } + } + } + + for (SwRangeRedline *const pRedline : redlines) + { + // note: 1. the pRedline can still be merged & deleted + // 2. the impl. can even DeleteAndJoin the range => no plain PaM + std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*pRedline->GetMark())); + pCursor->SetMark(); + *pCursor->GetPoint() = *pRedline->GetPoint(); + m_rDoc.getIDocumentRedlineAccess().AppendRedline(pRedline, true); + // sw_redlinehide: 2 reasons why this is needed: + // 1. it's the first redline in node => RedlineDelText was sent but ignored + // 2. redline spans multiple nodes => must merge text frames + sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor); + } + m_rDoc.getIDocumentState().SetModified(); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + if (!undos.empty()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return true; +} + +bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, + const bool bForceJoinNext ) +{ + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + // #i100466# + if ( bForceJoinNext ) + { + bJoinPrev = false; + } + + { + bool const bSuccess( DeleteRangeImpl( rPam ) ); + if (!bSuccess) + return false; + } + + if( bJoinText ) + { + ::sw_JoinText( rPam, bJoinPrev ); + } + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } + + return true; +} + +bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, const bool) +{ + // Move all cursors out of the deleted range, but first copy the + // passed PaM, because it could be a cursor that would be moved! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + bool const bSuccess( DeleteRangeImplImpl( aDelPam ) ); + if (bSuccess) + { // now copy position from temp copy to given PaM + *rPam.GetPoint() = *aDelPam.GetPoint(); + } + + return bSuccess; +} + +bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) +{ + SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + if (!rPam.HasMark() + || (*pStt == *pEnd && !IsFlySelectedByCursor(m_rDoc, *pStt, *pEnd))) + { + return false; + } + + if( m_rDoc.GetAutoCorrExceptWord() ) + { + // if necessary the saved Word for the exception + if( m_rDoc.GetAutoCorrExceptWord()->IsDeleted() || pStt->nNode != pEnd->nNode || + pStt->nContent.GetIndex() + 1 != pEnd->nContent.GetIndex() || + !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt )) + { m_rDoc.DeleteAutoCorrExceptWord(); } + } + + { + // Delete all empty TextHints at the Mark's position + SwTextNode* pTextNd = rPam.GetMark()->nNode.GetNode().GetTextNode(); + SwpHints* pHts; + if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() ) + { + const sal_Int32 nMkCntPos = rPam.GetMark()->nContent.GetIndex(); + for( size_t n = pHts->Count(); n; ) + { + const SwTextAttr* pAttr = pHts->Get( --n ); + if( nMkCntPos > pAttr->GetStart() ) + break; + + const sal_Int32 *pEndIdx; + if( nMkCntPos == pAttr->GetStart() && + nullptr != (pEndIdx = pAttr->End()) && + *pEndIdx == pAttr->GetStart() ) + pTextNd->DestroyAttr( pHts->Cut( n ) ); + } + } + } + + { + // Send DataChanged before deletion, so that we still know + // which objects are in the range. + // Afterwards they could be before/after the Position. + SwDataChanged aTmp( rPam ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + bool bMerged(false); + if (m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + SwUndo *const pLastUndo( m_rDoc.GetUndoManager().GetLastUndo() ); + SwUndoDelete *const pUndoDelete( + dynamic_cast<SwUndoDelete *>(pLastUndo) ); + if (pUndoDelete) + { + bMerged = pUndoDelete->CanGrouping( &m_rDoc, rPam ); + // if CanGrouping() returns true it's already merged + } + } + if (!bMerged) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelete>( rPam ) ); + } + + m_rDoc.getIDocumentState().SetModified(); + + return true; + } + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); + + // Delete and move all "Flys at the paragraph", which are within the Selection + DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, + &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); + DelBookmarks( + pStt->nNode, + pEnd->nNode, + nullptr, + &pStt->nContent, + &pEnd->nContent); + + SwNodeIndex aSttIdx( pStt->nNode ); + SwContentNode * pCNd = aSttIdx.GetNode().GetContentNode(); + + do { // middle checked loop! + if( pCNd ) + { + SwTextNode * pStartTextNode( pCNd->GetTextNode() ); + if ( pStartTextNode ) + { + // now move the Content to the new Node + bool bOneNd = pStt->nNode == pEnd->nNode; + const sal_Int32 nLen = ( bOneNd ? pEnd->nContent.GetIndex() + : pCNd->Len() ) + - pStt->nContent.GetIndex(); + + // Don't call again, if already empty + if( nLen ) + { + pStartTextNode->EraseText( pStt->nContent, nLen ); + + if( !pStartTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pStartTextNode->RemoveMetadataReference(); + } + } + + if( bOneNd ) // that's it + break; + + ++aSttIdx; + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pStt->nContent.Assign( nullptr, 0 ); + } + } + + pCNd = pEnd->nNode.GetNode().GetContentNode(); + if( pCNd ) + { + SwTextNode * pEndTextNode( pCNd->GetTextNode() ); + if( pEndTextNode ) + { + // if already empty, don't call again + if( pEnd->nContent.GetIndex() ) + { + SwIndex aIdx( pCNd, 0 ); + pEndTextNode->EraseText( aIdx, pEnd->nContent.GetIndex() ); + + if( !pEndTextNode->Len() ) + { + // METADATA: remove reference if empty (consider node deleted) + pEndTextNode->RemoveMetadataReference(); + } + } + } + else + { + // So that there are no indices left registered when deleted, + // we remove a SwPaM from the Content here. + pEnd->nContent.Assign( nullptr, 0 ); + } + } + + // if the end is not a content node, delete it as well + sal_uInt32 nEnd = pEnd->nNode.GetIndex(); + if( pCNd == nullptr ) + nEnd++; + + if( aSttIdx != nEnd ) + { + // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete + SwNode *pTmpNd; + while (pEnd == rPam.GetPoint() + && nEnd + 2 < m_rDoc.GetNodes().Count() + && (pTmpNd = m_rDoc.GetNodes()[nEnd + 1])->IsEndNode() + && pTmpNd->StartOfSectionNode()->IsSectionNode() + && aSttIdx <= pTmpNd->StartOfSectionNode()->GetIndex()) + { + SwNodeRange range(*pTmpNd->StartOfSectionNode(), *pTmpNd); + m_rDoc.GetNodes().SectionUp(&range); + --nEnd; // account for deleted start node + } + + // delete the Nodes from the NodesArary + m_rDoc.GetNodes().Delete( aSttIdx, nEnd - aSttIdx.GetIndex() ); + } + + // If the Node that contained the Cursor has been deleted, + // the Content has to be assigned to the current Content. + pStt->nContent.Assign( pStt->nNode.GetNode().GetContentNode(), + pStt->nContent.GetIndex() ); + + // If we deleted across Node boundaries we have to correct the PaM, + // because they are in different Nodes now. + // Also, the Selection is revoked. + *pEnd = *pStt; + rPam.DeleteMark(); + + } while( false ); + + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +// It's possible to call Replace with a PaM that spans 2 paragraphs: +// search with regex for "$", then replace _all_ +bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUString& rStr, + const bool bRegExReplace ) +{ + if (!rPam.HasMark()) + return false; + + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + + { + // Create a copy of the Cursor in order to move all Pams from + // the other views out of the deletion range. + // Except for itself! + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); + + SwPosition *pStt = aDelPam.Start(), + *pEnd = aDelPam.End(); + bool bOneNode = pStt->nNode == pEnd->nNode; + + // Own Undo? + OUString sRepl( rStr ); + SwTextNode* pTextNd = pStt->nNode.GetNode().GetTextNode(); + sal_Int32 nStt = pStt->nContent.GetIndex(); + sal_Int32 nEnd; + + SwDataChanged aTmp( aDelPam ); + + if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineFlags eOld = m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in ReplaceRange"); + + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete ); + + *aDelPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *aDelPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + pStt = aDelPam.Start(); + pTextNd = pStt->nNode.GetNode().GetTextNode(); + nStt = pStt->nContent.GetIndex(); + } + + if( !sRepl.isEmpty() ) + { + // Apply the first character's attributes to the ReplaceText + SfxItemSet aSet( m_rDoc.GetAttrPool(), + svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1>{} ); + pTextNd->GetParaAttr( aSet, nStt+1, nStt+1 ); + + aSet.ClearItem( RES_TXTATR_REFMARK ); + aSet.ClearItem( RES_TXTATR_TOXMARK ); + aSet.ClearItem( RES_TXTATR_CJK_RUBY ); + aSet.ClearItem( RES_TXTATR_INETFMT ); + aSet.ClearItem( RES_TXTATR_META ); + aSet.ClearItem( RES_TXTATR_METAFIELD ); + + if( aDelPam.GetPoint() != aDelPam.End() ) + aDelPam.Exchange(); + + // Remember the End + SwNodeIndex aPtNd( aDelPam.GetPoint()->nNode, -1 ); + const sal_Int32 nPtCnt = aDelPam.GetPoint()->nContent.GetIndex(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + InsertString( aDelPam, sIns ); + if( bFirst ) + { + SwNodeIndex aMkNd( aDelPam.GetMark()->nNode, -1 ); + const sal_Int32 nMkCnt = aDelPam.GetMark()->nContent.GetIndex(); + + SplitNode( *aDelPam.GetPoint(), false ); + + ++aMkNd; + aDelPam.GetMark()->nNode = aMkNd; + aDelPam.GetMark()->nContent.Assign( + aMkNd.GetNode().GetContentNode(), nMkCnt ); + bFirst = false; + } + else + SplitNode( *aDelPam.GetPoint(), false ); + } + if( !sIns.isEmpty() ) + { + InsertString( aDelPam, sIns ); + } + + SwPaM aTmpRange( *aDelPam.GetPoint() ); + aTmpRange.SetMark(); + + ++aPtNd; + aDelPam.GetPoint()->nNode = aPtNd; + aDelPam.GetPoint()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt); + *aTmpRange.GetMark() = *aDelPam.GetPoint(); + + m_rDoc.RstTextAttrs( aTmpRange ); + InsertItemSet( aTmpRange, aSet ); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE )); + } + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true); + + *rPam.GetMark() = *aDelPam.GetMark(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + *aDelPam.GetPoint() = *rPam.GetPoint(); + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + // If any Redline will change (split!) the node + const ::sw::mark::IMark* pBkmk = + m_rDoc.getIDocumentMarkAccess()->makeMark( aDelPam, + OUString(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, + ::sw::mark::InsertMode::New); + + SwIndex& rIdx = aDelPam.GetPoint()->nContent; + rIdx.Assign( nullptr, 0 ); + aDelPam.GetMark()->nContent = rIdx; + rPam.GetPoint()->nNode = 0; + rPam.GetPoint()->nContent = rIdx; + *rPam.GetMark() = *rPam.GetPoint(); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + + *rPam.GetPoint() = pBkmk->GetMarkPos(); + if(pBkmk->IsExpanded()) + *rPam.GetMark() = pBkmk->GetOtherMarkPos(); + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + } + bJoinText = false; + } + else + { + assert((pStt->nNode == pEnd->nNode || + ( pStt->nNode.GetIndex() + 1 == pEnd->nNode.GetIndex() && + !pEnd->nContent.GetIndex() )) && + "invalid range: Point and Mark on different nodes" ); + + if( !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( aDelPam, true, RedlineType::Any ); + + SwUndoReplace* pUndoRpl = nullptr; + bool const bDoesUndo = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + if (bDoesUndo) + { + pUndoRpl = new SwUndoReplace(aDelPam, sRepl, bRegExReplace); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoRpl)); + } + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + + if( aDelPam.GetPoint() != pStt ) + aDelPam.Exchange(); + + SwNodeIndex aPtNd( pStt->nNode, -1 ); + const sal_Int32 nPtCnt = pStt->nContent.GetIndex(); + + // Set the values again, if Frames or footnotes on the Text have been removed. + nStt = nPtCnt; + nEnd = bOneNode ? pEnd->nContent.GetIndex() + : pTextNd->GetText().getLength(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + SplitNode( *pStt, false); + bFirst = false; + } + + if( bFirst || !sIns.isEmpty() ) + { + if (!bFirst || nStt == pTextNd->GetText().getLength()) + { + InsertString( aDelPam, sIns ); + } + else if( nStt < nEnd || !sIns.isEmpty() ) + { + pTextNd->ReplaceText( pStt->nContent, nEnd - nStt, sIns ); + } + } + + *rPam.GetPoint() = *aDelPam.GetMark(); + ++aPtNd; + rPam.GetMark()->nNode = aPtNd; + rPam.GetMark()->nContent.Assign( aPtNd.GetNode().GetContentNode(), + nPtCnt ); + + if (bJoinText) + { + assert(rPam.GetPoint() == rPam.End()); + // move so that SetEnd remembers position after sw_JoinText + rPam.Move(fnMoveBackward); + } + else if (aDelPam.GetPoint() == pStt) // backward selection? + { + assert(*rPam.GetMark() <= *rPam.GetPoint()); + rPam.Exchange(); // swap so that rPam is backwards + } + + if( pUndoRpl ) + { + pUndoRpl->SetEnd(rPam); + } + } + } + + bool bRet(true); + if (bJoinText) + { + bRet = ::sw_JoinText(rPam, bJoinPrev); + } + + m_rDoc.getIDocumentState().SetModified(); + return bRet; +} + +SwFlyFrameFormat* DocumentContentOperationsManager::InsNoTextNode( const SwPosition& rPos, SwNoTextNode* pNode, + const SfxItemSet* pFlyAttrSet, + const SfxItemSet* pGrfAttrSet, + SwFrameFormat* pFrameFormat) +{ + SwFlyFrameFormat *pFormat = nullptr; + if( pNode ) + { + pFormat = m_rDoc.MakeFlySection_( rPos, *pNode, RndStdIds::FLY_AT_PARA, + pFlyAttrSet, pFrameFormat ); + if( pGrfAttrSet ) + pNode->SetAttr( *pGrfAttrSet ); + } + return pFormat; +} + +#define NUMRULE_STATE \ + SfxItemState aNumRuleState = SfxItemState::UNKNOWN; \ + std::shared_ptr<SwNumRuleItem> aNumRuleItem; \ + SfxItemState aListIdState = SfxItemState::UNKNOWN; \ + std::shared_ptr<SfxStringItem> aListIdItem; \ + +#define PUSH_NUMRULE_STATE \ + lcl_PushNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd ); + +#define POP_NUMRULE_STATE \ + lcl_PopNumruleState( aNumRuleState, aNumRuleItem, aListIdState, aListIdItem, pDestTextNd, rPam ); + +static void lcl_PushNumruleState( + SfxItemState &aNumRuleState, std::shared_ptr<SwNumRuleItem>& aNumRuleItem, + SfxItemState &aListIdState, std::shared_ptr<SfxStringItem>& aListIdItem, + const SwTextNode *pDestTextNd ) +{ + // Safe numrule item at destination. + // #i86492# - Safe also <ListId> item of destination. + const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet(); + if (pAttrSet != nullptr) + { + const SfxPoolItem * pItem = nullptr; + aNumRuleState = pAttrSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem); + if (SfxItemState::SET == aNumRuleState) + { + aNumRuleItem.reset(static_cast<SwNumRuleItem*>(pItem->Clone())); + } + + aListIdState = pAttrSet->GetItemState(RES_PARATR_LIST_ID, false, &pItem); + if (SfxItemState::SET == aListIdState) + { + aListIdItem.reset(static_cast<SfxStringItem*>(pItem->Clone())); + } + } +} + +static void lcl_PopNumruleState( + SfxItemState aNumRuleState, const std::shared_ptr<SwNumRuleItem>& aNumRuleItem, + SfxItemState aListIdState, const std::shared_ptr<SfxStringItem>& aListIdItem, + SwTextNode *pDestTextNd, const SwPaM& rPam ) +{ + /* If only a part of one paragraph is copied + restore the numrule at the destination. */ + // #i86492# - restore also <ListId> item + if ( !lcl_MarksWholeNode(rPam) ) + { + if (SfxItemState::SET == aNumRuleState) + { + pDestTextNd->SetAttr(*aNumRuleItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_NUMRULE); + } + if (SfxItemState::SET == aListIdState) + { + pDestTextNd->SetAttr(*aListIdItem); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_LIST_ID); + } + } +} + +bool DocumentContentOperationsManager::CopyImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCopyRange) const +{ + std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; + + sw::CalcBreaks(Breaks, rPam, true); + + if (Breaks.empty()) + { + return CopyImplImpl(rPam, rPos, flags, pCopyRange); + } + + SwPosition const & rSelectionEnd( *rPam.End() ); + + bool bRet(true); + bool bFirst(true); + // iterate from end to start, ... don't think it's necessary here? + auto iter( Breaks.rbegin() ); + sal_uLong nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->nNode.GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + SwPaM copyRange(rPos, rPos); + + while (iter != Breaks.rend()) + { + rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + // pass in copyRange member as rPos; should work ... + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + nOffset = iter->first - rStart.nNode.GetIndex(); // fly nodes... + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + bFirst = false; + } + rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); + ++iter; + } + + rStart = *rPam.Start(); // set to original start + if (rStart < rEnd) // check if part is empty + { + bRet &= CopyImplImpl(aPam, *copyRange.Start(), flags & ~SwCopyFlags::IsMoveToFly, ©Range); + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + } + + return bRet; +} + +bool DocumentContentOperationsManager::CopyImplImpl(SwPaM& rPam, SwPosition& rPos, + SwCopyFlags const flags, + SwPaM *const pCpyRange) const +{ + SwDoc* pDoc = rPos.nNode.GetNode().GetDoc(); + const bool bColumnSel = pDoc->IsClipBoard() && pDoc->IsColumnSelection(); + + SwPosition const*const pStt = rPam.Start(); + SwPosition *const pEnd = rPam.End(); + + // Catch when there's no copy to do. + if (!rPam.HasMark() || (IsEmptyRange(*pStt, *pEnd, flags) && !bColumnSel) || + //JP 29.6.2001: 88963 - don't copy if inspos is in region of start to end + //JP 15.11.2001: don't test inclusive the end, ever exclusive + ( pDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd )) + { + return false; + } + + const bool bEndEqualIns = pDoc == &m_rDoc && rPos == *pEnd; + + // If Undo is enabled, create the UndoCopy object + SwUndoCpyDoc* pUndo = nullptr; + // lcl_DeleteRedlines may delete the start or end node of the cursor when + // removing the redlines so use cursor that is corrected by PaMCorrAbs + std::shared_ptr<SwUnoCursor> const pCopyPam(pDoc->CreateUnoCursor(rPos)); + + SwTableNumFormatMerge aTNFM( m_rDoc, *pDoc ); + std::unique_ptr<std::vector<SwFrameFormat*>> pFlys; + std::vector<SwFrameFormat*> const* pFlysAtInsPos; + + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoCpyDoc(*pCopyPam); + pDoc->GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + pFlysAtInsPos = pUndo->GetFlysAnchoredAt(); + } + else + { + pFlys = sw::GetFlysAnchoredAt(*pDoc, rPos.nNode.GetIndex()); + pFlysAtInsPos = pFlys.get(); + } + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + // Move the PaM one node back from the insert position, so that + // the position doesn't get moved + pCopyPam->SetMark(); + bool bCanMoveBack = pCopyPam->Move(fnMoveBackward, GoInContent); + // If the position was shifted from more than one node, an end node has been skipped + bool bAfterTable = false; + if ((rPos.nNode.GetIndex() - pCopyPam->GetPoint()->nNode.GetIndex()) > 1) + { + // First go back to the original place + pCopyPam->GetPoint()->nNode = rPos.nNode; + pCopyPam->GetPoint()->nContent = rPos.nContent; + + bCanMoveBack = false; + bAfterTable = true; + } + if( !bCanMoveBack ) + { + pCopyPam->GetPoint()->nNode--; + assert(pCopyPam->GetPoint()->nContent.GetIndex() == 0); + } + + SwNodeRange aRg( pStt->nNode, pEnd->nNode ); + SwNodeIndex aInsPos( rPos.nNode ); + const bool bOneNode = pStt->nNode == pEnd->nNode; + SwTextNode* pSttTextNd = pStt->nNode.GetNode().GetTextNode(); + SwTextNode* pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode(); + bool bCopyCollFormat = !pDoc->IsInsOnlyTextGlossary() && + ( (pDestTextNd && !pDestTextNd->GetText().getLength()) || + ( !bOneNode && !rPos.nContent.GetIndex() ) ); + bool bCopyBookmarks = true; + bool bCopyPageSource = false; + int nDeleteTextNodes = 0; + + // #i104585# copy outline num rule to clipboard (for ASCII filter) + if (pDoc->IsClipBoard() && m_rDoc.GetOutlineNumRule()) + { + pDoc->SetOutlineNumRule(*m_rDoc.GetOutlineNumRule()); + } + + // #i86492# + // Correct the search for a previous list: + // First search for non-outline numbering list. Then search for non-outline + // bullet list. + // Keep also the <ListId> value for possible propagation. + OUString aListIdToPropagate; + const SwNumRule* pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true ); + if ( !pNumRuleToPropagate ) + { + pNumRuleToPropagate = + pDoc->SearchNumRule( rPos, false, false, false, 0, aListIdToPropagate, nullptr, true ); + } + // #i86492# + // Do not propagate previous found list, if + // - destination is an empty paragraph which is not in a list and + // - source contains at least one paragraph which is not in a list + if ( pNumRuleToPropagate && + pDestTextNd && !pDestTextNd->GetText().getLength() && + !pDestTextNd->IsInList() && + !lcl_ContainsOnlyParagraphsInList( rPam ) ) + { + pNumRuleToPropagate = nullptr; + } + + // This do/while block is only there so that we can break out of it! + do { + if( pSttTextNd ) + { + ++nDeleteTextNodes; // must be joined in Undo + // Don't copy the beginning completely? + if( !bCopyCollFormat || bColumnSel || pStt->nContent.GetIndex() ) + { + SwIndex aDestIdx( rPos.nContent ); + bool bCopyOk = false; + if( !pDestTextNd ) + { + if( pStt->nContent.GetIndex() || bOneNode ) + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + else + { + pDestTextNd = pSttTextNd->MakeCopy(pDoc, aInsPos, true)->GetTextNode(); + bCopyOk = true; + } + aDestIdx.Assign( pDestTextNd, 0 ); + bCopyCollFormat = true; + } + else if( !bOneNode || bColumnSel ) + { + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + pDestTextNd = pDoc->GetNodes()[ aInsPos.GetIndex()-1 ]->GetTextNode(); + aDestIdx.Assign( + pDestTextNd, pDestTextNd->GetText().getLength()); + + // Correct the area again + if( bEndEqualIns ) + { + bool bChg = pEnd != rPam.GetPoint(); + if( bChg ) + rPam.Exchange(); + rPam.Move( fnMoveBackward, GoInContent ); + if( bChg ) + rPam.Exchange(); + } + else if( rPos == *pEnd ) + { + // The end was also moved + pEnd->nNode--; + pEnd->nContent.Assign( pDestTextNd, nContentEnd ); + } + // tdf#63022 always reset pEndTextNd after SplitNode + aRg.aEnd = pEnd->nNode; + pEndTextNd = pEnd->nNode.GetNode().GetTextNode(); + } + + NUMRULE_STATE + if( bCopyCollFormat && bOneNode ) + { + PUSH_NUMRULE_STATE + } + + if( !bCopyOk ) + { + const sal_Int32 nCpyLen = ( bOneNode + ? pEnd->nContent.GetIndex() + : pSttTextNd->GetText().getLength()) + - pStt->nContent.GetIndex(); + pSttTextNd->CopyText( pDestTextNd, aDestIdx, + pStt->nContent, nCpyLen ); + if( bEndEqualIns ) + pEnd->nContent -= nCpyLen; + } + + aRg.aStart++; + + if( bOneNode ) + { + if (bCopyCollFormat) + { + pSttTextNd->CopyCollFormat( *pDestTextNd ); + POP_NUMRULE_STATE + } + + // copy at-char flys in rPam + SwNodeIndex temp(*pDestTextNd); // update to new (start) node for flys + // tdf#126626 prevent duplicate Undos + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(aRg, &rPam, temp, false); + + break; + } + } + } + else if( pDestTextNd ) + { + // Problems with insertion of table selections into "normal" text solved. + // We have to set the correct PaM for Undo, if this PaM starts in a textnode, + // the undo operation will try to merge this node after removing the table. + // If we didn't split a textnode, the PaM should start at the inserted table node + if( rPos.nContent.GetIndex() == pDestTextNd->Len() ) + { // Insertion at the last position of a textnode (empty or not) + ++aInsPos; // The table will be inserted behind the text node + } + else if( rPos.nContent.GetIndex() ) + { // Insertion in the middle of a text node, it has to be split + // (and joined from undo) + ++nDeleteTextNodes; + + const sal_Int32 nContentEnd = pEnd->nContent.GetIndex(); + { + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->getIDocumentContentOperations().SplitNode( rPos, false ); + } + + if (bCanMoveBack && rPos == *pCopyPam->GetPoint()) + { + // after the SplitNode, span the CpyPam correctly again + pCopyPam->Move( fnMoveBackward, GoInContent ); + pCopyPam->Move( fnMoveBackward, GoInContent ); + } + + // Correct the area again + if( bEndEqualIns ) + aRg.aEnd--; + // The end would also be moved + else if( rPos == *pEnd ) + { + rPos.nNode-=2; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + nContentEnd ); + rPos.nNode++; + aRg.aEnd--; + } + } + else if( bCanMoveBack ) + { // Insertion at the first position of a text node. It will not be split, the table + // will be inserted before the text node. + // See below, before the SetInsertRange function of the undo object will be called, + // the CpyPam would be moved to the next content position. This has to be avoided + // We want to be moved to the table node itself thus we have to set bCanMoveBack + // and to manipulate pCopyPam. + bCanMoveBack = false; + pCopyPam->GetPoint()->nNode--; + } + } + + pDestTextNd = aInsPos.GetNode().GetTextNode(); + if (pEndTextNd) + { + SwIndex aDestIdx( rPos.nContent ); + if( !pDestTextNd ) + { + pDestTextNd = pDoc->GetNodes().MakeTextNode( aInsPos, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + aDestIdx.Assign( pDestTextNd, 0 ); + aInsPos--; + + // if we have to insert an extra text node + // at the destination, this node will be our new destination + // (text) node, and thus we increment nDeleteTextNodes. This + // will ensure that this node will be deleted during Undo. + ++nDeleteTextNodes; // must be deleted + } + + const bool bEmptyDestNd = pDestTextNd->GetText().isEmpty(); + + NUMRULE_STATE + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + PUSH_NUMRULE_STATE + } + + pEndTextNd->CopyText( pDestTextNd, aDestIdx, SwIndex( pEndTextNd ), + pEnd->nContent.GetIndex() ); + + // Also copy all format templates + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + pEndTextNd->CopyCollFormat( *pDestTextNd ); + if ( bOneNode ) + { + POP_NUMRULE_STATE + } + } + } + + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + if (pSttTextNd && bCopyCollFormat && pDestTextNd->HasSwAttrSet()) + { + aBrkSet.Put( *pDestTextNd->GetpSwAttrSet() ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_BREAK, false ) ) + pDestTextNd->ResetAttr( RES_BREAK ); + if( SfxItemState::SET == aBrkSet.GetItemState( RES_PAGEDESC, false ) ) + pDestTextNd->ResetAttr( RES_PAGEDESC ); + } + } + + { + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos); + if( aInsPos == pEnd->nNode ) + { + SwNodeIndex aSaveIdx( aInsPos, -1 ); + assert(pStt->nNode != pEnd->nNode); + pEnd->nContent = 0; // TODO why this? + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + ++aSaveIdx; + pEnd->nNode = aSaveIdx; + pEnd->nContent.Assign( aSaveIdx.GetNode().GetTextNode(), 0 ); + } + else + CopyWithFlyInFly(aRg, aInsPos, &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + + bCopyBookmarks = false; + } + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + // ... also for at-para anchors; here start is preferable because + // it's consistent with SplitNode from SwUndoInserts::RedoImpl() + if (pFlysAtInsPos) + { + // init *again* - because CopyWithFlyInFly moved startPos + SwPosition startPos(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1), + SwIndex(SwNodeIndex(pCopyPam->GetPoint()->nNode, +1).GetNode().GetContentNode())); + if (bCanMoveBack) + { // pCopyPam is actually 1 before the copy range so move it fwd + SwPaM temp(*pCopyPam->GetPoint()); + temp.Move(fnMoveForward, GoInContent); + startPos = *temp.GetPoint(); + } + assert(startPos.nNode.GetNode().IsContentNode()); + SwPosition startPosAtPara(startPos); + startPosAtPara.nContent.Assign(nullptr, 0); + + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPos ); + pFly->SetFormatAttr(anchor); + } + else if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( &startPosAtPara ); + pFly->SetFormatAttr(anchor); + } + } + } + + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + // Put the breaks back into the first node + if( aBrkSet.Count() && nullptr != ( pDestTextNd = pDoc->GetNodes()[ + pCopyPam->GetPoint()->nNode.GetIndex()+1 ]->GetTextNode())) + { + pDestTextNd->SetAttr( aBrkSet ); + bCopyPageSource = true; + } + } + } while( false ); + + + // it is not possible to make this test when copy from the clipBoard to document + // in this case the PageNum not exist anymore + // tdf#39400 and tdf#97526 + // when copy from document to ClipBoard, and it is from the first page + // and not the source has the page break + if (pDoc->IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource) + { + pDestTextNd->ResetAttr(RES_BREAK); // remove the page-break + pDestTextNd->ResetAttr(RES_PAGEDESC); + } + + + // Adjust position (in case it was moved / in another node) + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + rPos.nContent.GetIndex() ); + + if( rPos.nNode != aInsPos ) + { + pCopyPam->GetMark()->nNode = aInsPos; + if (aInsPos < rPos.nNode) + { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above + pCopyPam->GetContentNode(false)->MakeEndIndex(&pCopyPam->GetMark()->nContent); + } + else // incremented in (!pSttTextNd && pDestTextNd) above + { + pCopyPam->GetMark()->nContent.Assign(pCopyPam->GetContentNode(false), 0); + } + rPos = *pCopyPam->GetMark(); + } + else + *pCopyPam->GetMark() = rPos; + + if ( !bAfterTable ) + pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode ); + else + { + pCopyPam->GetPoint()->nNode++; + + // Reset the offset to 0 as it was before the insertion + pCopyPam->GetPoint()->nContent.Assign(pCopyPam->GetPoint()->nNode.GetNode().GetContentNode(), 0); + // If the next node is a start node, then step back: the start node + // has been copied and needs to be in the selection for the undo + if (pCopyPam->GetPoint()->nNode.GetNode().IsStartNode()) + pCopyPam->GetPoint()->nNode--; + + } + pCopyPam->Exchange(); + + // Also copy all bookmarks + if( bCopyBookmarks && m_rDoc.getIDocumentMarkAccess()->getAllMarksCount() ) + { + sw::CopyBookmarks(rPam, *pCopyPam->Start()); + } + + if( RedlineFlags::DeleteRedlines & eOld ) + { + assert(*pCopyPam->GetPoint() == rPos); + // the Node rPos points to may be deleted so unregister ... + rPos.nContent.Assign(nullptr, 0); + lcl_DeleteRedlines(rPam, *pCopyPam); + rPos = *pCopyPam->GetPoint(); // ... and restore. + } + + // If Undo is enabled, store the inserted area + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + pUndo->SetInsertRange(*pCopyPam, true, nDeleteTextNodes); + } + + if( pCpyRange ) + { + pCpyRange->SetMark(); + *pCpyRange->GetPoint() = *pCopyPam->GetPoint(); + *pCpyRange->GetMark() = *pCopyPam->GetMark(); + } + + if ( pNumRuleToPropagate != nullptr ) + { + // #i86492# - use <SwDoc::SetNumRule(..)>, because it also handles the <ListId> + // Don't reset indent attributes, that would mean loss of direct + // formatting. + pDoc->SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr, + aListIdToPropagate, true, /*bResetIndentAttrs=*/false ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + pDoc->getIDocumentState().SetModified(); + + return true; +} + + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentDeviceManager.cxx b/sw/source/core/doc/DocumentDeviceManager.cxx new file mode 100644 index 000000000..18da12493 --- /dev/null +++ b/sw/source/core/doc/DocumentDeviceManager.cxx @@ -0,0 +1,376 @@ +/* -*- 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 <DocumentDeviceManager.hxx> + +#include <memory> +#include <utility> + +#include <doc.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <sfx2/printer.hxx> +#include <vcl/virdev.hxx> +#include <vcl/outdev.hxx> +#include <vcl/jobset.hxx> +#include <printdata.hxx> +#include <vcl/mapmod.hxx> +#include <svl/itemset.hxx> +#include <cfgitems.hxx> +#include <cmdid.h> +#include <drawdoc.hxx> +#include <wdocsh.hxx> +#include <prtopt.hxx> +#include <viewsh.hxx> +#include <rootfrm.hxx> +#include <viewopt.hxx> +#include <swwait.hxx> +#include <fntcache.hxx> + +class SwDocShell; +class SwWait; + +namespace sw { + +DocumentDeviceManager::DocumentDeviceManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), mpPrt(nullptr), mpVirDev(nullptr) {} + +SfxPrinter* DocumentDeviceManager::getPrinter(/*[in]*/ bool bCreate ) const +{ + SfxPrinter* pRet = nullptr; + if ( !bCreate || mpPrt ) + pRet = mpPrt; + else + pRet = &CreatePrinter_(); + + return pRet; +} + +void DocumentDeviceManager::setPrinter(/*[in]*/ SfxPrinter *pP,/*[in]*/ bool bDeleteOld,/*[in]*/ bool bCallPrtDataChanged ) +{ + assert ( !pP || !pP->isDisposed() ); + if ( pP != mpPrt ) + { + if ( bDeleteOld ) + mpPrt.disposeAndClear(); + mpPrt = pP; + + // our printer should always use TWIP. Don't rely on this being set in SwViewShell::InitPrt, there + // are situations where this isn't called. #i108712# + if ( mpPrt ) + { + MapMode aMapMode( mpPrt->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + mpPrt->SetMapMode( aMapMode ); + } + + if ( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && !m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::USE_VIRTUAL_DEVICE ) ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( mpPrt ); + } + + if ( bCallPrtDataChanged && + // #i41075# Do not call PrtDataChanged() if we do not + // use the printer for formatting: + !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + PrtDataChanged(); +} + +VirtualDevice* DocumentDeviceManager::getVirtualDevice(/*[in]*/ bool bCreate ) const +{ + VirtualDevice* pRet = nullptr; + if ( !bCreate || mpVirDev ) + pRet = mpVirDev; + else + pRet = &CreateVirtualDevice_(); + + assert ( !pRet || !pRet->isDisposed() ); + + return pRet; +} + +void DocumentDeviceManager::setVirtualDevice(/*[in]*/ VirtualDevice* pVd ) +{ + assert ( !pVd->isDisposed() ); + + if ( mpVirDev.get() != pVd ) + { + mpVirDev.disposeAndClear(); + mpVirDev = pVd; + + if ( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::USE_VIRTUAL_DEVICE ) ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( mpVirDev ); + } +} + +OutputDevice* DocumentDeviceManager::getReferenceDevice(/*[in]*/ bool bCreate ) const +{ + OutputDevice* pRet = nullptr; + if ( !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + { + pRet = getPrinter( bCreate ); + + if ( bCreate && !mpPrt->IsValid() ) + { + pRet = getVirtualDevice( true ); + } + } + else + { + pRet = getVirtualDevice( bCreate ); + } + + assert ( !pRet || !pRet->isDisposed() ); + + return pRet; +} + +void DocumentDeviceManager::setReferenceDeviceType(/*[in]*/ bool bNewVirtual, /*[in]*/ bool bNewHiRes ) +{ + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) != bNewVirtual || + m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE) != bNewHiRes ) + { + if ( bNewVirtual ) + { + VirtualDevice* pMyVirDev = getVirtualDevice( true ); + if ( !bNewHiRes ) + pMyVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::Dpi600 ); + else + pMyVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pMyVirDev ); + } + else + { + // #i41075# + // We have to take care that a printer exists before calling + // PrtDataChanged() in order to prevent that PrtDataChanged() + // triggers this funny situation: + // getReferenceDevice()->getPrinter()->CreatePrinter_() + // ->setPrinter()-> PrtDataChanged() + SfxPrinter* pPrinter = getPrinter( true ); + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pPrinter ); + } + + m_rDoc.GetDocumentSettingManager().set(DocumentSettingId::USE_VIRTUAL_DEVICE, bNewVirtual ); + m_rDoc.GetDocumentSettingManager().set(DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE, bNewHiRes ); + PrtDataChanged(); + m_rDoc.getIDocumentState().SetModified(); + } +} + +const JobSetup* DocumentDeviceManager::getJobsetup() const +{ + return mpPrt ? &mpPrt->GetJobSetup() : nullptr; +} + +void DocumentDeviceManager::setJobsetup(/*[in]*/ const JobSetup &rJobSetup ) +{ + bool bCheckPageDescs = !mpPrt; + bool bDataChanged = false; + + if ( mpPrt ) + { + if ( mpPrt->GetName() == rJobSetup.GetPrinterName() ) + { + if ( mpPrt->GetJobSetup() != rJobSetup ) + { + mpPrt->SetJobSetup( rJobSetup ); + bDataChanged = true; + } + } + else + mpPrt.disposeAndClear(); + } + + if( !mpPrt ) + { + //The ItemSet is deleted by Sfx! + auto pSet = std::make_unique<SfxItemSet>( + m_rDoc.GetAttrPool(), + svl::Items< + SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN, + SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC, + SID_HTML_MODE, SID_HTML_MODE, + FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>{}); + VclPtr<SfxPrinter> p = VclPtr<SfxPrinter>::Create( std::move(pSet), rJobSetup ); + if ( bCheckPageDescs ) + setPrinter( p, true, true ); + else + { + mpPrt = p; + bDataChanged = true; + } + } + if ( bDataChanged && !m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::USE_VIRTUAL_DEVICE) ) + PrtDataChanged(); +} + +const SwPrintData & DocumentDeviceManager::getPrintData() const +{ + if(!mpPrtData) + { + DocumentDeviceManager * pThis = const_cast< DocumentDeviceManager * >(this); + pThis->mpPrtData.reset(new SwPrintData); + + // SwPrintData should be initialized from the configuration, + // the respective config item is implemented by SwPrintOptions which + // is also derived from SwPrintData + const SwDocShell *pDocSh = m_rDoc.GetDocShell(); + OSL_ENSURE( pDocSh, "pDocSh is 0, can't determine if this is a WebDoc or not" ); + bool bWeb = dynamic_cast< const SwWebDocShell * >(pDocSh) != nullptr; + SwPrintOptions aPrintOptions( bWeb ); + *pThis->mpPrtData = aPrintOptions; + } + return *mpPrtData; +} + +void DocumentDeviceManager::setPrintData(/*[in]*/ const SwPrintData& rPrtData ) +{ + if(!mpPrtData) + mpPrtData.reset(new SwPrintData); + *mpPrtData = rPrtData; +} + +DocumentDeviceManager::~DocumentDeviceManager() +{ + mpPrtData.reset(); + mpVirDev.disposeAndClear(); + mpPrt.disposeAndClear(); +} + +VirtualDevice& DocumentDeviceManager::CreateVirtualDevice_() const +{ +#ifdef IOS + VclPtr<VirtualDevice> pNewVir = VclPtr<VirtualDevice>::Create(DeviceFormat::GRAYSCALE); +#else + VclPtr<VirtualDevice> pNewVir = VclPtr<VirtualDevice>::Create(DeviceFormat::BITMASK); +#endif + + pNewVir->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + + // #i60945# External leading compatibility for unix systems. + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING ) ) + pNewVir->Compat_ZeroExtleadBug(); + + MapMode aMapMode( pNewVir->GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + pNewVir->SetMapMode( aMapMode ); + + const_cast<DocumentDeviceManager*>(this)->setVirtualDevice( pNewVir ); + return *mpVirDev; +} + +SfxPrinter& DocumentDeviceManager::CreatePrinter_() const +{ + OSL_ENSURE( ! mpPrt, "Do not call CreatePrinter_(), call getPrinter() instead" ); + +#if OSL_DEBUG_LEVEL > 1 + SAL_INFO("sw", "Printer will be created!" ); +#endif + + // We create a default SfxPrinter. + // The ItemSet is deleted by Sfx! + auto pSet = std::make_unique<SfxItemSet>( + m_rDoc.GetAttrPool(), + svl::Items< + SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN, + SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC, + SID_HTML_MODE, SID_HTML_MODE, + FN_PARAM_ADDPRINTER, FN_PARAM_ADDPRINTER>{}); + + VclPtr<SfxPrinter> pNewPrt = VclPtr<SfxPrinter>::Create( std::move(pSet) ); + + // assign PrintData to newly created printer + const SwPrintData& rPrtData = getPrintData(); + SwAddPrinterItem aAddPrinterItem(rPrtData); + SfxItemSet aOptions(pNewPrt->GetOptions()); + aOptions.Put(aAddPrinterItem); + pNewPrt->SetOptions(aOptions); + + const_cast<DocumentDeviceManager*>(this)->setPrinter( pNewPrt, true, true ); + return *mpPrt; +} + +void DocumentDeviceManager::PrtDataChanged() +{ +// If you change this, also modify InJobSetup in Sw3io if appropriate. + + // #i41075# + OSL_ENSURE( m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::USE_VIRTUAL_DEVICE) || + nullptr != getPrinter( false ), "PrtDataChanged will be called recursively!" ); + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + std::unique_ptr<SwWait> pWait; + bool bEndAction = false; + + if( m_rDoc.GetDocShell() ) + m_rDoc.GetDocShell()->UpdateFontList(); + + bool bDraw = true; + if ( pTmpRoot ) + { + SwViewShell *pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh && + (!pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsPrtFormat()) ) + { + if ( m_rDoc.GetDocShell() ) + pWait.reset(new SwWait( *m_rDoc.GetDocShell(), true )); + + pTmpRoot->StartAllAction(); + bEndAction = true; + + bDraw = false; + if( m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + { + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetAddExtLeading( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::ADD_EXT_LEADING) ); + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( getReferenceDevice( false ) ); + } + + pFntCache->Flush(); + + for(SwRootFrame* aLayout : m_rDoc.GetAllLayouts()) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + + for(SwViewShell& rShell : pSh->GetRingContainer()) + rShell.InitPrt(getPrinter(false)); + } + } + if ( bDraw && m_rDoc.getIDocumentDrawModelAccess().GetDrawModel() ) + { + const bool bTmpAddExtLeading = m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::ADD_EXT_LEADING); + if ( bTmpAddExtLeading != m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->IsAddExtLeading() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetAddExtLeading( bTmpAddExtLeading ); + + OutputDevice* pOutDev = getReferenceDevice( false ); + if ( pOutDev != m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetRefDevice() ) + m_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->SetRefDevice( pOutDev ); + } + + m_rDoc.PrtOLENotify( true ); + + if ( bEndAction ) + pTmpRoot->EndAllAction(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentDrawModelManager.cxx b/sw/source/core/doc/DocumentDrawModelManager.cxx new file mode 100644 index 000000000..ef1aa1a29 --- /dev/null +++ b/sw/source/core/doc/DocumentDrawModelManager.cxx @@ -0,0 +1,356 @@ +/* -*- 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 <DocumentDrawModelManager.hxx> + +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docsh.hxx> +#include <wrtsh.hxx> +#include <swtypes.hxx> +#include <svl/hint.hxx> +#include <viewsh.hxx> +#include <view.hxx> +#include <drawdoc.hxx> +#include <rootfrm.hxx> +#include <fmtanchr.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <svx/svdlayer.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdview.hxx> +#include <svl/srchitem.hxx> +#include <unotools/configmgr.hxx> +#include <sal/log.hxx> + +class SdrOutliner; + +namespace sw +{ + +DocumentDrawModelManager::DocumentDrawModelManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , mnHeaven(0) + , mnHell(0) + , mnControls(0) + , mnInvisibleHeaven(0) + , mnInvisibleHell(0) + , mnInvisibleControls(0) +{ +} + +// Is also called by the Sw3 Reader, if there was an error when reading the +// drawing layer. If it is called by the Sw3 Reader the layer is rebuilt +// from scratch. +void DocumentDrawModelManager::InitDrawModel() +{ + // !! Attention: there is similar code in the Sw3 Reader (sw3imp.cxx) that + // also has to be maintained!! + if ( mpDrawModel ) + ReleaseDrawModel(); + + // set FontHeight pool defaults without changing static SdrEngineDefaults + m_rDoc.GetAttrPool().SetPoolDefaultItem(SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT )); + + SAL_INFO( "sw.doc", "before create DrawDocument" ); + // The document owns the SwDrawModel. We always have two layers and one page. + mpDrawModel.reset( new SwDrawModel( &m_rDoc ) ); + + mpDrawModel->EnableUndo( m_rDoc.GetIDocumentUndoRedo().DoesUndo() ); + + OUString sLayerNm; + sLayerNm = "Hell"; + mnHell = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "Heaven"; + mnHeaven = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "Controls"; + mnControls = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + mpDrawModel->GetLayerAdmin().SetControlLayerName(sLayerNm); + + // add invisible layers corresponding to the visible ones. + { + sLayerNm = "InvisibleHell"; + mnInvisibleHell = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "InvisibleHeaven"; + mnInvisibleHeaven = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + + sLayerNm = "InvisibleControls"; + mnInvisibleControls = mpDrawModel->GetLayerAdmin().NewLayer( sLayerNm )->GetID(); + } + + SdrPage* pMasterPage = mpDrawModel->AllocPage( false ); + mpDrawModel->InsertPage( pMasterPage ); + SAL_INFO( "sw.doc", "after create DrawDocument" ); + SdrOutliner& rOutliner = mpDrawModel->GetDrawOutliner(); + if (!utl::ConfigManager::IsFuzzing()) + { + SAL_INFO( "sw.doc", "before create Spellchecker/Hyphenator" ); + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpell = ::GetSpellChecker(); + rOutliner.SetSpeller( xSpell ); + css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator( ::GetHyphenator() ); + rOutliner.SetHyphenator( xHyphenator ); + SAL_INFO( "sw.doc", "after create Spellchecker/Hyphenator" ); + } + m_rDoc.SetCalcFieldValueHdl(&rOutliner); + m_rDoc.SetCalcFieldValueHdl(&mpDrawModel->GetHitTestOutliner()); + + // Set the LinkManager in the model so that linked graphics can be inserted. + // The WinWord import needs it too. + mpDrawModel->SetLinkManager( & m_rDoc.getIDocumentLinksAdministration().GetLinkManager() ); + mpDrawModel->SetAddExtLeading( m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_EXT_LEADING) ); + + OutputDevice* pRefDev = m_rDoc.getIDocumentDeviceAccess().getReferenceDevice( false ); + if ( pRefDev ) + mpDrawModel->SetRefDevice( pRefDev ); + + mpDrawModel->SetNotifyUndoActionHdl( std::bind( &SwDoc::AddDrawUndo, &m_rDoc, std::placeholders::_1 )); + SwViewShell* const pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(const SwViewShell& rViewSh : pSh->GetRingContainer()) + { + SwRootFrame* pRoot = rViewSh.GetLayout(); + if( pRoot && !pRoot->GetDrawPage() ) + { + // Disable "multiple layout" for the moment: + // use pMasterPage instead of a new created SdrPage + // mpDrawModel->AllocPage( FALSE ); + // mpDrawModel->InsertPage( pDrawPage ); + SdrPage* pDrawPage = pMasterPage; + pRoot->SetDrawPage( pDrawPage ); + pDrawPage->SetSize( pRoot->getFrameArea().SSize() ); + } + } + } +} + + +void DocumentDrawModelManager::ReleaseDrawModel() +{ + // !! Also maintain the code in the sw3io for inserting documents!! + mpDrawModel.reset(); +} + + +const SwDrawModel* DocumentDrawModelManager::GetDrawModel() const +{ + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::GetDrawModel() +{ + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::MakeDrawModel_() +{ + OSL_ENSURE( !mpDrawModel, "MakeDrawModel_: Why?" ); + InitDrawModel(); + SwViewShell* const pSh = m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(SwViewShell& rViewSh : pSh->GetRingContainer()) + rViewSh.MakeDrawView(); + + // Broadcast, so that the FormShell can be connected to the DrawView + if( m_rDoc.GetDocShell() ) + { + SfxHint aHint( SfxHintId::SwDrawViewsCreated ); + m_rDoc.GetDocShell()->Broadcast( aHint ); + } + } + return mpDrawModel.get(); +} + +SwDrawModel* DocumentDrawModelManager::GetOrCreateDrawModel() +{ + return GetDrawModel() ? GetDrawModel() : MakeDrawModel_(); +} + +SdrLayerID DocumentDrawModelManager::GetHeavenId() const +{ + return mnHeaven; +} + +SdrLayerID DocumentDrawModelManager::GetHellId() const +{ + return mnHell; +} + +SdrLayerID DocumentDrawModelManager::GetControlsId() const +{ + return mnControls; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleHeavenId() const +{ + return mnInvisibleHeaven; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleHellId() const +{ + return mnInvisibleHell; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleControlsId() const +{ + return mnInvisibleControls; +} + +void DocumentDrawModelManager::NotifyInvisibleLayers( SdrPageView& _rSdrPageView ) +{ + OUString sLayerNm; + sLayerNm = "InvisibleHell"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); + + sLayerNm = "InvisibleHeaven"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); + + sLayerNm = "InvisibleControls"; + _rSdrPageView.SetLayerVisible( sLayerNm, false ); +} + +bool DocumentDrawModelManager::IsVisibleLayerId( SdrLayerID _nLayerId ) const +{ + bool bRetVal; + + if ( _nLayerId == GetHeavenId() || + _nLayerId == GetHellId() || + _nLayerId == GetControlsId() ) + { + bRetVal = true; + } + else if ( _nLayerId == GetInvisibleHeavenId() || + _nLayerId == GetInvisibleHellId() || + _nLayerId == GetInvisibleControlsId() ) + { + bRetVal = false; + } + else + { + OSL_FAIL( "<SwDoc::IsVisibleLayerId(..)> - unknown layer ID." ); + bRetVal = false; + } + + return bRetVal; +} + +SdrLayerID DocumentDrawModelManager::GetInvisibleLayerIdByVisibleOne( SdrLayerID _nVisibleLayerId ) +{ + SdrLayerID nInvisibleLayerId; + + if ( _nVisibleLayerId == GetHeavenId() ) + { + nInvisibleLayerId = GetInvisibleHeavenId(); + } + else if ( _nVisibleLayerId == GetHellId() ) + { + nInvisibleLayerId = GetInvisibleHellId(); + } + else if ( _nVisibleLayerId == GetControlsId() ) + { + nInvisibleLayerId = GetInvisibleControlsId(); + } + else if ( _nVisibleLayerId == GetInvisibleHeavenId() || + _nVisibleLayerId == GetInvisibleHellId() || + _nVisibleLayerId == GetInvisibleControlsId() ) + { + OSL_FAIL( "<SwDoc::GetInvisibleLayerIdByVisibleOne(..)> - given layer ID already an invisible one." ); + nInvisibleLayerId = _nVisibleLayerId; + } + else + { + OSL_FAIL( "<SwDoc::GetInvisibleLayerIdByVisibleOne(..)> - given layer ID is unknown." ); + nInvisibleLayerId = _nVisibleLayerId; + } + + return nInvisibleLayerId; +} + +bool DocumentDrawModelManager::Search(const SwPaM& rPaM, const SvxSearchItem& rSearchItem) +{ + SwPosFlyFrames aFrames = m_rDoc.GetAllFlyFormats(&rPaM, /*bDrawAlso=*/true); + + for (const SwPosFlyFramePtr& pPosFlyFrame : aFrames) + { + // Filter for at-paragraph anchored draw frames. + const SwFrameFormat& rFrameFormat = pPosFlyFrame->GetFormat(); + const SwFormatAnchor& rAnchor = rFrameFormat.GetAnchor(); + if (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PARA || rFrameFormat.Which() != RES_DRAWFRMFMT) + continue; + + // Does the shape have matching text? + SdrOutliner& rOutliner = GetDrawModel()->GetDrawOutliner(); + SdrObject* pObject = const_cast<SdrObject*>(rFrameFormat.FindSdrObject()); + SdrTextObj* pTextObj = dynamic_cast<SdrTextObj*>(pObject); + if (!pTextObj) + continue; + const OutlinerParaObject* pParaObj = pTextObj->GetOutlinerParaObject(); + if (!pParaObj) + continue; + rOutliner.SetText(*pParaObj); + SwDocShell* pDocShell = m_rDoc.GetDocShell(); + if (!pDocShell) + return false; + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + if (!pWrtShell) + return false; + if (!rOutliner.HasText(rSearchItem)) + continue; + + // If so, then select highlight the search result. + pWrtShell->SelectObj(Point(), 0, pObject); + SwView* pView = pDocShell->GetView(); + if (!pView) + return false; + if (!pView->EnterShapeDrawTextMode(pObject)) + continue; + SdrView* pSdrView = pWrtShell->GetDrawView(); + if (!pSdrView) + return false; + OutlinerView* pOutlinerView = pSdrView->GetTextEditOutlinerView(); + if (!rSearchItem.GetBackward()) + pOutlinerView->SetSelection(ESelection(0, 0, 0, 0)); + else + pOutlinerView->SetSelection(ESelection(EE_PARA_MAX_COUNT, EE_TEXTPOS_MAX_COUNT, EE_PARA_MAX_COUNT, EE_TEXTPOS_MAX_COUNT)); + pOutlinerView->StartSearchAndReplace(rSearchItem); + return true; + } + + return false; +} + +void DocumentDrawModelManager::DrawNotifyUndoHdl() +{ + mpDrawModel->SetNotifyUndoActionHdl( nullptr ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentExternalDataManager.cxx b/sw/source/core/doc/DocumentExternalDataManager.cxx new file mode 100644 index 000000000..3e751a316 --- /dev/null +++ b/sw/source/core/doc/DocumentExternalDataManager.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 <DocumentExternalDataManager.hxx> + +namespace sw +{ + +void DocumentExternalDataManager::setExternalData(::sw::tExternalDataType eType, ::sw::tExternalDataPointer pPayload) +{ + m_externalData[eType] = pPayload; +} + +::sw::tExternalDataPointer DocumentExternalDataManager::getExternalData(::sw::tExternalDataType eType) +{ + return m_externalData[eType]; +} + +} diff --git a/sw/source/core/doc/DocumentFieldsManager.cxx b/sw/source/core/doc/DocumentFieldsManager.cxx new file mode 100644 index 000000000..800c90967 --- /dev/null +++ b/sw/source/core/doc/DocumentFieldsManager.cxx @@ -0,0 +1,1860 @@ +/* -*- 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 <DocumentFieldsManager.hxx> +#include <config_features.h> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <rootfrm.hxx> +#include <dbmgr.hxx> +#include <chpfld.hxx> +#include <dbfld.hxx> +#include <reffld.hxx> +#include <flddropdown.hxx> +#include <strings.hrc> +#include <SwUndoField.hxx> +#include <flddat.hxx> +#include <cntfrm.hxx> +#include <section.hxx> +#include <docufld.hxx> +#include <calbck.hxx> +#include <cellatr.hxx> +#include <swtable.hxx> +#include <frmfmt.hxx> +#include <fmtfld.hxx> +#include <ndtxt.hxx> +#include <txtfld.hxx> +#include <docfld.hxx> +#include <hints.hxx> +#include <docary.hxx> +#include <fldbas.hxx> +#include <expfld.hxx> +#include <ddefld.hxx> +#include <authfld.hxx> +#include <usrfld.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <o3tl/deleter.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <comphelper/scopeguard.hxx> +#include <com/sun/star/uno/Any.hxx> + +using namespace ::com::sun::star::uno; + +namespace sw +{ + bool IsFieldDeletedInModel(IDocumentRedlineAccess const& rIDRA, + SwTextField const& rTextField) + { + SwRedlineTable::size_type tmp; + SwPosition const pos(rTextField.GetTextNode(), + rTextField.GetStart()); + SwRangeRedline const*const pRedline(rIDRA.GetRedline(pos, &tmp)); + return (pRedline + && pRedline->GetType() == RedlineType::Delete + && *pRedline->GetPoint() != *pRedline->GetMark()); + } +} + +namespace +{ + #if HAVE_FEATURE_DBCONNECTIVITY + + OUString lcl_GetDBVarName( SwDoc& rDoc, SwDBNameInfField& rDBField ) + { + SwDBData aDBData( rDBField.GetDBData( &rDoc )); + OUString sDBNumNm; + SwDBData aDocData = rDoc.GetDBData(); + + if( aDBData != aDocData ) + { + sDBNumNm = aDBData.sDataSource + OUStringChar(DB_DELIM) + + aDBData.sCommand + OUStringChar(DB_DELIM); + } + sDBNumNm += SwFieldType::GetTypeStr(SwFieldTypesEnum::DatabaseSetNumber); + + return sDBNumNm; + } + + #endif + + bool IsFieldDeleted(IDocumentRedlineAccess const& rIDRA, + SwRootFrame const& rLayout, SwTextField const& rTextField) + { + SwTextNode const& rNode(rTextField.GetTextNode()); + bool const isInBody( + rNode.GetNodes().GetEndOfExtras().GetIndex() < rNode.GetIndex()); + if (!isInBody && nullptr == rNode.getLayoutFrame(&rLayout)) + { // see SwDocUpdateField::GetBodyNode() - fields in hidden sections + // don't have layout frames but must be updated, so use the same + // check as there, but do it again because GetBodyNode() checks + // for *any* layout... + return true; + } + return sw::IsFieldDeletedInModel(rIDRA, rTextField); + } + + void lcl_CalcField( SwDoc& rDoc, SwCalc& rCalc, const SetGetExpField& rSGEField, + SwDBManager* pMgr, SwRootFrame const*const pLayout) + { + const SwTextField* pTextField = rSGEField.GetTextField(); + if( !pTextField ) + return ; + + if (pLayout && pLayout->IsHideRedlines() + && IsFieldDeleted(rDoc.getIDocumentRedlineAccess(), *pLayout, *pTextField)) + { + return; + } + + const SwField* pField = pTextField->GetFormatField().GetField(); + const SwFieldIds nFieldWhich = pField->GetTyp()->Which(); + + if( SwFieldIds::SetExp == nFieldWhich ) + { + SwSbxValue aValue; + if( nsSwGetSetExpType::GSE_EXPR & pField->GetSubType() ) + aValue.PutDouble( static_cast<const SwSetExpField*>(pField)->GetValue(pLayout) ); + else + // Extension to calculate with Strings + aValue.PutString( static_cast<const SwSetExpField*>(pField)->GetExpStr(pLayout) ); + + // set the new value in Calculator + rCalc.VarChange( pField->GetTyp()->GetName(), aValue ); + } + else if( pMgr ) + { + #if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDoc; + #else + switch( nFieldWhich ) + { + case SwFieldIds::DbNumSet: + { + SwDBNumSetField* pDBField = const_cast<SwDBNumSetField*>(static_cast<const SwDBNumSetField*>(pField)); + + SwDBData aDBData(pDBField->GetDBData(&rDoc)); + + if( pDBField->IsCondValid() && + pMgr->OpenDataSource( aDBData.sDataSource, aDBData.sCommand )) + rCalc.VarChange( lcl_GetDBVarName( rDoc, *pDBField), + pDBField->GetFormat() ); + } + break; + case SwFieldIds::DbNextSet: + { + SwDBNextSetField* pDBField = const_cast<SwDBNextSetField*>(static_cast<const SwDBNextSetField*>(pField)); + SwDBData aDBData(pDBField->GetDBData(&rDoc)); + if( !pDBField->IsCondValid() || + !pMgr->OpenDataSource( aDBData.sDataSource, aDBData.sCommand )) + break; + + OUString sDBNumNm(lcl_GetDBVarName( rDoc, *pDBField)); + SwCalcExp* pExp = rCalc.VarLook( sDBNumNm ); + if( pExp ) + rCalc.VarChange( sDBNumNm, pExp->nValue.GetLong() + 1 ); + } + break; + + default: break; + } + #endif + } + } +} + +namespace sw +{ + +DocumentFieldsManager::DocumentFieldsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + mbNewFieldLst(true), + mpUpdateFields(new SwDocUpdateField(m_rDoc)), + mpFieldTypes( new SwFieldTypes ), + mnLockExpField( 0 ) +{ +} + +const SwFieldTypes* DocumentFieldsManager::GetFieldTypes() const +{ + return mpFieldTypes.get(); +} + +/** Insert field types + * + * @param rFieldTyp ??? + * @return Always returns a pointer to the type, if it's new or already added. + */ +SwFieldType* DocumentFieldsManager::InsertFieldType(const SwFieldType &rFieldTyp) +{ + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + const SwFieldIds nFieldWhich = rFieldTyp.Which(); + + SwFieldTypes::size_type i = INIT_FLDTYPES; + + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + //JP 29.01.96: SequenceFields start at INIT_FLDTYPES - 3!! + // Or we get doubble number circles!! + //MIB 14.03.95: From now on also the SW3-Reader relies on this, when + //constructing string pools and when reading SetExp fields + if( nsSwGetSetExpType::GSE_SEQ & static_cast<const SwSetExpFieldType&>(rFieldTyp).GetType() ) + i -= INIT_SEQ_FLDTYPES; + [[fallthrough]]; + case SwFieldIds::Database: + case SwFieldIds::User: + case SwFieldIds::Dde: + { + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + OUString sFieldNm( rFieldTyp.GetName() ); + for( ; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() && + rSCmp.isEqual( sFieldNm, (*mpFieldTypes)[i]->GetName() )) + return (*mpFieldTypes)[i].get(); + } + break; + + case SwFieldIds::TableOfAuthorities: + for( ; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + break; + + default: + for( i = 0; i < nSize; ++i ) + if( nFieldWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + } + + std::unique_ptr<SwFieldType> pNew = rFieldTyp.Copy(); + switch( nFieldWhich ) + { + case SwFieldIds::Dde: + static_cast<SwDDEFieldType*>(pNew.get())->SetDoc( &m_rDoc ); + break; + + case SwFieldIds::Database: + case SwFieldIds::Table: + case SwFieldIds::DateTime: + case SwFieldIds::GetExp: + static_cast<SwValueFieldType*>(pNew.get())->SetDoc( &m_rDoc ); + break; + + case SwFieldIds::User: + case SwFieldIds::SetExp: + static_cast<SwValueFieldType*>(pNew.get())->SetDoc( &m_rDoc ); + // JP 29.07.96: Optionally prepare FieldList for Calculator: + mpUpdateFields->InsertFieldType( *pNew ); + break; + case SwFieldIds::TableOfAuthorities : + static_cast<SwAuthorityFieldType*>(pNew.get())->SetDoc( &m_rDoc ); + break; + default: break; + } + + mpFieldTypes->insert( mpFieldTypes->begin() + nSize, std::move(pNew) ); + m_rDoc.getIDocumentState().SetModified(); + + return (*mpFieldTypes)[ nSize ].get(); +} + +/// @returns the field type of the Doc +SwFieldType *DocumentFieldsManager::GetSysFieldType( const SwFieldIds eWhich ) const +{ + for( SwFieldTypes::size_type i = 0; i < INIT_FLDTYPES; ++i ) + if( eWhich == (*mpFieldTypes)[i]->Which() ) + return (*mpFieldTypes)[i].get(); + return nullptr; +} + +/// Find first type with ResId and name +SwFieldType* DocumentFieldsManager::GetFieldType( + SwFieldIds nResId, + const OUString& rName, + bool bDbFieldMatching // used in some UNO calls for SwFieldIds::Database to use different string matching code #i51815# + ) const +{ + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + SwFieldTypes::size_type i {0}; + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + + switch( nResId ) + { + case SwFieldIds::SetExp: + //JP 29.01.96: SequenceFields start at INIT_FLDTYPES - 3!! + // Or we get doubble number circles!! + //MIB 14.03.95: From now on also the SW3-Reader relies on this, when + //constructing string pools and when reading SetExp fields + i = INIT_FLDTYPES - INIT_SEQ_FLDTYPES; + break; + + case SwFieldIds::Database: + case SwFieldIds::User: + case SwFieldIds::Dde: + case SwFieldIds::TableOfAuthorities: + i = INIT_FLDTYPES; + break; + default: break; + } + + SwFieldType* pRet = nullptr; + for( ; i < nSize; ++i ) + { + SwFieldType* pFieldType = (*mpFieldTypes)[i].get(); + + if (nResId == pFieldType->Which()) + { + OUString aFieldName( pFieldType->GetName() ); + if (bDbFieldMatching && nResId == SwFieldIds::Database) // #i51815# + aFieldName = aFieldName.replace(DB_DELIM, '.'); + + if (rSCmp.isEqual( rName, aFieldName )) + { + pRet = pFieldType; + break; + } + } + } + return pRet; +} + +/// Remove field type +void DocumentFieldsManager::RemoveFieldType(size_t nField) +{ + OSL_ENSURE( INIT_FLDTYPES <= nField, "don't remove InitFields" ); + /* + * Dependent fields present -> ErrRaise + */ + if(nField < mpFieldTypes->size()) + { + SwFieldType* pTmp = (*mpFieldTypes)[nField].get(); + + // JP 29.07.96: Optionally prepare FieldList for Calculator + SwFieldIds nWhich = pTmp->Which(); + switch( nWhich ) + { + case SwFieldIds::SetExp: + case SwFieldIds::User: + mpUpdateFields->RemoveFieldType( *pTmp ); + [[fallthrough]]; + case SwFieldIds::Dde: + if( pTmp->HasWriterListeners() && !m_rDoc.IsUsed( *pTmp ) ) + { + if( SwFieldIds::SetExp == nWhich ) + static_cast<SwSetExpFieldType*>(pTmp)->SetDeleted( true ); + else if( SwFieldIds::User == nWhich ) + static_cast<SwUserFieldType*>(pTmp)->SetDeleted( true ); + else + static_cast<SwDDEFieldType*>(pTmp)->SetDeleted( true ); + nWhich = SwFieldIds::Database; + } + break; + default: break; + } + + if( nWhich != SwFieldIds::Database ) + { + OSL_ENSURE( !pTmp->HasWriterListeners(), "Dependent fields present!" ); + } + else + (*mpFieldTypes)[nField].release(); // DB fields are ref-counted and delete themselves + + mpFieldTypes->erase( mpFieldTypes->begin() + nField ); + m_rDoc.getIDocumentState().SetModified(); + } +} + +// All have to be re-evaluated. +void DocumentFieldsManager::UpdateFields( bool bCloseDB ) +{ + // Call Modify() for every field type, + // dependent SwTextField get notified ... + + for( auto const & pFieldType : *mpFieldTypes ) + { + switch( pFieldType->Which() ) + { + // Update table fields second to last + // Update references last + case SwFieldIds::GetRef: + case SwFieldIds::Table: + case SwFieldIds::Database: + case SwFieldIds::JumpEdit: + case SwFieldIds::RefPageSet: // are never expanded! + break; + + case SwFieldIds::Dde: + { + SwMsgPoolItem aUpdateDDE( RES_UPDATEDDETBL ); + pFieldType->ModifyNotification( nullptr, &aUpdateDDE ); + break; + } + case SwFieldIds::GetExp: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + // Expression fields are treated separately + break; + default: + pFieldType->ModifyNotification ( nullptr, nullptr ); + } + } + + if( !IsExpFieldsLocked() ) + UpdateExpFields( nullptr, false ); // update expression fields + + // Tables + UpdateTableFields(nullptr); + + // References + UpdateRefFields(); + if( bCloseDB ) + { +#if HAVE_FEATURE_DBCONNECTIVITY + m_rDoc.GetDBManager()->CloseAll(); +#endif + } + // Only evaluate on full update + m_rDoc.getIDocumentState().SetModified(); +} + +void DocumentFieldsManager::InsDeletedFieldType( SwFieldType& rFieldTyp ) +{ + // The FieldType was marked as deleted and removed from the array. + // One has to look this up again, now. + // - If it's not present, it can be re-inserted. + // - If the same type is found, the deleted one has to be renamed. + + const SwFieldTypes::size_type nSize = mpFieldTypes->size(); + const SwFieldIds nFieldWhich = rFieldTyp.Which(); + + OSL_ENSURE( SwFieldIds::SetExp == nFieldWhich || + SwFieldIds::User == nFieldWhich || + SwFieldIds::Dde == nFieldWhich, "Wrong FieldType" ); + + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + const OUString& rFieldNm = rFieldTyp.GetName(); + + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < nSize; ++i ) + { + SwFieldType* pFnd = (*mpFieldTypes)[i].get(); + if( nFieldWhich == pFnd->Which() && + rSCmp.isEqual( rFieldNm, pFnd->GetName() ) ) + { + // find new name + SwFieldTypes::size_type nNum = 1; + do { + OUString sSrch = rFieldNm + OUString::number( nNum ); + for( i = INIT_FLDTYPES; i < nSize; ++i ) + { + pFnd = (*mpFieldTypes)[i].get(); + if( nFieldWhich == pFnd->Which() && + rSCmp.isEqual( sSrch, pFnd->GetName() ) ) + break; + } + if( i >= nSize ) // not found + { + const_cast<OUString&>(rFieldNm) = sSrch; + break; // exit while loop + } + ++nNum; + } while( true ); + break; + } + } + + // not found, so insert, and updated deleted flag + mpFieldTypes->insert( mpFieldTypes->begin() + nSize, std::unique_ptr<SwFieldType>(&rFieldTyp) ); + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + static_cast<SwSetExpFieldType&>(rFieldTyp).SetDeleted( false ); + break; + case SwFieldIds::User: + static_cast<SwUserFieldType&>(rFieldTyp).SetDeleted( false ); + break; + case SwFieldIds::Dde: + static_cast<SwDDEFieldType&>(rFieldTyp).SetDeleted( false ); + break; + default: break; + } +} + +void DocumentFieldsManager::PutValueToField(const SwPosition & rPos, + const Any& rVal, sal_uInt16 nWhich) +{ + Any aOldVal; + SwField * pField = GetFieldAtPos(rPos); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + pField->QueryValue(aOldVal, nWhich)) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFieldFromAPI>(rPos, aOldVal, rVal, nWhich)); + } + + pField->PutValue(rVal, nWhich); +} + +bool DocumentFieldsManager::UpdateField(SwTextField * pDstTextField, SwField & rSrcField, + SwMsgPoolItem * pMsgHint, + bool bUpdateFields) +{ + OSL_ENSURE(pDstTextField, "no field to update!"); + + bool bTableSelBreak = false; + + SwFormatField * pDstFormatField = const_cast<SwFormatField*>(&pDstTextField->GetFormatField()); + SwField * pDstField = pDstFormatField->GetField(); + SwFieldIds nFieldWhich = rSrcField.GetTyp()->Which(); + SwNodeIndex aTableNdIdx(pDstTextField->GetTextNode()); + + if (pDstField->GetTyp()->Which() == + rSrcField.GetTyp()->Which()) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwPosition aPosition( pDstTextField->GetTextNode() ); + aPosition.nContent = pDstTextField->GetStart(); + + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFieldFromDoc>( aPosition, *pDstField, rSrcField, pMsgHint, bUpdateFields) ); + } + + pDstFormatField->SetField(rSrcField.CopyField()); + SwField* pNewField = pDstFormatField->GetField(); + + switch( nFieldWhich ) + { + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + UpdateExpFields( pDstTextField, true ); + break; + + case SwFieldIds::Table: + { + const SwTableNode* pTableNd = + m_rDoc.IsIdxInTable(aTableNdIdx); + if( pTableNd ) + { + SwTableFormulaUpdate aTableUpdate( &pTableNd-> + GetTable() ); + if (bUpdateFields) + UpdateTableFields( &aTableUpdate ); + else + pNewField->GetTyp()->ModifyNotification(nullptr, &aTableUpdate); + + if (! bUpdateFields) + bTableSelBreak = true; + } + } + break; + + case SwFieldIds::Macro: + if( bUpdateFields && pDstTextField->GetpTextNode() ) + pDstTextField->GetpTextNode()-> + ModifyNotification( nullptr, pDstFormatField ); + break; + + case SwFieldIds::DatabaseName: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + m_rDoc.ChgDBData(static_cast<SwDBNameInfField*>( pNewField)->GetRealDBData()); + pNewField->GetTyp()->UpdateFields(); + + break; + + case SwFieldIds::Database: +#if HAVE_FEATURE_DBCONNECTIVITY + { + // JP 10.02.96: call ChgValue, so that the style change sets the + // ContentString correctly + SwDBField* pDBField = static_cast<SwDBField*>(pNewField); + if (pDBField->IsInitialized()) + pDBField->ChgValue( pDBField->GetValue(), true ); + + pDBField->ClearInitialized(); + pDBField->InitContent(); + } +#endif + [[fallthrough]]; + + default: + pDstFormatField->UpdateTextNode(nullptr, pMsgHint); + } + + // The fields we can calculate here are being triggered for an update + // here explicitly. + if( nFieldWhich == SwFieldIds::User ) + UpdateUsrFields(); + } + + return bTableSelBreak; +} + +/// Update reference and table fields +void DocumentFieldsManager::UpdateRefFields() +{ + for( auto const & pFieldType : *mpFieldTypes ) + if( SwFieldIds::GetRef == pFieldType->Which() ) + pFieldType->ModifyNotification( nullptr, nullptr ); +} + +void DocumentFieldsManager::UpdateTableFields( SfxPoolItem* pHt ) +{ + OSL_ENSURE( !pHt || RES_TABLEFML_UPDATE == pHt->Which(), + "What MessageItem is this?" ); + + auto pFieldType = GetFieldType( SwFieldIds::Table, OUString(), false ); + if(pFieldType) + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + SwTableFormulaUpdate* pUpdateField = nullptr; + if( pHt && RES_TABLEFML_UPDATE == pHt->Which() ) + pUpdateField = static_cast<SwTableFormulaUpdate*>(pHt); + for(auto pFormatField : vFields) + { + SwTableField* pField = static_cast<SwTableField*>(pFormatField->GetField()); + if( pUpdateField ) + { + // table where this field is located + const SwTableNode* pTableNd; + const SwTextNode& rTextNd = pFormatField->GetTextField()->GetTextNode(); + pTableNd = rTextNd.FindTableNode(); + if (pTableNd == nullptr) + continue; + + switch( pUpdateField->m_eFlags ) + { + case TBL_CALC: + // re-set the value flag + // JP 17.06.96: internal representation of all formulas + // (reference to other table!!!) + if( nsSwExtendedSubType::SUB_CMD & pField->GetSubType() ) + pField->PtrToBoxNm( pUpdateField->m_pTable ); + else + pField->ChgValid( false ); + break; + case TBL_BOXNAME: + // is this the wanted table? + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // to the external representation + pField->PtrToBoxNm( pUpdateField->m_pTable ); + break; + case TBL_BOXPTR: + // to the internal representation + // JP 17.06.96: internal representation on all formulas + // (reference to other table!!!) + pField->BoxNmToPtr( pUpdateField->m_pTable ); + break; + case TBL_RELBOXNAME: + // is this the wanted table? + if( &pTableNd->GetTable() == pUpdateField->m_pTable ) + // to the relative representation + pField->ToRelBoxNm( pUpdateField->m_pTable ); + break; + default: + break; + } + } + else + // reset the value flag for all + pField->ChgValid( false ); + } + } + // process all table box formulas + for (const SfxPoolItem* pItem : m_rDoc.GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + auto pBoxFormula = dynamic_cast<const SwTableBoxFormula*>(pItem); + if( pBoxFormula && pBoxFormula->GetDefinedIn() ) + { + const_cast<SwTableBoxFormula*>(pBoxFormula)->ChangeState( pHt ); + } + } + + SwRootFrame const* pLayout(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + assert(!pLayout || pLay->IsHideRedlines() == pLayout->IsHideRedlines()); // TODO + pLayout = pLay; + } + + // all fields/boxes are now invalid, so we can start to calculate + if( pHt && ( RES_TABLEFML_UPDATE != pHt->Which() || + TBL_CALC != static_cast<SwTableFormulaUpdate*>(pHt)->m_eFlags )) + return ; + + std::unique_ptr<SwCalc, o3tl::default_delete<SwCalc>> pCalc; + + if( pFieldType ) + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + for(SwFormatField* pFormatField: vFields) + { + // start calculation at the end + // new fields are inserted at the beginning of the modify chain + // that gives faster calculation on import + // mba: do we really need this "optimization"? Is it still valid? + SwTableField *const pField(static_cast<SwTableField*>(pFormatField->GetField())); + if (nsSwExtendedSubType::SUB_CMD & pField->GetSubType()) + continue; + + // needs to be recalculated + if( !pField->IsValid() ) + { + // table where this field is located + const SwTextNode& rTextNd = pFormatField->GetTextField()->GetTextNode(); + const SwTableNode* pTableNd = rTextNd.FindTableNode(); + if( !pTableNd ) + continue; + + // if this field is not in the to-be-updated table, skip it + if( pHt && &pTableNd->GetTable() != + static_cast<SwTableFormulaUpdate*>(pHt)->m_pTable ) + continue; + + if( !pCalc ) + pCalc.reset(new SwCalc( m_rDoc )); + + // get the values of all SetExpression fields that are valid + // until the table + SwFrame* pFrame = nullptr; + if( pTableNd->GetIndex() < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + // is in the special section, that's expensive! + Point aPt; // return the first frame of the layout - Tab.Headline!! + std::pair<Point, bool> const tmp(aPt, true); + pFrame = rTextNd.getLayoutFrame(pLayout, nullptr, &tmp); + if( pFrame ) + { + SwPosition aPos( *pTableNd ); + if( GetBodyTextNode( m_rDoc, aPos, *pFrame ) ) + FieldsToCalc( *pCalc, SetGetExpField( + aPos.nNode, pFormatField->GetTextField(), + &aPos.nContent), pLayout); + else + pFrame = nullptr; + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( rTextNd ); + FieldsToCalc( *pCalc, + SetGetExpField(aIdx, pFormatField->GetTextField()), + pLayout); + } + + SwTableCalcPara aPara(*pCalc, pTableNd->GetTable(), pLayout); + pField->CalcField( aPara ); + if( aPara.IsStackOverflow() ) + { + bool const bResult = aPara.CalcWithStackOverflow(); + if (bResult) + { + pField->CalcField( aPara ); + } + OSL_ENSURE(bResult, + "the chained formula could no be calculated"); + } + pCalc->SetCalcError( SwCalcError::NONE ); + } + pFormatField->UpdateTextNode(nullptr, pHt); + } + } + + // calculate the formula at the boxes + for (const SfxPoolItem* pItem : m_rDoc.GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + auto pFormula = const_cast<SwTableBoxFormula*>(dynamic_cast<const SwTableBoxFormula*>(pItem)); + if( pFormula && pFormula->GetDefinedIn() && !pFormula->IsValid() ) + { + SwTableBox* pBox = pFormula->GetTableBox(); + if( pBox && pBox->GetSttNd() && + pBox->GetSttNd()->GetNodes().IsDocNodes() ) + { + const SwTableNode* pTableNd = pBox->GetSttNd()->FindTableNode(); + if( !pHt || &pTableNd->GetTable() == + static_cast<SwTableFormulaUpdate*>(pHt)->m_pTable ) + { + double nValue; + if( !pCalc ) + pCalc.reset(new SwCalc( m_rDoc )); + + // get the values of all SetExpression fields that are valid + // until the table + SwFrame* pFrame = nullptr; + if( pTableNd->GetIndex() < m_rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + // is in the special section, that's expensive! + Point aPt; // return the first frame of the layout - Tab.Headline!! + SwNodeIndex aCNdIdx( *pTableNd, +2 ); + SwContentNode* pCNd = aCNdIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = m_rDoc.GetNodes().GoNext( &aCNdIdx ); + + if (pCNd) + { + std::pair<Point, bool> const tmp(aPt, true); + pFrame = pCNd->getLayoutFrame(pLayout, nullptr, &tmp); + if( pFrame ) + { + SwPosition aPos( *pCNd ); + if( GetBodyTextNode( m_rDoc, aPos, *pFrame ) ) + FieldsToCalc(*pCalc, SetGetExpField(aPos.nNode), + pLayout); + else + pFrame = nullptr; + } + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( *pTableNd ); + FieldsToCalc(*pCalc, SetGetExpField(aIdx), pLayout); + } + + SwTableCalcPara aPara(*pCalc, pTableNd->GetTable(), pLayout); + pFormula->Calc( aPara, nValue ); + + if( aPara.IsStackOverflow() ) + { + bool const bResult = aPara.CalcWithStackOverflow(); + if (bResult) + { + pFormula->Calc( aPara, nValue ); + } + OSL_ENSURE(bResult, + "the chained formula could no be calculated"); + } + + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + SfxItemSet aTmp( m_rDoc.GetAttrPool(), + svl::Items<RES_BOXATR_BEGIN,RES_BOXATR_END-1>{} ); + + if( pCalc->IsCalcError() ) + nValue = DBL_MAX; + aTmp.Put( SwTableBoxValue( nValue )); + if( SfxItemState::SET != pFormat->GetItemState( RES_BOXATR_FORMAT )) + aTmp.Put( SwTableBoxNumFormat( 0 )); + pFormat->SetFormatAttr( aTmp ); + + pCalc->SetCalcError( SwCalcError::NONE ); + } + } + } + } +} + +void DocumentFieldsManager::UpdateExpFields( SwTextField* pUpdateField, bool bUpdRefFields ) +{ + if( IsExpFieldsLocked() || m_rDoc.IsInReading() ) + return; + + bool bOldInUpdateFields = mpUpdateFields->IsInUpdateFields(); + mpUpdateFields->SetInUpdateFields( true ); + + mpUpdateFields->MakeFieldList( m_rDoc, true, GETFLD_ALL ); + mbNewFieldLst = false; + + if (mpUpdateFields->GetSortList()->empty()) + { + if( bUpdRefFields ) + UpdateRefFields(); + + mpUpdateFields->SetInUpdateFields( bOldInUpdateFields ); + mpUpdateFields->SetFieldsDirty( false ); + return ; + } + + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + if (pLayout || !pLayoutRLHidden) // always calc *something*... + { + UpdateExpFieldsImpl(pUpdateField, pLayout); + } + if (pLayoutRLHidden) + { + UpdateExpFieldsImpl(pUpdateField, pLayoutRLHidden); + } + + // update reference fields + if( bUpdRefFields ) + UpdateRefFields(); + + mpUpdateFields->SetInUpdateFields( bOldInUpdateFields ); + mpUpdateFields->SetFieldsDirty( false ); +} + +void DocumentFieldsManager::UpdateExpFieldsImpl( + SwTextField * pUpdateField, SwRootFrame const*const pLayout) +{ + SwFieldIds nWhich; + + // Hash table for all string replacements is filled on-the-fly. + // Try to fabricate an uneven number. + const SwFieldTypes::size_type nHashSize {(( mpFieldTypes->size() / 7 ) + 1 ) * 7}; + const sal_uInt16 nStrFormatCnt = static_cast<sal_uInt16>(nHashSize); + OSL_ENSURE( nStrFormatCnt == nHashSize, "Downcasting to sal_uInt16 lost information!" ); + SwHashTable<HashStr> aHashStrTable(nStrFormatCnt); + + { + const SwFieldType* pFieldType; + // process separately: + for( auto n = mpFieldTypes->size(); n; ) + { + pFieldType = (*mpFieldTypes)[ --n ].get(); + switch( pFieldType->Which() ) + { + case SwFieldIds::User: + { + // Entry present? + sal_uInt16 nPos; + const OUString& rNm = pFieldType->GetName(); + OUString sExpand(const_cast<SwUserFieldType*>(static_cast<const SwUserFieldType*>(pFieldType))->Expand(nsSwGetSetExpType::GSE_STRING, 0, LANGUAGE_SYSTEM)); + SwHash* pFnd = aHashStrTable.Find( rNm, &nPos ); + if( pFnd ) + // modify entry in the hash table + static_cast<HashStr*>(pFnd)->aSetStr = sExpand; + else + // insert the new entry + aHashStrTable[nPos].reset( new HashStr( rNm, sExpand, + aHashStrTable[nPos].release() ) ); + } + break; + default: break; + } + } + } + + // The array is filled with all fields; start calculation. + SwCalc aCalc( m_rDoc ); + +#if HAVE_FEATURE_DBCONNECTIVITY + OUString sDBNumNm( SwFieldType::GetTypeStr( SwFieldTypesEnum::DatabaseSetNumber ) ); + + // already set the current record number + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll( false ); + + SvtSysLocale aSysLocale; + const LocaleDataWrapper* pLclData = aSysLocale.GetLocaleDataPtr(); + const LanguageType nLang = pLclData->getLanguageTag().getLanguageType(); + bool bCanFill = pMgr->FillCalcWithMergeData( m_rDoc.GetNumberFormatter(), nLang, aCalc ); +#endif + + // Make sure we don't hide all content, which would lead to a crash. First, count how many visible sections we have. + int nShownSections = 0; + sal_uLong nContentStart = m_rDoc.GetNodes().GetEndOfContent().StartOfSectionIndex() + 1; + sal_uLong nContentEnd = m_rDoc.GetNodes().GetEndOfContent().GetIndex(); + SwSectionFormats& rSectFormats = m_rDoc.GetSections(); + for( SwSectionFormats::size_type n = 0; n<rSectFormats.size(); ++n ) + { + SwSectionFormat& rSectFormat = *rSectFormats[ n ]; + SwSectionNode* pSectionNode = rSectFormat.GetSectionNode(); + SwSection* pSect = rSectFormat.GetSection(); + + // Usually some of the content is not in a section: count that as a virtual section, so that all real sections can be hidden. + // Only look for section gaps at the lowest level, ignoring sub-sections. + if ( pSectionNode && !rSectFormat.GetParent() ) + { + SwNodeIndex aNextIdx( *pSectionNode->EndOfSectionNode(), 1 ); + if ( n == 0 && pSectionNode->GetIndex() != nContentStart ) + nShownSections++; //document does not start with a section + if ( n == rSectFormats.size() - 1 ) + { + if ( aNextIdx.GetIndex() != nContentEnd ) + nShownSections++; //document does not end in a section + } + else if ( !aNextIdx.GetNode().IsSectionNode() ) + nShownSections++; //section is not immediately followed by another section + } + + // count only visible sections + if ( pSect && !pSect->CalcHiddenFlag()) + nShownSections++; + } + + IDocumentRedlineAccess const& rIDRA(m_rDoc.getIDocumentRedlineAccess()); + std::unordered_map<SwSetExpFieldType const*, SwTextNode const*> SetExpOutlineNodeMap; + + for (std::unique_ptr<SetGetExpField> const& it : *mpUpdateFields->GetSortList()) + { + SwSection* pSect = const_cast<SwSection*>(it->GetSection()); + if( pSect ) + { + SwSbxValue aValue = aCalc.Calculate( + pSect->GetCondition() ); + if(!aValue.IsVoidValue()) + { + // Do we want to hide this one? + bool bHide = aValue.GetBool(); + if (bHide && !pSect->IsCondHidden()) + { + // This section will be hidden, but it wasn't before + if (nShownSections == 1) + { + // This would be the last section, so set its condition to false, and avoid hiding it. + pSect->SetCondition("0"); + bHide = false; + } + nShownSections--; + } + pSect->SetCondHidden( bHide ); + } + continue; + } + + SwTextField* pTextField = const_cast<SwTextField*>(it->GetTextField()); + if( !pTextField ) + { + OSL_ENSURE( false, "what's wrong now'" ); + continue; + } + + if (pLayout && pLayout->IsHideRedlines() + && IsFieldDeleted(rIDRA, *pLayout, *pTextField)) + { + continue; + } + + SwFormatField* pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField()); + const SwField* pField = pFormatField->GetField(); + + nWhich = pField->GetTyp()->Which(); + switch( nWhich ) + { + case SwFieldIds::HiddenText: + { + SwHiddenTextField* pHField = const_cast<SwHiddenTextField*>(static_cast<const SwHiddenTextField*>(pField)); + SwSbxValue aValue = aCalc.Calculate( pHField->GetPar1() ); + bool bValue = !aValue.GetBool(); + if(!aValue.IsVoidValue()) + { + pHField->SetValue( bValue ); + // evaluate field + pHField->Evaluate(&m_rDoc); + } + } + break; + case SwFieldIds::HiddenPara: + { + SwHiddenParaField* pHPField = const_cast<SwHiddenParaField*>(static_cast<const SwHiddenParaField*>(pField)); + SwSbxValue aValue = aCalc.Calculate( pHPField->GetPar1() ); + bool bValue = aValue.GetBool(); + if(!aValue.IsVoidValue()) + pHPField->SetHidden( bValue ); + } + break; + case SwFieldIds::DbSetNumber: +#if HAVE_FEATURE_DBCONNECTIVITY + { + const_cast<SwDBSetNumberField*>(static_cast<const SwDBSetNumberField*>(pField))->Evaluate(&m_rDoc); + aCalc.VarChange( sDBNumNm, static_cast<const SwDBSetNumberField*>(pField)->GetSetNumber()); + pField->ExpandField(m_rDoc.IsClipBoard(), nullptr); + } +#endif + break; + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: +#if HAVE_FEATURE_DBCONNECTIVITY + { + UpdateDBNumFields( *const_cast<SwDBNameInfField*>(static_cast<const SwDBNameInfField*>(pField)), aCalc ); + if( bCanFill ) + bCanFill = pMgr->FillCalcWithMergeData( m_rDoc.GetNumberFormatter(), nLang, aCalc ); + } +#endif + break; + case SwFieldIds::Database: + { +#if HAVE_FEATURE_DBCONNECTIVITY + // evaluate field + const_cast<SwDBField*>(static_cast<const SwDBField*>(pField))->Evaluate(); + + SwDBData aTmpDBData(static_cast<const SwDBField*>(pField)->GetDBData()); + + if( pMgr->IsDataSourceOpen(aTmpDBData.sDataSource, aTmpDBData.sCommand, false)) + aCalc.VarChange( sDBNumNm, pMgr->GetSelectedRecordId(aTmpDBData.sDataSource, aTmpDBData.sCommand, aTmpDBData.nCommandType)); + + const OUString& rName = pField->GetTyp()->GetName(); + + // Add entry to hash table + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = aHashStrTable.Find( rName, &nPos ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd ) + { + // Modify entry in the hash table + pFnd->aSetStr = value; + } + else + { + // insert new entry + aHashStrTable[nPos].reset( new HashStr( rName, + value, aHashStrTable[nPos].release()) ); + } +#endif + } + break; + case SwFieldIds::GetExp: + case SwFieldIds::SetExp: + { + if( nsSwGetSetExpType::GSE_STRING & pField->GetSubType() ) // replace String + { + if( SwFieldIds::GetExp == nWhich ) + { + SwGetExpField* pGField = const_cast<SwGetExpField*>(static_cast<const SwGetExpField*>(pField)); + + if( (!pUpdateField || pUpdateField == pTextField ) + && pGField->IsInBodyText() ) + { + OUString aNew = LookString( aHashStrTable, pGField->GetFormula() ); + pGField->ChgExpStr( aNew, pLayout ); + } + } + else + { + SwSetExpField* pSField = const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)); + // is the "formula" a field? + OUString aNew = LookString( aHashStrTable, pSField->GetFormula() ); + + if( aNew.isEmpty() ) // nothing found then the formula is the new value + aNew = pSField->GetFormula(); + + // only update one field + if( !pUpdateField || pUpdateField == pTextField ) + pSField->ChgExpStr( aNew, pLayout ); + + // lookup the field's name + aNew = static_cast<SwSetExpFieldType*>(pSField->GetTyp())->GetSetRefName(); + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = aHashStrTable.Find( aNew, &nPos ); + if( pFnd ) + // Modify entry in the hash table + pFnd->aSetStr = pSField->GetExpStr(pLayout); + else + { + // insert new entry + aHashStrTable[nPos].reset( new HashStr( aNew, + pSField->GetExpStr(pLayout), + aHashStrTable[nPos].release() ) ); + pFnd = aHashStrTable[nPos].get(); + } + + // Extension for calculation with Strings + SwSbxValue aValue; + aValue.PutString( pFnd->aSetStr ); + aCalc.VarChange( aNew, aValue ); + } + } + else // recalculate formula + { + if( SwFieldIds::GetExp == nWhich ) + { + SwGetExpField* pGField = const_cast<SwGetExpField*>(static_cast<const SwGetExpField*>(pField)); + + if( (!pUpdateField || pUpdateField == pTextField ) + && pGField->IsInBodyText() ) + { + SwSbxValue aValue = aCalc.Calculate( + pGField->GetFormula()); + if(!aValue.IsVoidValue()) + pGField->SetValue(aValue.GetDouble(), pLayout); + } + } + else + { + SwSetExpField* pSField = const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)); + SwSetExpFieldType* pSFieldTyp = static_cast<SwSetExpFieldType*>(pField->GetTyp()); + OUString aNew = pSFieldTyp->GetName(); + + SwNode* pSeqNd = nullptr; + + if( pSField->IsSequenceField() ) + { + const sal_uInt8 nLvl = pSFieldTyp->GetOutlineLvl(); + if( MAXLEVEL > nLvl ) + { + // test if the Number needs to be updated + pSeqNd = m_rDoc.GetNodes()[ it->GetNode() ]; + + const SwTextNode* pOutlNd = pSeqNd-> + FindOutlineNodeOfLevel(nLvl, pLayout); + auto const iter(SetExpOutlineNodeMap.find(pSFieldTyp)); + if (iter == SetExpOutlineNodeMap.end() + || iter->second != pOutlNd) + { + SetExpOutlineNodeMap[pSFieldTyp] = pOutlNd; + aCalc.VarChange( aNew, 0 ); + } + } + } + + aNew += "=" + pSField->GetFormula(); + + SwSbxValue aValue = aCalc.Calculate( aNew ); + if (!aCalc.IsCalcError()) + { + double nErg = aValue.GetDouble(); + // only update one field + if( !aValue.IsVoidValue() && (!pUpdateField || pUpdateField == pTextField) ) + { + pSField->SetValue(nErg, pLayout); + + if( pSeqNd ) + pSFieldTyp->SetChapter(*pSField, *pSeqNd, pLayout); + } + } + } + } + } + break; + default: break; + } // switch + + { + // avoid calling ReplaceText() for input fields, it is pointless + // here and moves the cursor if it's inside the field ... + SwTextInputField *const pInputField( + pUpdateField == pTextField // ... except once, when the dialog + ? nullptr // is used to change content via UpdateOneField() + : dynamic_cast<SwTextInputField *>(pTextField)); + if (pInputField) + { + bool const tmp = pInputField->LockNotifyContentChange(); + (void) tmp; + assert(tmp && "should not be locked here?"); + } + ::comphelper::ScopeGuard g([pInputField]() + { + if (pInputField) + { + pInputField->UnlockNotifyContentChange(); + } + }); + pFormatField->UpdateTextNode(nullptr, nullptr); // trigger formatting + } + + if (pUpdateField == pTextField) // if only this one is updated + { + if( SwFieldIds::GetExp == nWhich || // only GetField or + SwFieldIds::HiddenText == nWhich || // HiddenText? + SwFieldIds::HiddenPara == nWhich) // HiddenParaField? + break; // quit + pUpdateField = nullptr; // update all from here on + } + } + +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +/// Insert field type that was marked as deleted +void DocumentFieldsManager::UpdateUsrFields() +{ + SwCalc* pCalc = nullptr; + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < mpFieldTypes->size(); ++i ) + { + const SwFieldType* pFieldType = (*mpFieldTypes)[i].get(); + if( SwFieldIds::User == pFieldType->Which() ) + { + if( !pCalc ) + pCalc = new SwCalc( m_rDoc ); + const_cast<SwUserFieldType*>(static_cast<const SwUserFieldType*>(pFieldType))->GetValue( *pCalc ); + } + } + + if( pCalc ) + { + delete pCalc; + m_rDoc.getIDocumentState().SetModified(); + } +} + +sal_Int32 DocumentFieldsManager::GetRecordsPerDocument() const +{ + sal_Int32 nRecords = 1; + + mpUpdateFields->MakeFieldList( m_rDoc, true, GETFLD_ALL ); + if (mpUpdateFields->GetSortList()->empty()) + return nRecords; + + for (std::unique_ptr<SetGetExpField> const& it : *mpUpdateFields->GetSortList()) + { + const SwTextField *pTextField = it->GetTextField(); + if( !pTextField ) + continue; + + const SwFormatField &pFormatField = pTextField->GetFormatField(); + const SwField* pField = pFormatField.GetField(); + + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + nRecords++; + break; + default: + break; + } + } + + return nRecords; +} + +void DocumentFieldsManager::UpdatePageFields( SfxPoolItem* pMsgHint ) +{ + for( SwFieldTypes::size_type i = 0; i < INIT_FLDTYPES; ++i ) + { + SwFieldType* pFieldType = (*mpFieldTypes)[ i ].get(); + switch( pFieldType->Which() ) + { + case SwFieldIds::PageNumber: + case SwFieldIds::Chapter: + case SwFieldIds::GetExp: + case SwFieldIds::RefPageGet: + pFieldType->ModifyNotification( nullptr, pMsgHint ); + break; + case SwFieldIds::DocStat: + pFieldType->ModifyNotification( nullptr, nullptr ); + break; + default: break; + } + } + SetNewFieldLst(true); +} + +void DocumentFieldsManager::LockExpFields() +{ + ++mnLockExpField; +} + +void DocumentFieldsManager::UnlockExpFields() +{ + assert(mnLockExpField != 0); + if( mnLockExpField ) + --mnLockExpField; +} + +bool DocumentFieldsManager::IsExpFieldsLocked() const +{ + return 0 != mnLockExpField; +} + +SwDocUpdateField& DocumentFieldsManager::GetUpdateFields() const +{ + return *mpUpdateFields; +} + +bool DocumentFieldsManager::SetFieldsDirty( bool b, const SwNode* pChk, sal_uLong nLen ) +{ + // See if the supplied nodes actually contain fields. + // If they don't, the flag doesn't need to be changed. + bool bFieldsFnd = false; + if( b && pChk && !GetUpdateFields().IsFieldsDirty() && !m_rDoc.IsInDtor() + // ?? what's up with Undo, this is also wanted there! + /*&& &pChk->GetNodes() == &GetNodes()*/ ) + { + b = false; + if( !nLen ) + ++nLen; + sal_uLong nStt = pChk->GetIndex(); + const SwNodes& rNds = pChk->GetNodes(); + while( nLen-- ) + { + const SwTextNode* pTNd = rNds[ nStt++ ]->GetTextNode(); + if( pTNd ) + { + if( pTNd->GetAttrOutlineLevel() != 0 ) + // update chapter fields + b = true; + else if( pTNd->GetpSwpHints() && pTNd->GetSwpHints().Count() ) + { + const size_t nEnd = pTNd->GetSwpHints().Count(); + for( size_t n = 0 ; n < nEnd; ++n ) + { + const SwTextAttr* pAttr = pTNd->GetSwpHints().Get(n); + if ( pAttr->Which() == RES_TXTATR_FIELD + || pAttr->Which() == RES_TXTATR_INPUTFIELD) + { + b = true; + break; + } + } + } + + if( b ) + break; + } + } + bFieldsFnd = b; + } + GetUpdateFields().SetFieldsDirty( b ); + return bFieldsFnd; +} + +void DocumentFieldsManager::SetFixFields( const DateTime* pNewDateTime ) +{ + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + sal_Int32 nDate; + sal_Int64 nTime; + if( pNewDateTime ) + { + nDate = pNewDateTime->GetDate(); + nTime = pNewDateTime->GetTime(); + } + else + { + DateTime aDateTime( DateTime::SYSTEM ); + nDate = aDateTime.GetDate(); + nTime = aDateTime.GetTime(); + } + + SwFieldIds const aTypes[] { + /*0*/ SwFieldIds::DocInfo, + /*1*/ SwFieldIds::Author, + /*2*/ SwFieldIds::ExtUser, + /*3*/ SwFieldIds::Filename, + /*4*/ SwFieldIds::DateTime }; // MUST be at the end! + + for(SwFieldIds aType : aTypes) + { + std::vector<SwFormatField*> vFields; + GetSysFieldType(aType)->GatherFields(vFields); + for(auto pFormatField: vFields) + { + if (pFormatField->GetTextField()) + { + bool bChgd = false; + switch( aType ) + { + case SwFieldIds::DocInfo: + if( static_cast<SwDocInfoField*>(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwDocInfoField* pDocInfField = static_cast<SwDocInfoField*>(pFormatField->GetField()); + pDocInfField->SetExpansion( static_cast<SwDocInfoFieldType*>( + pDocInfField->GetTyp())->Expand( + pDocInfField->GetSubType(), + pDocInfField->GetFormat(), + pDocInfField->GetLanguage(), + pDocInfField->GetName() ) ); + } + break; + + case SwFieldIds::Author: + if( static_cast<SwAuthorField*>(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwAuthorField* pAuthorField = static_cast<SwAuthorField*>(pFormatField->GetField()); + pAuthorField->SetExpansion( SwAuthorFieldType::Expand( pAuthorField->GetFormat() ) ); + } + break; + + case SwFieldIds::ExtUser: + if( static_cast<SwExtUserField*>(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwExtUserField* pExtUserField = static_cast<SwExtUserField*>(pFormatField->GetField()); + pExtUserField->SetExpansion( SwExtUserFieldType::Expand(pExtUserField->GetSubType()) ); + } + break; + + case SwFieldIds::DateTime: + if( static_cast<SwDateTimeField*>(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + static_cast<SwDateTimeField*>(pFormatField->GetField())->SetDateTime( + DateTime(Date(nDate), tools::Time(nTime)) ); + } + break; + + case SwFieldIds::Filename: + if( static_cast<SwFileNameField*>(pFormatField->GetField())->IsFixed() ) + { + bChgd = true; + SwFileNameField* pFileNameField = + static_cast<SwFileNameField*>(pFormatField->GetField()); + pFileNameField->SetExpansion( static_cast<SwFileNameFieldType*>( + pFileNameField->GetTyp())->Expand( + pFileNameField->GetFormat() ) ); + } + break; + default: break; + } + + // Trigger formatting + if( bChgd ) + pFormatField->UpdateTextNode(nullptr, nullptr); + } + } + } + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); +} + +void DocumentFieldsManager::FieldsToCalc(SwCalc& rCalc, + const SetGetExpField& rToThisField, SwRootFrame const*const pLayout) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_CALC ); + mbNewFieldLst = false; + +#if !HAVE_FEATURE_DBCONNECTIVITY + SwDBManager* pMgr = NULL; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll(false); +#endif + + if (!mpUpdateFields->GetSortList()->empty()) + { + SetGetExpFields::const_iterator const itLast = + mpUpdateFields->GetSortList()->upper_bound( + &rToThisField); + for (auto it = mpUpdateFields->GetSortList()->begin(); it != itLast; ++it) + { + lcl_CalcField(m_rDoc, rCalc, **it, pMgr, pLayout); + } + } +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToCalc( SwCalc& rCalc, sal_uLong nLastNd, sal_uInt16 nLastCnt ) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_CALC ); + mbNewFieldLst = false; + +#if !HAVE_FEATURE_DBCONNECTIVITY + SwDBManager* pMgr = NULL; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + pMgr->CloseAll(false); +#endif + + SwRootFrame const* pLayout(nullptr); + SwRootFrame const* pLayoutRLHidden(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayoutRLHidden = pLay; + } + else + { + pLayout = pLay; + } + } + + // note this is not duplicate of the other FieldsToCalc because there is + // (currently) no SetGetExpField that compares only a position + for(auto it = mpUpdateFields->GetSortList()->begin(); + it != mpUpdateFields->GetSortList()->end() && + ( (*it)->GetNode() < nLastNd || + ( (*it)->GetNode() == nLastNd && (*it)->GetContent() <= nLastCnt ) + ); + ++it ) + { + if (pLayout || !pLayoutRLHidden) // always calc *something*... + { + lcl_CalcField( m_rDoc, rCalc, **it, pMgr, pLayout ); + } + if (pLayoutRLHidden) + { + lcl_CalcField( m_rDoc, rCalc, **it, pMgr, pLayoutRLHidden ); + } + } + +#if HAVE_FEATURE_DBCONNECTIVITY + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToExpand( SwHashTable<HashStr> & rHashTable, + const SetGetExpField& rToThisField, SwRootFrame const& rLayout) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_EXPAND ); + mbNewFieldLst = false; + + IDocumentRedlineAccess const& rIDRA(m_rDoc.getIDocumentRedlineAccess()); + + // Hash table for all string replacements is filled on-the-fly. + // Try to fabricate an uneven number. + sal_uInt16 nTableSize = ((mpUpdateFields->GetSortList()->size() / 7) + 1) * 7; + rHashTable.resize(nTableSize); + + SetGetExpFields::const_iterator const itLast = + mpUpdateFields->GetSortList()->upper_bound(&rToThisField); + + for (auto it = mpUpdateFields->GetSortList()->begin(); it != itLast; ++it) + { + const SwTextField* pTextField = (*it)->GetTextField(); + if( !pTextField ) + continue; + + if (rLayout.IsHideRedlines() + && IsFieldDeleted(rIDRA, rLayout, *pTextField)) + { + continue; + } + + const SwField* pField = pTextField->GetFormatField().GetField(); + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::SetExp: + if( nsSwGetSetExpType::GSE_STRING & pField->GetSubType() ) + { + // set the new value in the hash table + // is the formula a field? + SwSetExpField* pSField = const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)); + OUString aNew = LookString( rHashTable, pSField->GetFormula() ); + + if( aNew.isEmpty() ) // nothing found, then the formula is + aNew = pSField->GetFormula(); // the new value + + // #i3141# - update expression of field as in method + // <SwDoc::UpdateExpFields(..)> for string/text fields + pSField->ChgExpStr(aNew, &rLayout); + + // look up the field's name + aNew = static_cast<SwSetExpFieldType*>(pSField->GetTyp())->GetSetRefName(); + // Entry present? + sal_uInt16 nPos; + SwHash* pFnd = rHashTable.Find( aNew, &nPos ); + if( pFnd ) + // modify entry in the hash table + static_cast<HashStr*>(pFnd)->aSetStr = pSField->GetExpStr(&rLayout); + else + // insert the new entry + rHashTable[nPos].reset( new HashStr( aNew, + pSField->GetExpStr(&rLayout), rHashTable[nPos].release())); + } + break; + case SwFieldIds::Database: + { + const OUString& rName = pField->GetTyp()->GetName(); + + // Insert entry in the hash table + // Entry present? + sal_uInt16 nPos; + HashStr* pFnd = rHashTable.Find( rName, &nPos ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd ) + { + // modify entry in the hash table + pFnd->aSetStr = value; + } + else + { + // insert the new entry + rHashTable[nPos].reset( new HashStr( rName, + value, rHashTable[nPos].release()) ); + } + } + break; + default: break; + } + } +} + + +bool DocumentFieldsManager::IsNewFieldLst() const +{ + return mbNewFieldLst; +} + +void DocumentFieldsManager::SetNewFieldLst(bool bFlag) +{ + mbNewFieldLst = bFlag; +} + +void DocumentFieldsManager::InsDelFieldInFieldLst( bool bIns, const SwTextField& rField ) +{ + if (!mbNewFieldLst && !m_rDoc.IsInDtor()) + mpUpdateFields->InsDelFieldInFieldLst( bIns, rField ); +} + +SwField * DocumentFieldsManager::GetFieldAtPos(const SwPosition & rPos) +{ + SwTextField * const pAttr = GetTextFieldAtPos(rPos); + + return pAttr ? const_cast<SwField *>( pAttr->GetFormatField().GetField() ) : nullptr; +} + +SwTextField * DocumentFieldsManager::GetTextFieldAtPos(const SwPosition & rPos) +{ + SwTextNode * const pNode = rPos.nNode.GetNode().GetTextNode(); + + return (pNode != nullptr) + ? pNode->GetFieldTextAttrAt( rPos.nContent.GetIndex(), true ) + : nullptr; +} + +/// @note For simplicity assume that all field types have updatable contents so +/// optimization currently only available when no fields exist. +bool DocumentFieldsManager::containsUpdatableFields() +{ + std::vector<SwFormatField*> vFields; + for (auto const& pFieldType: *mpFieldTypes) + { + pFieldType->GatherFields(vFields); + if(vFields.size()>0) + return true; + } + return false; +} + +/// Remove all unreferenced field types of a document +void DocumentFieldsManager::GCFieldTypes() +{ + for( auto n = mpFieldTypes->size(); n > INIT_FLDTYPES; ) + if( !(*mpFieldTypes)[ --n ]->HasWriterListeners() ) + RemoveFieldType( n ); +} + +void DocumentFieldsManager::InitFieldTypes() // is being called by the CTOR +{ + // Field types + mpFieldTypes->emplace_back( new SwDateTimeFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwChapterFieldType ); + mpFieldTypes->emplace_back( new SwPageNumberFieldType ); + mpFieldTypes->emplace_back( new SwAuthorFieldType ); + mpFieldTypes->emplace_back( new SwFileNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDBNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwGetExpFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwGetRefFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwHiddenTextFieldType ); + mpFieldTypes->emplace_back( new SwPostItFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDocStatFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwDocInfoFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwInputFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwTableFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwMacroFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwHiddenParaFieldType ); + mpFieldTypes->emplace_back( new SwDBNextSetFieldType ); + mpFieldTypes->emplace_back( new SwDBNumSetFieldType ); + mpFieldTypes->emplace_back( new SwDBSetNumberFieldType ); + mpFieldTypes->emplace_back( new SwTemplNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwTemplNameFieldType(&m_rDoc) ); + mpFieldTypes->emplace_back( new SwExtUserFieldType ); + mpFieldTypes->emplace_back( new SwRefPageSetFieldType ); + mpFieldTypes->emplace_back( new SwRefPageGetFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwJumpEditFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwScriptFieldType( &m_rDoc ) ); + mpFieldTypes->emplace_back( new SwCombinedCharFieldType ); + mpFieldTypes->emplace_back( new SwDropDownFieldType ); + + // Types have to be at the end! + // We expect this in the InsertFieldType! + // MIB 14.04.95: In Sw3StringPool::Setup (sw3imp.cxx) and + // lcl_sw3io_InSetExpField (sw3field.cxx) now also + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_ABB), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_TABLE), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_FRAME), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_DRAWING), nsSwGetSetExpType::GSE_SEQ) ); + mpFieldTypes->emplace_back( new SwSetExpFieldType(&m_rDoc, + SwResId(STR_POOLCOLL_LABEL_FIGURE), nsSwGetSetExpType::GSE_SEQ) ); + + assert( mpFieldTypes->size() == INIT_FLDTYPES ); +} + +void DocumentFieldsManager::ClearFieldTypes() +{ + mpFieldTypes->erase( mpFieldTypes->begin() + INIT_FLDTYPES, mpFieldTypes->end() ); +} + +void DocumentFieldsManager::UpdateDBNumFields( SwDBNameInfField& rDBField, SwCalc& rCalc ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBField; + (void) rCalc; +#else + SwDBManager* pMgr = m_rDoc.GetDBManager(); + + SwFieldIds nFieldType = rDBField.Which(); + + bool bPar1 = rCalc.Calculate( rDBField.GetPar1() ).GetBool(); + + if( SwFieldIds::DbNextSet == nFieldType ) + static_cast<SwDBNextSetField&>(rDBField).SetCondValid( bPar1 ); + else + static_cast<SwDBNumSetField&>(rDBField).SetCondValid( bPar1 ); + + if( !rDBField.GetRealDBData().sDataSource.isEmpty() ) + { + // Edit a certain database + if( SwFieldIds::DbNextSet == nFieldType ) + static_cast<SwDBNextSetField&>(rDBField).Evaluate(&m_rDoc); + else + static_cast<SwDBNumSetField&>(rDBField).Evaluate(&m_rDoc); + + SwDBData aTmpDBData( rDBField.GetDBData(&m_rDoc) ); + + if( pMgr->OpenDataSource( aTmpDBData.sDataSource, aTmpDBData.sCommand )) + rCalc.VarChange( lcl_GetDBVarName( m_rDoc, rDBField), + pMgr->GetSelectedRecordId(aTmpDBData.sDataSource, aTmpDBData.sCommand, aTmpDBData.nCommandType) ); + } + else + { + OSL_FAIL("TODO: what should happen with unnamed DBFields?"); + } +#endif +} + +DocumentFieldsManager::~DocumentFieldsManager() +{ + mpUpdateFields.reset(); + mpFieldTypes.reset(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentLayoutManager.cxx b/sw/source/core/doc/DocumentLayoutManager.cxx new file mode 100644 index 000000000..8d5cc79dc --- /dev/null +++ b/sw/source/core/doc/DocumentLayoutManager.cxx @@ -0,0 +1,517 @@ +/* -*- 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 <DocumentLayoutManager.hxx> +#include <doc.hxx> +#include <IDocumentState.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <undobj.hxx> +#include <viewsh.hxx> +#include <layouter.hxx> +#include <poolfmt.hxx> +#include <frmfmt.hxx> +#include <fmtcntnt.hxx> +#include <fmtcnct.hxx> +#include <ndole.hxx> +#include <fmtanchr.hxx> +#include <txtflcnt.hxx> +#include <fmtflcnt.hxx> +#include <ndtxt.hxx> +#include <unoframe.hxx> +#include <docary.hxx> +#include <textboxhelper.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <frameformats.hxx> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <svx/svdobj.hxx> + +using namespace ::com::sun::star; + +namespace sw +{ + +DocumentLayoutManager::DocumentLayoutManager( SwDoc& i_rSwdoc ) : + m_rDoc( i_rSwdoc ), + mpCurrentView( nullptr ) +{ +} + +const SwViewShell *DocumentLayoutManager::GetCurrentViewShell() const +{ + return mpCurrentView; +} + +SwViewShell *DocumentLayoutManager::GetCurrentViewShell() +{ + return mpCurrentView; +} + +void DocumentLayoutManager::SetCurrentViewShell( SwViewShell* pNew ) +{ + mpCurrentView = pNew; +} + +// It must be able to communicate to a SwViewShell. This is going to be removed later. +const SwRootFrame *DocumentLayoutManager::GetCurrentLayout() const +{ + if(GetCurrentViewShell()) + return GetCurrentViewShell()->GetLayout(); + return nullptr; +} + +SwRootFrame *DocumentLayoutManager::GetCurrentLayout() +{ + if(GetCurrentViewShell()) + return GetCurrentViewShell()->GetLayout(); + return nullptr; +} + +bool DocumentLayoutManager::HasLayout() const +{ + // if there is a view, there is always a layout + return (mpCurrentView != nullptr); +} + +SwLayouter* DocumentLayoutManager::GetLayouter() +{ + return mpLayouter.get(); +} + +const SwLayouter* DocumentLayoutManager::GetLayouter() const +{ + return mpLayouter.get(); +} + +void DocumentLayoutManager::SetLayouter( SwLayouter* pNew ) +{ + mpLayouter.reset( pNew ); +} + +/** Create a new format whose settings fit to the Request by default. + + The format is put into the respective format array. + If there already is a fitting format, it is returned instead. */ +SwFrameFormat *DocumentLayoutManager::MakeLayoutFormat( RndStdIds eRequest, const SfxItemSet* pSet ) +{ + SwFrameFormat *pFormat = nullptr; + const bool bMod = m_rDoc.getIDocumentState().IsModified(); + bool bHeader = false; + + switch ( eRequest ) + { + case RndStdIds::HEADER: + case RndStdIds::HEADERL: + case RndStdIds::HEADERR: + { + bHeader = true; + [[fallthrough]]; + } + case RndStdIds::FOOTER: + { + pFormat = new SwFrameFormat( m_rDoc.GetAttrPool(), + (bHeader ? "Right header" : "Right footer"), + m_rDoc.GetDfltFrameFormat() ); + + SwNodeIndex aTmpIdx( m_rDoc.GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = + m_rDoc.GetNodes().MakeTextSection + ( aTmpIdx, + bHeader ? SwHeaderStartNode : SwFooterStartNode, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>( bHeader + ? ( eRequest == RndStdIds::HEADERL + ? RES_POOLCOLL_HEADERL + : eRequest == RndStdIds::HEADERR + ? RES_POOLCOLL_HEADERR + : RES_POOLCOLL_HEADER ) + : RES_POOLCOLL_FOOTER + ) ) ); + pFormat->SetFormatAttr( SwFormatContent( pSttNd )); + + if( pSet ) // Set a few more attributes + pFormat->SetFormatAttr( *pSet ); + + // Why set it back? Doc has changed, or not? + // In any case, wrong for the FlyFrames! + if ( !bMod ) + m_rDoc.getIDocumentState().ResetModified(); + } + break; + + case RndStdIds::DRAW_OBJECT: + { + pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() ); + if( pSet ) // Set a few more attributes + pFormat->SetFormatAttr( *pSet ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsLayFormat>(pFormat, 0, 0)); + } + } + break; + +#if OSL_DEBUG_LEVEL > 0 + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AT_FLY: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AS_CHAR: + OSL_FAIL( "use new interface instead: SwDoc::MakeFlySection!" ); + break; +#endif + + default: + OSL_ENSURE( false, + "LayoutFormat was requested with an invalid Request." ); + + } + return pFormat; +} + +/// Deletes the denoted format and its content. +void DocumentLayoutManager::DelLayoutFormat( SwFrameFormat *pFormat ) +{ + // A chain of frames needs to be merged, if necessary, + // so that the Frame's contents are adjusted accordingly before we destroy the Frames. + const SwFormatChain &rChain = pFormat->GetChain(); + if ( rChain.GetPrev() ) + { + SwFormatChain aChain( rChain.GetPrev()->GetChain() ); + aChain.SetNext( rChain.GetNext() ); + m_rDoc.SetAttr( aChain, *rChain.GetPrev() ); + } + if ( rChain.GetNext() ) + { + SwFormatChain aChain( rChain.GetNext()->GetChain() ); + aChain.SetPrev( rChain.GetPrev() ); + m_rDoc.SetAttr( aChain, *rChain.GetNext() ); + } + + const SwNodeIndex* pCntIdx = nullptr; + // The draw format doesn't own its content, it just has a pointer to it. + if (pFormat->Which() != RES_DRAWFRMFMT) + pCntIdx = pFormat->GetContent().GetContentIdx(); + if (pCntIdx && !m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // Disconnect if it's an OLE object + SwOLENode* pOLENd = m_rDoc.GetNodes()[ pCntIdx->GetIndex()+1 ]->GetOLENode(); + if( pOLENd && pOLENd->GetOLEObj().IsOleRef() ) + { + try + { + pOLENd->GetOLEObj().GetOleRef()->changeState( embed::EmbedStates::LOADED ); + } + catch ( uno::Exception& ) + { + } + } + } + + // Destroy Frames + pFormat->DelFrames(); + + // Only FlyFrames are undoable at first + const sal_uInt16 nWh = pFormat->Which(); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + (RES_FLYFRMFMT == nWh || RES_DRAWFRMFMT == nWh)) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelLayFormat>( pFormat )); + } + else + { + // #i32089# - delete at-frame anchored objects + if ( nWh == RES_FLYFRMFMT ) + { + // determine frame formats of at-frame anchored objects + const SwNodeIndex* pContentIdx = nullptr; + if (pFormat->Which() != RES_DRAWFRMFMT) + pContentIdx = pFormat->GetContent().GetContentIdx(); + if (pContentIdx) + { + const SwFrameFormats* pTable = pFormat->GetDoc()->GetSpzFrameFormats(); + if ( pTable ) + { + std::vector<SwFrameFormat*> aToDeleteFrameFormats; + const sal_uLong nNodeIdxOfFlyFormat( pContentIdx->GetIndex() ); + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat* pTmpFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pTmpFormat->GetAnchor(); + if ( rAnch.GetAnchorId() == RndStdIds::FLY_AT_FLY && + rAnch.GetContentAnchor()->nNode.GetIndex() == nNodeIdxOfFlyFormat ) + { + aToDeleteFrameFormats.push_back( pTmpFormat ); + } + } + + // delete found frame formats + while ( !aToDeleteFrameFormats.empty() ) + { + SwFrameFormat* pTmpFormat = aToDeleteFrameFormats.back(); + pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pTmpFormat ); + + aToDeleteFrameFormats.pop_back(); + } + } + } + } + + // Delete content + if( pCntIdx ) + { + SwNode *pNode = &pCntIdx->GetNode(); + const_cast<SwFormatContent&>(pFormat->GetFormatAttr( RES_CNTNT )).SetNewContentIdx( nullptr ); + m_rDoc.getIDocumentContentOperations().DeleteSection( pNode ); + } + + // Delete the character for FlyFrames anchored as char (if necessary) + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) && rAnchor.GetContentAnchor()) + { + const SwPosition* pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNd = pPos->nNode.GetNode().GetTextNode(); + + // attribute is still in text node, delete it + if ( pTextNd ) + { + SwTextFlyCnt* const pAttr = static_cast<SwTextFlyCnt*>( + pTextNd->GetTextAttrForCharAt( pPos->nContent.GetIndex(), + RES_TXTATR_FLYCNT )); + if ( pAttr && (pAttr->GetFlyCnt().GetFrameFormat() == pFormat) ) + { + // don't delete, set pointer to 0 + const_cast<SwFormatFlyCnt&>(pAttr->GetFlyCnt()).SetFlyFormat(); + SwIndex aIdx( pPos->nContent ); + pTextNd->EraseText( aIdx, 1 ); + } + } + } + + m_rDoc.DelFrameFormat( pFormat ); + } + m_rDoc.getIDocumentState().SetModified(); +} + +/** Copies the stated format (pSrc) to pDest and returns pDest. + + If there's no pDest, it is created. + If the source format is located in another document, also copy correctly + in this case. + The Anchor attribute's position is always set to 0! */ +SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat( + const SwFrameFormat& rSource, + const SwFormatAnchor& rNewAnchor, + bool bSetTextFlyAtt, + bool bMakeFrames ) +{ + const bool bFly = RES_FLYFRMFMT == rSource.Which(); + const bool bDraw = RES_DRAWFRMFMT == rSource.Which(); + OSL_ENSURE( bFly || bDraw, "this method only works for fly or draw" ); + + SwDoc* pSrcDoc = const_cast<SwDoc*>(rSource.GetDoc()); + + // May we copy this object? + // We may, unless it's 1) it's a control (and therefore a draw) + // 2) anchored in a header/footer + // 3) anchored (to paragraph?) + bool bMayNotCopy = false; + if(bDraw) + { + const auto pCAnchor = rNewAnchor.GetContentAnchor(); + bool bCheckControlLayer = false; + rSource.CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer)); + bMayNotCopy = + bCheckControlLayer && + ((RndStdIds::FLY_AT_PARA == rNewAnchor.GetAnchorId()) || (RndStdIds::FLY_AT_FLY == rNewAnchor.GetAnchorId()) || (RndStdIds::FLY_AT_CHAR == rNewAnchor.GetAnchorId())) && + pCAnchor && m_rDoc.IsInHeaderFooter(pCAnchor->nNode); + } + + // just return if we can't copy this + if( bMayNotCopy ) + return nullptr; + + SwFrameFormat* pDest = m_rDoc.GetDfltFrameFormat(); + if( rSource.GetRegisteredIn() != pSrcDoc->GetDfltFrameFormat() ) + pDest = m_rDoc.CopyFrameFormat( *static_cast<const SwFrameFormat*>(rSource.GetRegisteredIn()) ); + if( bFly ) + { + // #i11176# + // To do a correct cloning concerning the ZOrder for all objects + // it is necessary to actually create a draw object for fly frames, too. + // These are then added to the DrawingLayer (which needs to exist). + // Together with correct sorting of all drawinglayer based objects + // before cloning ZOrder transfer works correctly then. + SwFlyFrameFormat *pFormat = m_rDoc.MakeFlyFrameFormat( rSource.GetName(), pDest ); + pDest = pFormat; + + SwXFrame::GetOrCreateSdrObject(*pFormat); + } + else + pDest = m_rDoc.MakeDrawFrameFormat( OUString(), pDest ); + + // Copy all other or new attributes + pDest->CopyAttrs( rSource ); + + // Do not copy chains + pDest->ResetFormatAttr( RES_CHAIN ); + + if( bFly ) + { + // Duplicate the content. + const SwNode& rCSttNd = rSource.GetContent().GetContentIdx()->GetNode(); + SwNodeRange aRg( rCSttNd, 1, *rCSttNd.EndOfSectionNode() ); + + SwNodeIndex aIdx( m_rDoc.GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aIdx, SwFlyStartNode ); + + // Set the Anchor/ContentIndex first. + // Within the copying part, we can access the values (DrawFormat in Headers and Footers) + aIdx = *pSttNd; + SwFormatContent aAttr( rSource.GetContent() ); + aAttr.SetNewContentIdx( &aIdx ); + pDest->SetFormatAttr( aAttr ); + pDest->SetFormatAttr( rNewAnchor ); + + if( !m_rDoc.IsCopyIsMove() || &m_rDoc != pSrcDoc ) + { + if( m_rDoc.IsInReading() || m_rDoc.IsInMailMerge() ) + pDest->SetName( OUString() ); + else + { + // Test first if the name is already taken, if so generate a new one. + SwNodeType nNdTyp = aRg.aStart.GetNode().GetNodeType(); + + OUString sOld( pDest->GetName() ); + pDest->SetName( OUString() ); + if( m_rDoc.FindFlyByName( sOld, nNdTyp ) ) // found one + switch( nNdTyp ) + { + case SwNodeType::Grf: sOld = m_rDoc.GetUniqueGrfName(); break; + case SwNodeType::Ole: sOld = m_rDoc.GetUniqueOLEName(); break; + default: sOld = m_rDoc.GetUniqueFrameName(); break; + } + + pDest->SetName( sOld ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsLayFormat>(pDest,0,0)); + } + + // Make sure that FlyFrames in FlyFrames are copied + aIdx = *pSttNd->EndOfSectionNode(); + + //fdo#36631 disable (scoped) any undo operations associated with the + //contact object itself. They should be managed by SwUndoInsLayFormat. + const ::sw::DrawUndoGuard drawUndoGuard(m_rDoc.GetIDocumentUndoRedo()); + + pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aIdx, nullptr, false, true, true); + } + else + { + OSL_ENSURE( RES_DRAWFRMFMT == rSource.Which(), "Neither Fly nor Draw." ); + // #i52780# - Note: moving object to visible layer not needed. + rSource.CallSwClientNotify(sw::DrawFormatLayoutCopyHint(static_cast<SwDrawFrameFormat&>(*pDest), m_rDoc)); + + if(pDest->GetAnchor() == rNewAnchor) + { + // Do *not* connect to layout, if a <MakeFrames> will not be called. + if(bMakeFrames) + pDest->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::MAKE_FRAMES)); + + } + else + pDest->SetFormatAttr( rNewAnchor ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsLayFormat>(pDest,0,0)); + } + } + + if (bSetTextFlyAtt && (RndStdIds::FLY_AS_CHAR == rNewAnchor.GetAnchorId())) + { + const SwPosition* pPos = rNewAnchor.GetContentAnchor(); + SwFormatFlyCnt aFormat( pDest ); + pPos->nNode.GetNode().GetTextNode()->InsertItem( + aFormat, pPos->nContent.GetIndex(), 0 ); + } + + if( bMakeFrames ) + pDest->MakeFrames(); + + // If the draw format has a TextBox, then copy its fly format as well. + if (SwFrameFormat* pSourceTextBox = SwTextBoxHelper::getOtherTextBoxFormat(&rSource, RES_DRAWFRMFMT)) + { + SwFormatAnchor boxAnchor(rNewAnchor); + if (RndStdIds::FLY_AS_CHAR == boxAnchor.GetAnchorId()) + { + // AS_CHAR *must not* be set on textbox fly-frame + boxAnchor.SetType(RndStdIds::FLY_AT_CHAR); + } + // presumably these anchors are supported though not sure + assert(RndStdIds::FLY_AT_CHAR == boxAnchor.GetAnchorId() || RndStdIds::FLY_AT_PARA == boxAnchor.GetAnchorId()); + SwFrameFormat* pDestTextBox = CopyLayoutFormat(*pSourceTextBox, + boxAnchor, bSetTextFlyAtt, bMakeFrames); + SwAttrSet aSet(pDest->GetAttrSet()); + SwFormatContent aContent(pDestTextBox->GetContent().GetContentIdx()->GetNode().GetStartNode()); + aSet.Put(aContent); + pDest->SetFormatAttr(aSet); + + // Link FLY and DRAW formats, so it becomes a text box + pDest->SetOtherTextBoxFormat(pDestTextBox); + pDestTextBox->SetOtherTextBoxFormat(pDest); + } + + if (pDest->GetName().isEmpty()) + { + // Format name should have unique name. Let's use object name as a fallback + SdrObject *pObj = pDest->FindSdrObject(); + if (pObj) + pDest->SetName(pObj->GetName()); + } + + return pDest; +} + +//Load document from fdo#42534 under valgrind, drag the scrollbar down so full +//document layout is triggered. Close document before layout has completed, and +//SwAnchoredObject objects deleted by the deletion of layout remain referenced +//by the SwLayouter +void DocumentLayoutManager::ClearSwLayouterEntries() +{ + SwLayouter::ClearMovedFwdFrames( m_rDoc ); + SwLayouter::ClearObjsTmpConsiderWrapInfluence( m_rDoc ); + // #i65250# + SwLayouter::ClearMoveBwdLayoutInfo( m_rDoc ); +} + +DocumentLayoutManager::~DocumentLayoutManager() +{ +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/sw/source/core/doc/DocumentLinksAdministrationManager.cxx b/sw/source/core/doc/DocumentLinksAdministrationManager.cxx new file mode 100644 index 000000000..c5ca5b11e --- /dev/null +++ b/sw/source/core/doc/DocumentLinksAdministrationManager.cxx @@ -0,0 +1,583 @@ +/* -*- 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 <DocumentLinksAdministrationManager.hxx> + +#include <doc.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentMarkAccess.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/docfile.hxx> +#include <dialoghelp.hxx> +#include <linkenum.hxx> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <swtypes.hxx> +#include <docsh.hxx> +#include <bookmrk.hxx> +#include <swserv.hxx> +#include <swbaslnk.hxx> +#include <section.hxx> +#include <docary.hxx> +#include <frmfmt.hxx> +#include <fmtcntnt.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <frameformats.hxx> +#include <tools/urlobj.hxx> +#include <unotools/charclass.hxx> +#include <unotools/securityoptions.hxx> + +using namespace ::com::sun::star; + +//Helper functions for this file +namespace +{ + struct FindItem + { + const OUString m_Item; + SwTableNode* pTableNd; + SwSectionNode* pSectNd; + + explicit FindItem(const OUString& rS) + : m_Item(rS), pTableNd(nullptr), pSectNd(nullptr) + {} + }; + + ::sfx2::SvBaseLink* lcl_FindNextRemovableLink( const ::sfx2::SvBaseLinks& rLinks ) + { + for (const auto& rLinkIter : rLinks) + { + ::sfx2::SvBaseLink& rLnk = *rLinkIter; + if ((sfx2::SvBaseLinkObjectType::ClientGraphic == rLnk.GetObjType() || sfx2::SvBaseLinkObjectType::ClientFile == rLnk.GetObjType()) + && dynamic_cast<const SwBaseLink*>(&rLnk) != nullptr) + { + tools::SvRef<sfx2::SvBaseLink> xLink(&rLnk); + + OUString sFName; + sfx2::LinkManager::GetDisplayNames( xLink.get(), nullptr, &sFName ); + + INetURLObject aURL( sFName ); + if( INetProtocol::File == aURL.GetProtocol() || + INetProtocol::Cid == aURL.GetProtocol() ) + return &rLnk; + } + } + return nullptr; + } + + + ::sw::mark::DdeBookmark* lcl_FindDdeBookmark( const IDocumentMarkAccess& rMarkAccess, const OUString& rName, const bool bCaseSensitive ) + { + //Iterating over all bookmarks, checking DdeBookmarks + const OUString sNameLc = bCaseSensitive ? rName : GetAppCharClass().lowercase(rName); + for(IDocumentMarkAccess::const_iterator_t ppMark = rMarkAccess.getAllMarksBegin(); + ppMark != rMarkAccess.getAllMarksEnd(); + ++ppMark) + { + if (::sw::mark::DdeBookmark* const pBkmk = dynamic_cast< ::sw::mark::DdeBookmark*>(*ppMark)) + { + if ( + (bCaseSensitive && (pBkmk->GetName() == sNameLc)) || + (!bCaseSensitive && GetAppCharClass().lowercase(pBkmk->GetName()) == sNameLc) + ) + { + return pBkmk; + } + } + } + return nullptr; + } + + + bool lcl_FindSection( const SwSectionFormat* pSectFormat, FindItem * const pItem, bool bCaseSensitive ) + { + SwSection* pSect = pSectFormat->GetSection(); + if( pSect ) + { + OUString sNm( bCaseSensitive + ? pSect->GetSectionName() + : GetAppCharClass().lowercase( pSect->GetSectionName() )); + OUString sCompare( bCaseSensitive + ? pItem->m_Item + : GetAppCharClass().lowercase( pItem->m_Item ) ); + if( sNm == sCompare ) + { + // found, so get the data + const SwNodeIndex* pIdx = pSectFormat->GetContent().GetContentIdx(); + if( pIdx && &pSectFormat->GetDoc()->GetNodes() == &pIdx->GetNodes() ) + { + // a table in the normal NodesArr + pItem->pSectNd = pIdx->GetNode().GetSectionNode(); + return false; + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + } + return true; + } + + bool lcl_FindTable( const SwFrameFormat* pTableFormat, FindItem * const pItem ) + { + OUString sNm( GetAppCharClass().lowercase( pTableFormat->GetName() )); + if ( sNm == pItem->m_Item ) + { + SwTable* pTmpTable = SwTable::FindTable( pTableFormat ); + if( pTmpTable ) + { + SwTableBox* pFBox = pTmpTable->GetTabSortBoxes()[0]; + if( pFBox && pFBox->GetSttNd() && + &pTableFormat->GetDoc()->GetNodes() == &pFBox->GetSttNd()->GetNodes() ) + { + // a table in the normal NodesArr + pItem->pTableNd = const_cast<SwTableNode*>( + pFBox->GetSttNd()->FindTableNode()); + return false; + } + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + return true; + } + +} + + +namespace sw +{ + +DocumentLinksAdministrationManager::DocumentLinksAdministrationManager( SwDoc& i_rSwdoc ) + : mbVisibleLinks(true) + , mbLinksUpdated( false ) //#i38810# + , m_pLinkMgr( new sfx2::LinkManager(nullptr) ) + , m_rDoc( i_rSwdoc ) +{ +} + +bool DocumentLinksAdministrationManager::IsVisibleLinks() const +{ + return mbVisibleLinks; +} + +void DocumentLinksAdministrationManager::SetVisibleLinks(bool bFlag) +{ + mbVisibleLinks = bFlag; +} + +sfx2::LinkManager& DocumentLinksAdministrationManager::GetLinkManager() +{ + return *m_pLinkMgr; +} + +const sfx2::LinkManager& DocumentLinksAdministrationManager::GetLinkManager() const +{ + return *m_pLinkMgr; +} + +// #i42634# Moved common code of SwReader::Read() and SwDocShell::UpdateLinks() +// to new SwDoc::UpdateLinks(): +void DocumentLinksAdministrationManager::UpdateLinks() +{ + if (!m_rDoc.GetDocShell()) + return; + SfxObjectCreateMode eMode = m_rDoc.GetDocShell()->GetCreateMode(); + if (eMode == SfxObjectCreateMode::INTERNAL) + return; + if (eMode == SfxObjectCreateMode::ORGANIZER) + return; + if (m_rDoc.GetDocShell()->IsPreview()) + return; + if (GetLinkManager().GetLinks().empty()) + return; + sal_uInt16 nLinkMode = m_rDoc.GetDocumentSettingManager().getLinkUpdateMode(true); + sal_uInt16 nUpdateDocMode = m_rDoc.GetDocShell()->GetUpdateDocMode(); + if (nLinkMode == NEVER && nUpdateDocMode != document::UpdateDocMode::FULL_UPDATE) + return; + + bool bAskUpdate = nLinkMode == MANUAL; + bool bUpdate = true; + switch(nUpdateDocMode) + { + case document::UpdateDocMode::NO_UPDATE: bUpdate = false;break; + case document::UpdateDocMode::QUIET_UPDATE:bAskUpdate = false; break; + case document::UpdateDocMode::FULL_UPDATE: bAskUpdate = true; break; + } + if (nLinkMode == AUTOMATIC && !bAskUpdate) + { + SfxMedium * medium = m_rDoc.GetDocShell()->GetMedium(); + if (!SvtSecurityOptions().isTrustedLocationUriForUpdatingLinks( + medium == nullptr ? OUString() : medium->GetName())) + { + bAskUpdate = true; + } + } + comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = m_rDoc.GetDocShell()->getEmbeddedObjectContainer(); + if (bUpdate) + { + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(true); + + weld::Window* pDlgParent = GetFrameWeld(m_rDoc.GetDocShell()); + GetLinkManager().UpdateAllLinks(bAskUpdate, false, pDlgParent); + } + else + { + rEmbeddedObjectContainer.setUserAllowsLinkUpdate(false); + } +} + +bool DocumentLinksAdministrationManager::GetData( const OUString& rItem, const OUString& rMimeType, + uno::Any & rValue ) const +{ + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk) + return SwServerObject(*pBkmk).GetData(rValue, rMimeType); + + // Do we already have the Item? + OUString sItem( bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + // found, so get the data + return SwServerObject( *aPara.pSectNd ).GetData( rValue, rMimeType ); + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + FindItem aPara( GetAppCharClass().lowercase( rItem )); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if( aPara.pTableNd ) + { + return SwServerObject( *aPara.pTableNd ).GetData( rValue, rMimeType ); + } + + return false; +} + +void DocumentLinksAdministrationManager::SetData( const OUString& rItem ) +{ + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk) + { + return; + } + + // Do we already have the Item? + OUString sItem( bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + // found, so get the data + return; + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + OUString sItem(GetAppCharClass().lowercase(rItem)); + FindItem aPara( sItem ); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } +} + +::sfx2::SvLinkSource* DocumentLinksAdministrationManager::CreateLinkSource(const OUString& rItem) +{ + SwServerObject* pObj = nullptr; + + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + // bookmarks + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk && pBkmk->IsExpanded()) + { + pObj = pBkmk->GetRefObject(); + if( !pObj ) + { + // mark found, but no link yet -> create hotlink + pObj = new SwServerObject(*pBkmk); + pBkmk->SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + } + if(pObj) + return pObj; + + FindItem aPara(bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem)); + // sections + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + + if(aPara.pSectNd) + { + pObj = aPara.pSectNd->GetSection().GetObject(); + if( !pObj ) + { + // section found, but no link yet -> create hotlink + pObj = new SwServerObject( *aPara.pSectNd ); + aPara.pSectNd->GetSection().SetRefObject( pObj ); + GetLinkManager().InsertServer(pObj); + } + } + if(pObj) + return pObj; + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + FindItem aPara( GetAppCharClass().lowercase(rItem) ); + // tables + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if(aPara.pTableNd) + { + pObj = aPara.pTableNd->GetTable().GetObject(); + if( !pObj ) + { + // table found, but no link yet -> create hotlink + pObj = new SwServerObject(*aPara.pTableNd); + aPara.pTableNd->GetTable().SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + } + return pObj; +} + +/// embedded all local links (Areas/Graphics) +bool DocumentLinksAdministrationManager::EmbedAllLinks() +{ + bool bRet = false; + sfx2::LinkManager& rLnkMgr = GetLinkManager(); + const ::sfx2::SvBaseLinks& rLinks = rLnkMgr.GetLinks(); + if( !rLinks.empty() ) + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + + ::sfx2::SvBaseLink* pLnk = nullptr; + while( nullptr != (pLnk = lcl_FindNextRemovableLink( rLinks ) ) ) + { + tools::SvRef<sfx2::SvBaseLink> xLink = pLnk; + // Tell the link that it's being destroyed! + xLink->Closed(); + + // if one forgot to remove itself + if( xLink.is() ) + rLnkMgr.Remove( xLink.get() ); + + bRet = true; + } + + m_rDoc.GetIDocumentUndoRedo().DelAllUndoObj(); + m_rDoc.getIDocumentState().SetModified(); + } + return bRet; +} + +void DocumentLinksAdministrationManager::SetLinksUpdated(const bool bNewLinksUpdated) +{ + mbLinksUpdated = bNewLinksUpdated; +} + +bool DocumentLinksAdministrationManager::LinksUpdated() const +{ + return mbLinksUpdated; +} + +DocumentLinksAdministrationManager::~DocumentLinksAdministrationManager() +{ +} + +bool DocumentLinksAdministrationManager::SelectServerObj( const OUString& rStr, SwPaM*& rpPam, std::unique_ptr<SwNodeRange>& rpRange ) const +{ + // Do we actually have the Item? + rpPam = nullptr; + rpRange = nullptr; + + OUString sItem( INetURLObject::decode( rStr, + INetURLObject::DecodeMechanism::WithCharset )); + + sal_Int32 nPos = sItem.indexOf( cMarkSeparator ); + + const CharClass& rCC = GetAppCharClass(); + + // Extension for sections: not only link bookmarks/sections + // but also frames (text!), tables, outlines: + if( -1 != nPos ) + { + bool bContinue = false; + OUString sName( sItem.copy( 0, nPos ) ); + OUString sCmp( sItem.copy( nPos + 1 )); + sItem = rCC.lowercase( sItem ); + + FindItem aPara( sName ); + + if( sCmp == "table" ) + { + sName = rCC.lowercase( sName ); + for( const SwFrameFormat* pFormat : *m_rDoc.GetTableFrameFormats() ) + { + if (!(lcl_FindTable(pFormat, &aPara))) + break; + } + if( aPara.pTableNd ) + { + rpRange.reset(new SwNodeRange( *aPara.pTableNd, 0, + *aPara.pTableNd->EndOfSectionNode(), 1 )); + return true; + } + } + else if( sCmp == "frame" ) + { + SwNodeIndex* pIdx; + SwNode* pNd; + const SwFlyFrameFormat* pFlyFormat = m_rDoc.FindFlyByName( sName ); + if( pFlyFormat ) + { + pIdx = const_cast<SwNodeIndex*>(pFlyFormat->GetContent().GetContentIdx()); + if( pIdx ) + { + pNd = &pIdx->GetNode(); + if( !pNd->IsNoTextNode() ) + { + rpRange.reset(new SwNodeRange( *pNd, 1, *pNd->EndOfSectionNode() )); + return true; + } + } + } + } + else if( sCmp == "region" ) + { + sItem = sName; // Is being dealt with further down! + bContinue = true; + } + else if( sCmp == "outline" ) + { + SwPosition aPos( SwNodeIndex( m_rDoc.GetNodes() )); + if (m_rDoc.GotoOutline(aPos, sName, nullptr)) + { + SwNode* pNd = &aPos.nNode.GetNode(); + const int nLvl = pNd->GetTextNode()->GetAttrOutlineLevel()-1; + + const SwOutlineNodes& rOutlNds = m_rDoc.GetNodes().GetOutLineNds(); + SwOutlineNodes::size_type nTmpPos; + (void)rOutlNds.Seek_Entry( pNd, &nTmpPos ); + rpRange.reset(new SwNodeRange( aPos.nNode, 0, aPos.nNode )); + + // look for the section's end, now + for( ++nTmpPos; + nTmpPos < rOutlNds.size() && + nLvl < rOutlNds[ nTmpPos ]->GetTextNode()-> + GetAttrOutlineLevel()-1; + ++nTmpPos ) + ; // there is no block + + if( nTmpPos < rOutlNds.size() ) + rpRange->aEnd = *rOutlNds[ nTmpPos ]; + else + rpRange->aEnd = m_rDoc.GetNodes().GetEndOfContent(); + return true; + } + } + + if( !bContinue ) + return false; + } + + // search for bookmarks and sections case sensitive at first. If nothing is found then try again case insensitive + bool bCaseSensitive = true; + while( true ) + { + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), sItem, bCaseSensitive); + if(pBkmk) + { + if(pBkmk->IsExpanded()) + rpPam = new SwPaM( + pBkmk->GetMarkPos(), + pBkmk->GetOtherMarkPos()); + return static_cast<bool>(rpPam); + } + + FindItem aPara( bCaseSensitive ? sItem : rCC.lowercase( sItem ) ); + + if( !m_rDoc.GetSections().empty() ) + { + for( const SwSectionFormat* pFormat : m_rDoc.GetSections() ) + { + if (!(lcl_FindSection(pFormat, &aPara, bCaseSensitive))) + break; + } + if( aPara.pSectNd ) + { + rpRange.reset(new SwNodeRange( *aPara.pSectNd, 1, + *aPara.pSectNd->EndOfSectionNode() )); + return true; + + } + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + return false; +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentListItemsManager.cxx b/sw/source/core/doc/DocumentListItemsManager.cxx new file mode 100644 index 000000000..2a8f0691d --- /dev/null +++ b/sw/source/core/doc/DocumentListItemsManager.cxx @@ -0,0 +1,105 @@ +/* -*- 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 <DocumentListItemsManager.hxx> + +#include <SwNodeNum.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <osl/diagnose.h> + +namespace sw +{ + +DocumentListItemsManager::DocumentListItemsManager() : mpListItemsList( new tImplSortedNodeNumList ) // #i83479# +{ +} + +bool DocumentListItemsManager::lessThanNodeNum::operator()( const SwNodeNum* pNodeNumOne, + const SwNodeNum* pNodeNumTwo ) const +{ + return pNodeNumOne->LessThan( *pNodeNumTwo ); +} + +void DocumentListItemsManager::addListItem( const SwNodeNum& rNodeNum ) +{ + if ( mpListItemsList == nullptr ) + { + return; + } + + const bool bAlreadyInserted( + mpListItemsList->insert( &rNodeNum ).second ); + OSL_ENSURE( bAlreadyInserted, + "<DocumentListItemsManager::addListItem(..)> - <SwNodeNum> instance already registered as numbered item!" ); +} + +void DocumentListItemsManager::removeListItem( const SwNodeNum& rNodeNum ) +{ + if ( mpListItemsList == nullptr ) + { + return; + } + + const tImplSortedNodeNumList::size_type nDeleted = mpListItemsList->erase( &rNodeNum ); + if ( nDeleted > 1 ) + { + OSL_FAIL( "<DocumentListItemsManager::removeListItem(..)> - <SwNodeNum> was registered more than once as numbered item!" ); + } +} + +OUString DocumentListItemsManager::getListItemText(const SwNodeNum& rNodeNum, + SwRootFrame const& rLayout) const +{ + SwTextNode const*const pNode(rNodeNum.GetTextNode()); + assert(pNode); + return sw::GetExpandTextMerged(&rLayout, *pNode, true, true, ExpandMode::ExpandFootnote); +} + +bool DocumentListItemsManager::isNumberedInLayout( + SwNodeNum const& rNodeNum, // note: this is the non-hidden Num ... + SwRootFrame const& rLayout) const +{ + return sw::IsParaPropsNode(rLayout, *rNodeNum.GetTextNode()); +} + +void DocumentListItemsManager::getNumItems( tSortedNodeNumList& orNodeNumList ) const +{ + orNodeNumList.clear(); + orNodeNumList.reserve( mpListItemsList->size() ); + + for ( const SwNodeNum* pNodeNum : *mpListItemsList ) + { + if ( pNodeNum->IsCounted() && + pNodeNum->GetTextNode() && pNodeNum->GetTextNode()->HasNumber() ) + { + orNodeNumList.push_back( pNodeNum ); + } + } +} + +DocumentListItemsManager::~DocumentListItemsManager() +{ +} + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentListsManager.cxx b/sw/source/core/doc/DocumentListsManager.cxx new file mode 100644 index 000000000..d74e924fd --- /dev/null +++ b/sw/source/core/doc/DocumentListsManager.cxx @@ -0,0 +1,214 @@ +/* -*- 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 <DocumentListsManager.hxx> +#include <doc.hxx> +#include <list.hxx> +#include <numrule.hxx> + +#include <comphelper/random.hxx> +#include <osl/diagnose.h> + + +namespace sw +{ + +DocumentListsManager::DocumentListsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), maLists(), maListStyleLists() +{ +} + +SwList* DocumentListsManager::createList( const OUString& rListId, + const OUString& sDefaultListStyleName ) +{ + OUString sListId = rListId; + if ( sListId.isEmpty() ) + { + sListId = CreateUniqueListId(); + } + + if ( getListByName( sListId ) ) + { + OSL_FAIL( "<DocumentListsManager::createList(..)> - provided list id already used. Serious defect." ); + return nullptr; + } + + SwNumRule* pDefaultNumRuleForNewList = m_rDoc.FindNumRulePtr( sDefaultListStyleName ); + if ( !pDefaultNumRuleForNewList ) + { + OSL_FAIL( "<DocumentListsManager::createList(..)> - for provided default list style name no list style is found. Serious defect." ); + return nullptr; + } + + SwList* pNewList = new SwList( sListId, *pDefaultNumRuleForNewList, m_rDoc.GetNodes() ); + maLists[sListId].reset(pNewList); + + return pNewList; +} + +SwList* DocumentListsManager::getListByName( const OUString& sListId ) const +{ + SwList* pList = nullptr; + + auto aListIter = maLists.find( sListId ); + if ( aListIter != maLists.end() ) + { + pList = (*aListIter).second.get(); + } + + return pList; +} + +void DocumentListsManager::createListForListStyle( const OUString& sListStyleName ) +{ + if ( sListStyleName.isEmpty() ) + { + OSL_FAIL( "<DocumentListsManager::createListForListStyle(..)> - no list style name provided. Serious defect." ); + return; + } + + if ( getListForListStyle( sListStyleName ) ) + { + OSL_FAIL( "<DocumentListsManager::createListForListStyle(..)> - a list for the provided list style name already exists. Serious defect." ); + return; + } + + SwNumRule* pNumRule = m_rDoc.FindNumRulePtr( sListStyleName ); + if ( !pNumRule ) + { + OSL_FAIL( "<DocumentListsManager::createListForListStyle(..)> - for provided list style name no list style is found. Serious defect." ); + return; + } + + OUString sListId( pNumRule->GetDefaultListId() ); // can be empty String + if ( getListByName( sListId ) ) + { + sListId.clear(); + } + SwList* pNewList = createList( sListId, sListStyleName ); + maListStyleLists[sListStyleName] = pNewList; + pNumRule->SetDefaultListId( pNewList->GetListId() ); +} + +SwList* DocumentListsManager::getListForListStyle( const OUString& sListStyleName ) const +{ + SwList* pList = nullptr; + + std::unordered_map< OUString, SwList* >::const_iterator + aListIter = maListStyleLists.find( sListStyleName ); + if ( aListIter != maListStyleLists.end() ) + { + pList = (*aListIter).second; + } + + return pList; +} + +void DocumentListsManager::deleteListForListStyle( const OUString& sListStyleName ) +{ + OUString sListId; + { + SwList* pList = getListForListStyle( sListStyleName ); + OSL_ENSURE( pList, + "<DocumentListsManager::deleteListForListStyle(..)> - misusage of method: no list found for given list style name" ); + if ( pList ) + { + sListId = pList->GetListId(); + } + } + if ( !sListId.isEmpty() ) + { + maListStyleLists.erase( sListStyleName ); + maLists.erase( sListId ); + } +} + +void DocumentListsManager::deleteListsByDefaultListStyle( const OUString& rListStyleName ) +{ + auto aListIter = maLists.begin(); + while ( aListIter != maLists.end() ) + { + if ( (*aListIter).second->GetDefaultListStyleName() == rListStyleName ) + { + aListIter = maLists.erase(aListIter); + } + else + ++aListIter; + } +} + +void DocumentListsManager::trackChangeOfListStyleName( const OUString& sListStyleName, + const OUString& sNewListStyleName ) +{ + SwList* pList = getListForListStyle( sListStyleName ); + OSL_ENSURE( pList, + "<DocumentListsManager::changeOfListStyleName(..)> - misusage of method: no list found for given list style name" ); + + if ( pList != nullptr ) + { + maListStyleLists.erase( sListStyleName ); + maListStyleLists[sNewListStyleName] = pList; + } + for (auto & it : maLists) // tdf#91131 update these references too + { + if (it.second->GetDefaultListStyleName() == sListStyleName) + { + it.second->SetDefaultListStyleName(sNewListStyleName); + } + } +} + + +DocumentListsManager::~DocumentListsManager() +{ +} + + +OUString DocumentListsManager::MakeListIdUnique( const OUString& aSuggestedUniqueListId ) +{ + long nHitCount = 0; + OUString aTmpStr = aSuggestedUniqueListId; + while ( getListByName( aTmpStr ) ) + { + ++nHitCount; + aTmpStr = aSuggestedUniqueListId + OUString::number( nHitCount ); + } + + return aTmpStr; +} + +OUString DocumentListsManager::CreateUniqueListId() +{ + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + if (bHack) + { + static sal_Int64 nIdCounter = SAL_CONST_INT64(7000000000); + return MakeListIdUnique( OUString( "list" + OUString::number(nIdCounter++) ) ); + } + else + { + // #i92478# + unsigned int const n(comphelper::rng::uniform_uint_distribution(0, + std::numeric_limits<unsigned int>::max())); + OUString const aNewListId = "list" + OUString::number(n); + return MakeListIdUnique( aNewListId ); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentOutlineNodesManager.cxx b/sw/source/core/doc/DocumentOutlineNodesManager.cxx new file mode 100644 index 000000000..25f381476 --- /dev/null +++ b/sw/source/core/doc/DocumentOutlineNodesManager.cxx @@ -0,0 +1,131 @@ +/* -*- 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 <DocumentOutlineNodesManager.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <modeltoviewhelper.hxx> +#include <rtl/ustrbuf.hxx> + +namespace sw +{ + +DocumentOutlineNodesManager::DocumentOutlineNodesManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +IDocumentOutlineNodes::tSortedOutlineNodeList::size_type DocumentOutlineNodesManager::getOutlineNodesCount() const +{ + return m_rDoc.GetNodes().GetOutLineNds().size(); +} + +int DocumentOutlineNodesManager::getOutlineLevel( const tSortedOutlineNodeList::size_type nIdx ) const +{ + return m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]-> + GetTextNode()->GetAttrOutlineLevel()-1; +} + +OUString GetExpandTextMerged(SwRootFrame const*const pLayout, + SwTextNode const& rNode, bool const bWithNumber, + bool const bWithSpacesForLevel, ExpandMode const i_mode) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(rNode.getLayoutFrame(pLayout))); + if (pFrame) + { + sw::MergedPara const*const pMerged = pFrame->GetMergedPara(); + if (pMerged) + { + if (&rNode != pMerged->pParaPropsNode) + { + return OUString(); + } + else + { + ExpandMode const mode(ExpandMode::HideDeletions | i_mode); + OUStringBuffer ret(rNode.GetExpandText(pLayout, 0, -1, + bWithNumber, bWithNumber, bWithSpacesForLevel, mode)); + for (sal_uLong i = rNode.GetIndex() + 1; + i <= pMerged->pLastNode->GetIndex(); ++i) + { + SwNode *const pTmp(rNode.GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + ret.append(pTmp->GetTextNode()->GetExpandText( + pLayout, 0, -1, false, false, false, mode)); + } + } + return ret.makeStringAndClear(); + } + } + } + } + return rNode.GetExpandText(pLayout, 0, -1, bWithNumber, + bWithNumber, bWithSpacesForLevel, i_mode); +} + +OUString DocumentOutlineNodesManager::getOutlineText( + const tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const*const pLayout, + const bool bWithNumber, + const bool bWithSpacesForLevel, + const bool bWithFootnote ) const +{ + SwTextNode const*const pNode(m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode()); + return GetExpandTextMerged(pLayout, *pNode, + bWithNumber, bWithSpacesForLevel, + (bWithFootnote ? ExpandMode::ExpandFootnote : ExpandMode(0))); +} + +SwTextNode* DocumentOutlineNodesManager::getOutlineNode( const tSortedOutlineNodeList::size_type nIdx ) const +{ + return m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode(); +} + +bool DocumentOutlineNodesManager::isOutlineInLayout( + const tSortedOutlineNodeList::size_type nIdx, + SwRootFrame const& rLayout) const +{ + auto const pNode(m_rDoc.GetNodes().GetOutLineNds()[ nIdx ]->GetTextNode()); + return sw::IsParaPropsNode(rLayout, *pNode); +} + +void DocumentOutlineNodesManager::getOutlineNodes( IDocumentOutlineNodes::tSortedOutlineNodeList& orOutlineNodeList ) const +{ + orOutlineNodeList.clear(); + orOutlineNodeList.reserve( getOutlineNodesCount() ); + + const tSortedOutlineNodeList::size_type nOutlCount = getOutlineNodesCount(); + for ( tSortedOutlineNodeList::size_type i = 0; i < nOutlCount; ++i ) + { + orOutlineNodeList.push_back( + m_rDoc.GetNodes().GetOutLineNds()[i]->GetTextNode() ); + } +} + +DocumentOutlineNodesManager::~DocumentOutlineNodesManager() +{ +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx new file mode 100644 index 000000000..f3aaa13a6 --- /dev/null +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -0,0 +1,3233 @@ +/* -*- 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 <DocumentRedlineManager.hxx> +#include <frmfmt.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <fmtfld.hxx> +#include <frmtool.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentState.hxx> +#include <redline.hxx> +#include <UndoRedline.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <unocrsr.hxx> +#include <ftnidx.hxx> +#include <authfld.hxx> +#include <strings.hrc> +#include <swmodule.hxx> + +using namespace com::sun::star; + +#ifdef DBG_UTIL + + #define ERROR_PREFIX "redline table corrupted: " + + namespace + { + // helper function for lcl_CheckRedline + // 1. make sure that pPos->nContent points into pPos->nNode + // 2. check that position is valid and doesn't point after text + void lcl_CheckPosition( const SwPosition* pPos ) + { + assert(dynamic_cast<SwIndexReg*>(&pPos->nNode.GetNode()) + == pPos->nContent.GetIdxReg()); + + SwTextNode* pTextNode = pPos->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + assert(pPos->nContent == 0); + } + else + { + assert(pPos->nContent >= 0 && pPos->nContent <= pTextNode->Len()); + } + } + + void lcl_CheckPam( const SwPaM* pPam ) + { + assert(pPam); + lcl_CheckPosition( pPam->GetPoint() ); + lcl_CheckPosition( pPam->GetMark() ); + } + + // check validity of the redline table. Checks redline bounds, and make + // sure the redlines are sorted and non-overlapping. + void lcl_CheckRedline( IDocumentRedlineAccess& redlineAccess ) + { + const SwRedlineTable& rTable = redlineAccess.GetRedlineTable(); + + // verify valid redline positions + for(SwRangeRedline* i : rTable) + lcl_CheckPam( i ); + + for(SwRangeRedline* j : rTable) + { + // check for empty redlines + // note: these can destroy sorting in SwTextNode::Update() + // if there's another one without mark on the same pos. + OSL_ENSURE( ( *(j->GetPoint()) != *(j->GetMark()) ) || + ( j->GetContentIdx() != nullptr ), + ERROR_PREFIX "empty redline" ); + } + + // verify proper redline sorting + for( size_t n = 1; n < rTable.size(); ++n ) + { + const SwRangeRedline* pPrev = rTable[ n-1 ]; + const SwRangeRedline* pCurrent = rTable[ n ]; + + // check redline sorting + SAL_WARN_IF( *pPrev->Start() > *pCurrent->Start(), "sw", + ERROR_PREFIX "not sorted correctly" ); + + // check for overlapping redlines + SAL_WARN_IF( *pPrev->End() > *pCurrent->Start(), "sw", + ERROR_PREFIX "overlapping redlines" ); + } + + assert(std::is_sorted(rTable.begin(), rTable.end(), CompareSwRedlineTable())); + } + } + + #define CHECK_REDLINE( pDoc ) lcl_CheckRedline( pDoc ); + +#else + + #define CHECK_REDLINE( pDoc ) + +#endif + +namespace sw { + +static void UpdateFieldsForRedline(IDocumentFieldsAccess & rIDFA) +{ + auto const pAuthType(static_cast<SwAuthorityFieldType*>(rIDFA.GetFieldType( + SwFieldIds::TableOfAuthorities, OUString(), false))); + if (pAuthType) // created on demand... + { + pAuthType->DelSequenceArray(); + } + rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields(); + rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields(); + rIDFA.UpdateExpFields(nullptr, false); + rIDFA.UpdateRefFields(); +} + +void UpdateFramesForAddDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) +{ + // no need to call UpdateFootnoteNums for FTNNUM_PAGE: + // the AppendFootnote/RemoveFootnote will do it by itself! + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); + while (!pStartNode) + { + SwStartNode *const pTableOrSectionNode( + currentStart.nNode.GetNode().IsTableNode() + ? static_cast<SwStartNode*>(currentStart.nNode.GetNode().GetTableNode()) + : static_cast<SwStartNode*>(currentStart.nNode.GetNode().GetSectionNode())); + assert(pTableOrSectionNode); // known pathology + for (sal_uLong j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + for (SwRootFrame const*const pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->IsHideRedlines()) + { + if (pTableOrSectionNode->IsTableNode()) + { + static_cast<SwTableNode*>(pTableOrSectionNode)->DelFrames(pLayout); + } + else + { + static_cast<SwSectionNode*>(pTableOrSectionNode)->DelFrames(pLayout); + } + } + } + currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; + currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); + pStartNode = currentStart.nNode.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + if (frames.empty()) + { + auto const& layouts(rDoc.GetAllLayouts()); + assert(std::none_of(layouts.begin(), layouts.end(), + [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); + (void) layouts; + break; + } + auto eMode(sw::FrameMode::Existing); + SwTextNode * pLast(pNode); + for (SwTextFrame * pFrame : frames) + { + SwTextNode & rFirstNode(pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pFirstNode + : *pNode); + assert(pNode == pStartNode + ? rFirstNode.GetIndex() <= pNode->GetIndex() + : &rFirstNode == pNode); + // clear old one first to avoid DelFrames confusing updates & asserts... + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + // the first node of the new redline is not necessarily the first + // node of the merged frame, there could be another redline nearby + sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, *pNode, nullptr); + // if redline is split across table and table cell is empty, there's no redline in the cell and so no merged para + if (pFrame->GetMergedPara()) + { + pLast = const_cast<SwTextNode*>(pFrame->GetMergedPara()->pLastNode); + } + } + SwNodeIndex tmp(*pLast); + // skip over hidden sections! + pNode = static_cast<SwTextNode*>(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); + } + while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); + } + // fields last - SwGetRefField::UpdateField requires up-to-date frames + UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes + + // update SwPostItMgr / notes in the margin + rDoc.GetDocShell()->Broadcast( + SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::REMOVED) ); +} + +void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) +{ + bool isAppendObjsCalled(false); + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->nNode); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->nNode.GetNode().GetTextNode()); + while (!pStartNode) + { + SwStartNode const*const pTableOrSectionNode( + currentStart.nNode.GetNode().IsTableNode() + ? static_cast<SwStartNode*>(currentStart.nNode.GetNode().GetTableNode()) + : static_cast<SwStartNode*>(currentStart.nNode.GetNode().GetSectionNode())); + assert(pTableOrSectionNode); // known pathology + for (sal_uLong j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::None); + } + if (rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->IsHideRedlines()) + { + // note: this will also create frames for all currently hidden flys + // because it calls AppendAllObjs + SwNodeIndex const end(*pTableOrSectionNode->EndOfSectionNode()); + ::MakeFrames(&rDoc, currentStart.nNode, end); + isAppendObjsCalled = true; + } + currentStart.nNode = pTableOrSectionNode->EndOfSectionIndex() + 1; + currentStart.nContent.Assign(currentStart.nNode.GetNode().GetContentNode(), 0); + pStartNode = currentStart.nNode.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->IsHideRedlines()) + { + frames.push_back(pFrame); + } + } + if (frames.empty()) + { + auto const& layouts(rDoc.GetAllLayouts()); + assert(std::none_of(layouts.begin(), layouts.end(), + [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); + (void) layouts; + break; + } + + // first, call CheckParaRedlineMerge on the first paragraph, + // to init flag on new merge range (if any) + 1st node post the merge + auto eMode(sw::FrameMode::Existing); + SwTextNode * pLast(pNode); + for (SwTextFrame * pFrame : frames) + { + if (auto const pMergedPara = pFrame->GetMergedPara()) + { + pLast = const_cast<SwTextNode*>(pMergedPara->pLastNode); + assert(pNode == pStartNode + ? pMergedPara->pFirstNode->GetIndex() <= pNode->GetIndex() + : pMergedPara->pFirstNode == pNode); + // clear old one first to avoid DelFrames confusing updates & asserts... + SwTextNode & rFirstNode(*pMergedPara->pFirstNode); + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + } + } + if (pLast != pNode) + { + // now start node until end of merge + 1 has proper flags; MakeFrames + // should pick up from the next node in need of frames by checking flags + SwNodeIndex const start(*pNode, +1); + SwNodeIndex const end(*pLast, +1); // end is exclusive + // note: this will also create frames for all currently hidden flys + // both on first and non-first nodes because it calls AppendAllObjs + ::MakeFrames(&rDoc, start, end); + isAppendObjsCalled = true; + // re-use this to move flys that are now on the wrong frame, with end + // of redline as "second" node; the nodes between start and end should + // be complete with MakeFrames already + sw::MoveMergedFlysAndFootnotes(frames, *pNode, *pLast, false); + } + SwNodeIndex tmp(*pLast); + // skip over hidden sections! + pNode = static_cast<SwTextNode*>(pLast->GetNodes().GoNextSection(&tmp, /*bSkipHidden=*/true, /*bSkipProtect=*/false)); + } + while (pNode && pNode->GetIndex() <= rPam.End()->nNode.GetIndex()); + } + + if (!isAppendObjsCalled) + { // recreate flys in the one node the hard way... + for (auto const& pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->IsHideRedlines()) + { + AppendAllObjs(rDoc.GetSpzFrameFormats(), pLayout); + break; + } + } + } + // fields last - SwGetRefField::UpdateField requires up-to-date frames + UpdateFieldsForRedline(rDoc.getIDocumentFieldsAccess()); // after footnotes + + // update SwPostItMgr / notes in the margin + rDoc.GetDocShell()->Broadcast( + SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::INSERTED) ); +} + +} // namespace sw + +namespace +{ + bool IsPrevPos( const SwPosition & rPos1, const SwPosition & rPos2 ) + { + const SwContentNode* pCNd; + if( 0 != rPos2.nContent.GetIndex() ) + return false; + if( rPos2.nNode.GetIndex() - 1 != rPos1.nNode.GetIndex() ) + return false; + pCNd = rPos1.nNode.GetNode().GetContentNode(); + return pCNd && rPos1.nContent.GetIndex() == pCNd->Len(); + } + + // copy style or return with SwRedlineExtra_FormatColl with reject data of the upcoming copy + SwRedlineExtraData_FormatColl* lcl_CopyStyle( const SwPosition & rFrom, const SwPosition & rTo, bool bCopy = true ) + { + SwTextNode* pToNode = rTo.nNode.GetNode().GetTextNode(); + SwTextNode* pFromNode = rFrom.nNode.GetNode().GetTextNode(); + if (pToNode != nullptr && pFromNode != nullptr && pToNode != pFromNode) + { + const SwPaM aPam(*pToNode); + SwDoc* pDoc = aPam.GetDoc(); + // using Undo, copy paragraph style + SwTextFormatColl* pFromColl = pFromNode->GetTextColl(); + SwTextFormatColl* pToColl = pToNode->GetTextColl(); + if (bCopy && pFromColl != pToColl) + pDoc->SetTextFormatColl(aPam, pFromColl); + + // using Undo, remove direct paragraph formatting of the "To" paragraph, + // and apply here direct paragraph formatting of the "From" paragraph + SfxItemSet aTmp( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_BEGIN, RES_PARATR_END - 3, // skip RSID and GRABBAG + RES_PARATR_LIST_BEGIN, RES_UL_SPACE, // skip PAGEDESC and BREAK + RES_CNTNT, RES_FRMATR_END - 1>{}); + SfxItemSet aTmp2(aTmp); + + pToNode->GetParaAttr(aTmp, 0, 0); + pFromNode->GetParaAttr(aTmp2, 0, 0); + + bool bSameSet = aTmp == aTmp2; + + if (!bSameSet) + { + for( sal_uInt16 nItem = 0; nItem < aTmp.TotalCount(); ++nItem) + { + sal_uInt16 nWhich = aTmp.GetWhichByPos(nItem); + if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) && + SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) ) + aTmp2.Put( aTmp.GetPool()->GetDefaultItem(nWhich), nWhich ); + } + } + + if (bCopy && !bSameSet) + pDoc->getIDocumentContentOperations().InsertItemSet(aPam, aTmp2); + else if (!bCopy && (!bSameSet || pFromColl != pToColl)) + return new SwRedlineExtraData_FormatColl( pFromColl->GetName(), USHRT_MAX, &aTmp2 ); + } + return nullptr; + } + + bool lcl_AcceptRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng = nullptr, + const SwPosition* pEndRng = nullptr ) + { + bool bRet = true; + SwRangeRedline* pRedl = rArr[ rPos ]; + SwPosition *pRStt = nullptr, *pREnd = nullptr; + SwComparePosition eCmp = SwComparePosition::Outside; + if( pSttRng && pEndRng ) + { + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); + } + + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + case RedlineType::Format: + { + bool bCheck = false, bReplace = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( *pSttRng == *pRStt ) + pRedl->SetStart( *pEndRng, pRStt ); + else + { + if( *pEndRng != *pREnd ) + { + // split up + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEndRng ); + rArr.Insert( pNew ); ++rPos; + } + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + } + break; + + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + + if( bReplace || ( bCheck && !pRedl->HasValidRange() )) + { + // re-insert + rArr.Remove( pRedl ); + rArr.Insert( pRedl ); + } + } + break; + case RedlineType::Delete: + { + SwDoc& rDoc = *pRedl->GetDoc(); + const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; + bool bDelRedl = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( bCallDelete ) + { + pDelStt = pSttRng; + pDelEnd = pEndRng; + } + break; + + case SwComparePosition::OverlapBefore: + if( bCallDelete ) + { + pDelStt = pRStt; + pDelEnd = pEndRng; + } + break; + case SwComparePosition::OverlapBehind: + if( bCallDelete ) + { + pDelStt = pREnd; + pDelEnd = pSttRng; + } + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + rArr.Remove( rPos-- ); + bDelRedl = true; + if( bCallDelete ) + { + pDelStt = pRedl->Start(); + pDelEnd = pRedl->End(); + } + } + break; + default: + bRet = false; + } + + if( pDelStt && pDelEnd ) + { + SwPaM aPam( *pDelStt, *pDelEnd ); + SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + + // keep style of the empty paragraph after deletion of wholly paragraphs + if( pCSttNd && pCEndNd && pRStt && pREnd && pRStt->nContent == 0 ) + lcl_CopyStyle(*pREnd, *pRStt); + + if( bDelRedl ) + delete pRedl; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + if( pCSttNd && pCEndNd ) + rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + else if (pCSttNd && !pCEndNd) + { + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + rDoc.getIDocumentContentOperations().DelFullPara( aPam ); + } + else + { + rDoc.getIDocumentContentOperations().DeleteRange(aPam); + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( bDelRedl ) + delete pRedl; + } + break; + + case RedlineType::FmtColl: + rArr.DeleteAndDestroy( rPos-- ); + break; + + case RedlineType::ParagraphFormat: + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + return bRet; + } + + bool lcl_RejectRedline( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng = nullptr, + const SwPosition* pEndRng = nullptr ) + { + bool bRet = true; + SwRangeRedline* pRedl = rArr[ rPos ]; + SwDoc& rDoc = *pRedl->GetDoc(); + SwPosition *pRStt = nullptr, *pREnd = nullptr; + SwComparePosition eCmp = SwComparePosition::Outside; + if( pSttRng && pEndRng ) + { + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + eCmp = ComparePosition( *pSttRng, *pEndRng, *pRStt, *pREnd ); + } + + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + { + const SwPosition *pDelStt = nullptr, *pDelEnd = nullptr; + bool bDelRedl = false; + switch( eCmp ) + { + case SwComparePosition::Inside: + if( bCallDelete ) + { + pDelStt = pSttRng; + pDelEnd = pEndRng; + } + break; + + case SwComparePosition::OverlapBefore: + if( bCallDelete ) + { + pDelStt = pRStt; + pDelEnd = pEndRng; + } + break; + case SwComparePosition::OverlapBehind: + if( bCallDelete ) + { + pDelStt = pREnd; + pDelEnd = pSttRng; + } + break; + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + // delete the range again + rArr.Remove( rPos-- ); + bDelRedl = true; + if( bCallDelete ) + { + pDelStt = pRedl->Start(); + pDelEnd = pRedl->End(); + } + } + break; + + default: + bRet = false; + } + if( pDelStt && pDelEnd ) + { + SwPaM aPam( *pDelStt, *pDelEnd ); + + SwContentNode* pCSttNd = pDelStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->nNode.GetNode().GetContentNode(); + + if( bDelRedl ) + delete pRedl; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + if( pCSttNd && pCEndNd ) + rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + else if (pCSttNd && !pCEndNd) + { + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + if (aPam.End()->nNode.GetNode().IsStartNode()) + { // end node will be deleted too! see nNodeDiff+1 + --aPam.End()->nNode; + } + assert(!aPam.End()->nNode.GetNode().IsStartNode()); + rDoc.getIDocumentContentOperations().DelFullPara( aPam ); + } + else + { + rDoc.getIDocumentContentOperations().DeleteRange(aPam); + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( bDelRedl ) + delete pRedl; + } + break; + case RedlineType::Delete: + { + SwRangeRedline* pNew = nullptr; + bool bCheck = false, bReplace = false; + SwPaM const updatePaM(pSttRng ? *pSttRng : *pRedl->Start(), + pEndRng ? *pEndRng : *pRedl->End()); + + if( pRedl->GetExtraData() ) + pRedl->GetExtraData()->Reject( *pRedl ); + + switch( eCmp ) + { + case SwComparePosition::Inside: + { + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + if( *pSttRng == *pRStt ) + { + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + if( pNew ) + pNew->SetEnd( *pEndRng ); + } + else + { + if( *pEndRng != *pREnd ) + { + // split up + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEndRng ); + rArr.Insert( pCpy ); ++rPos; + if( pNew ) + pNew->SetEnd( *pEndRng ); + } + + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + if( pNew ) + pNew->SetStart( *pSttRng ); + } + } + break; + + case SwComparePosition::OverlapBefore: + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + pRedl->SetStart( *pEndRng, pRStt ); + bReplace = true; + if( pNew ) + pNew->SetEnd( *pEndRng ); + break; + + case SwComparePosition::OverlapBehind: + if( 1 < pRedl->GetStackCount() ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PopData(); + } + pRedl->SetEnd( *pSttRng, pREnd ); + bCheck = true; + if( pNew ) + pNew->SetStart( *pSttRng ); + break; + + case SwComparePosition::Outside: + case SwComparePosition::Equal: + if( !pRedl->PopData() ) + // deleting the RedlineObject is enough + rArr.DeleteAndDestroy( rPos-- ); + break; + + default: + bRet = false; + } + + if( pNew ) + { + rArr.Insert( pNew ); ++rPos; + } + + if( bReplace || ( bCheck && !pRedl->HasValidRange() )) + { + // re-insert + rArr.Remove( pRedl ); + rArr.Insert( pRedl ); + } + + sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM); + } + break; + + case RedlineType::Format: + case RedlineType::FmtColl: + case RedlineType::ParagraphFormat: + { + // tdf#52391 instead of hidden acception at the requested + // rejection, remove direct text formatting to get the potential + // original state of the text (FIXME if the original text + // has already contained direct text formatting: unfortunately + // ODF 1.2 doesn't support rejection of format-only changes) + if ( pRedl->GetType() == RedlineType::Format ) + { + SwPaM aPam( *(pRedl->Start()), *(pRedl->End()) ); + rDoc.ResetAttrs(aPam); + } + else if ( pRedl->GetType() == RedlineType::ParagraphFormat ) + { + // handle paragraph formatting changes + // (range is only a full paragraph or a part of it) + const SwPosition* pStt = pRedl->Start(); + SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + // expand range to the whole paragraph + // and reset only the paragraph attributes + SwPaM aPam( *pTNd, pTNd->GetText().getLength() ); + std::set<sal_uInt16> aResetAttrsArray; + + sal_uInt16 aResetableSetRange[] = { + RES_PARATR_BEGIN, RES_PARATR_END - 1, + RES_PARATR_LIST_BEGIN, RES_FRMATR_END - 1, + 0 + }; + + const sal_uInt16 *pUShorts = aResetableSetRange; + while (*pUShorts) + { + for (sal_uInt16 i = pUShorts[0]; i <= pUShorts[1]; ++i) + aResetAttrsArray.insert( aResetAttrsArray.end(), i ); + pUShorts += 2; + } + + rDoc.ResetAttrs(aPam, false, aResetAttrsArray); + + // remove numbering + if ( pTNd->GetNumRule() ) + rDoc.DelNumRules(aPam); + } + } + + if( pRedl->GetExtraData() ) + pRedl->GetExtraData()->Reject( *pRedl ); + + rArr.DeleteAndDestroy( rPos-- ); + } + break; + + default: + bRet = false; + } + return bRet; + } + + typedef bool (*Fn_AcceptReject)( SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + bool bCallDelete, + const SwPosition* pSttRng, + const SwPosition* pEndRng); + + + int lcl_AcceptRejectRedl( Fn_AcceptReject fn_AcceptReject, + SwRedlineTable& rArr, bool bCallDelete, + const SwPaM& rPam) + { + SwRedlineTable::size_type n = 0; + int nCount = 0; + + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + const SwRangeRedline* pFnd = rArr.FindAtPosition( *pStt, n ); + if( pFnd && // Is new a part of it? + ( *pFnd->Start() != *pStt || *pFnd->End() > *pEnd )) + { + // Only revoke the partial selection + if( (*fn_AcceptReject)( rArr, n, bCallDelete, pStt, pEnd )) + nCount++; + ++n; + } + + // tdf#119824 first we will accept only overlapping paragraph format changes + // in the first loop to avoid potential content changes during Redo + bool bHasParagraphFormatChange = false; + for( int m = 0 ; m < 2 && !bHasParagraphFormatChange; ++m ) + { + for(SwRedlineTable::size_type o = n ; o < rArr.size(); ++o ) + { + SwRangeRedline* pTmp = rArr[ o ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if( *pTmp->End() <= *pEnd ) + { + if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && + (*fn_AcceptReject)( rArr, o, bCallDelete, nullptr, nullptr )) + { + bHasParagraphFormatChange = true; + nCount++; + } + } + else + { + if( *pTmp->Start() < *pEnd ) + { + // Only revoke the partial selection + if( (m > 0 || RedlineType::ParagraphFormat == pTmp->GetType()) && + (*fn_AcceptReject)( rArr, o, bCallDelete, pStt, pEnd )) + { + bHasParagraphFormatChange = true; + nCount++; + } + } + break; + } + } + } + } + return nCount; + } + + void lcl_AdjustRedlineRange( SwPaM& rPam ) + { + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + SwDoc* pDoc = rPam.GetDoc(); + if( !pStt->nContent.GetIndex() && + !pDoc->GetNodes()[ pStt->nNode.GetIndex() - 1 ]->IsContentNode() ) + { + const SwRangeRedline* pRedl = pDoc->getIDocumentRedlineAccess().GetRedline( *pStt, nullptr ); + if( pRedl ) + { + const SwPosition* pRStt = pRedl->Start(); + if( !pRStt->nContent.GetIndex() && pRStt->nNode.GetIndex() == + pStt->nNode.GetIndex() - 1 ) + *pStt = *pRStt; + } + } + if( pEnd->nNode.GetNode().IsContentNode() && + !pDoc->GetNodes()[ pEnd->nNode.GetIndex() + 1 ]->IsContentNode() && + pEnd->nContent.GetIndex() == pEnd->nNode.GetNode().GetContentNode()->Len() ) + { + const SwRangeRedline* pRedl = pDoc->getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr ); + if( pRedl ) + { + const SwPosition* pREnd = pRedl->End(); + if( !pREnd->nContent.GetIndex() && pREnd->nNode.GetIndex() == + pEnd->nNode.GetIndex() + 1 ) + *pEnd = *pREnd; + } + } + } + + /// in case some text is deleted, ensure that the not-yet-inserted + /// SwRangeRedline has its positions corrected not to point to deleted node + class TemporaryRedlineUpdater + { + private: + SwRangeRedline & m_rRedline; + std::shared_ptr<SwUnoCursor> m_pCursor; + public: + TemporaryRedlineUpdater(SwDoc & rDoc, SwRangeRedline & rRedline) + : m_rRedline(rRedline) + , m_pCursor(rDoc.CreateUnoCursor(*rRedline.GetPoint(), false)) + { + if (m_rRedline.HasMark()) + { + m_pCursor->SetMark(); + *m_pCursor->GetMark() = *m_rRedline.GetMark(); + *m_rRedline.GetMark() = SwPosition(rDoc.GetNodes().GetEndOfContent()); + } + *m_rRedline.GetPoint() = SwPosition(rDoc.GetNodes().GetEndOfContent()); + } + ~TemporaryRedlineUpdater() + { + static_cast<SwPaM&>(m_rRedline) = *m_pCursor; + } + }; +} + +namespace sw +{ + +DocumentRedlineManager::DocumentRedlineManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , meRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) + , mpRedlineTable(new SwRedlineTable) + , mpExtraRedlineTable(new SwExtraRedlineTable) + , mbIsRedlineMove(false) + , mnAutoFormatRedlnCommentNo(0) +{ +} + +RedlineFlags DocumentRedlineManager::GetRedlineFlags() const +{ + return meRedlineFlags; +} + +void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode ) +{ + if( meRedlineFlags != eMode ) + { + if( (RedlineFlags::ShowMask & meRedlineFlags) != (RedlineFlags::ShowMask & eMode) + || !(RedlineFlags::ShowMask & eMode) ) + { + bool bSaveInXMLImportFlag = m_rDoc.IsInXMLImport(); + m_rDoc.SetInXMLImport( false ); + // and then hide/display everything + void (SwRangeRedline::*pFnc)(sal_uInt16, size_t); // Allow compiler warn if use of + // uninitialized ptr is possible + + RedlineFlags eShowMode = RedlineFlags::ShowMask & eMode; + if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + pFnc = &SwRangeRedline::Show; + else if (eShowMode == RedlineFlags::ShowInsert) + pFnc = &SwRangeRedline::Hide; + else if (eShowMode == RedlineFlags::ShowDelete) + pFnc = &SwRangeRedline::ShowOriginal; + else + { + pFnc = &SwRangeRedline::Hide; + eMode |= RedlineFlags::ShowInsert; + } + + CheckAnchoredFlyConsistency(m_rDoc); + CHECK_REDLINE( *this ) + + o3tl::sorted_vector<SwRootFrame *> hiddenLayouts; + if (eShowMode == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + { + // sw_redlinehide: the problem here is that MoveFromSection + // creates the frames wrongly (non-merged), because its own + // SwRangeRedline has wrong positions until after the nodes + // are all moved, so fix things up by force by re-creating + // all merged frames from scratch. + o3tl::sorted_vector<SwRootFrame *> const layouts(m_rDoc.GetAllLayouts()); + for (SwRootFrame *const pLayout : layouts) + { + if (pLayout->IsHideRedlines()) + { + pLayout->SetHideRedlines(false); + hiddenLayouts.insert(pLayout); + } + } + } + + for (sal_uInt16 nLoop = 1; nLoop <= 2; ++nLoop) + for (size_t i = 0; i < mpRedlineTable->size(); ++i) + { + SwRangeRedline *const pRedline((*mpRedlineTable)[i]); + (pRedline->*pFnc)(nLoop, i); + while (mpRedlineTable->size() <= i + || (*mpRedlineTable)[i] != pRedline) + { // ensure current position + --i; // a previous redline may have been deleted + } + } + + //SwRangeRedline::MoveFromSection routinely changes + //the keys that mpRedlineTable is sorted by + mpRedlineTable->Resort(); + + CheckAnchoredFlyConsistency(m_rDoc); + CHECK_REDLINE( *this ) + + for (SwRootFrame *const pLayout : hiddenLayouts) + { + pLayout->SetHideRedlines(true); + } + + m_rDoc.SetInXMLImport( bSaveInXMLImportFlag ); + } + meRedlineFlags = eMode; + m_rDoc.getIDocumentState().SetModified(); + } + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::IsRedlineOn() const +{ + return IDocumentRedlineAccess::IsRedlineOn(meRedlineFlags); +} + +bool DocumentRedlineManager::IsIgnoreRedline() const +{ + return bool(RedlineFlags::Ignore & meRedlineFlags); +} + +void DocumentRedlineManager::SetRedlineFlags_intern(RedlineFlags eMode) +{ + meRedlineFlags = eMode; +} + +const SwRedlineTable& DocumentRedlineManager::GetRedlineTable() const +{ + return *mpRedlineTable; +} + +SwRedlineTable& DocumentRedlineManager::GetRedlineTable() +{ + return *mpRedlineTable; +} + +const SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() const +{ + return *mpExtraRedlineTable; +} + +SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() +{ + return *mpExtraRedlineTable; +} + +bool DocumentRedlineManager::HasExtraRedlineTable() const +{ + return mpExtraRedlineTable != nullptr; +} + +bool DocumentRedlineManager::IsInRedlines(const SwNode & rNode) const +{ + SwPosition aPos(rNode); + SwNode & rEndOfRedlines = m_rDoc.GetNodes().GetEndOfRedlines(); + SwPaM aPam(SwPosition(*rEndOfRedlines.StartOfSectionNode()), + SwPosition(rEndOfRedlines)); + + return aPam.ContainsPosition(aPos); +} + +bool DocumentRedlineManager::IsRedlineMove() const +{ + return mbIsRedlineMove; +} + +void DocumentRedlineManager::SetRedlineMove(bool bFlag) +{ + mbIsRedlineMove = bFlag; +} + +/* +Text means Text not "polluted" by Redlines. + +Behaviour of Insert-Redline: + - in the Text - insert Redline Object + - in InsertRedline (own) - ignore, existing is extended + - in InsertRedline (others) - split up InsertRedline and + insert Redline Object + - in DeleteRedline - split up DeleteRedline or + move at the end/beginning + +Behaviour of Delete-Redline: + - in the Text - insert Redline Object + - in DeleteRedline (own/others) - ignore + - in InsertRedline (own) - ignore, but delete character + - in InsertRedline (others) - split up InsertRedline and + insert Redline Object + - Text and own Insert overlap - delete Text in the own Insert, + extend in the other Text + (up to the Insert!) + - Text and other Insert overlap - insert Redline Object, the + other Insert is overlapped by + the Delete +*/ +IDocumentRedlineAccess::AppendResult +DocumentRedlineManager::AppendRedline(SwRangeRedline* pNewRedl, bool const bCallDelete) +{ + bool bMerged = false; + CHECK_REDLINE( *this ) + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + + if( m_rDoc.IsAutoFormatRedline() ) + { + pNewRedl->SetAutoFormat(); + if( mpAutoFormatRedlnComment && !mpAutoFormatRedlnComment->isEmpty() ) + { + pNewRedl->SetComment( *mpAutoFormatRedlnComment ); + pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo ); + } + } + + SwPosition* pStt = pNewRedl->Start(), + * pEnd = pStt == pNewRedl->GetPoint() ? pNewRedl->GetMark() + : pNewRedl->GetPoint(); + { + SwTextNode* pTextNode = pStt->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pStt->nContent > 0 ) + { + OSL_ENSURE( false, "Redline start: non-text-node with content" ); + pStt->nContent = 0; + } + } + else + { + if( pStt->nContent > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline start: index after text" ); + pStt->nContent = pTextNode->Len(); + } + } + pTextNode = pEnd->nNode.GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pEnd->nContent > 0 ) + { + OSL_ENSURE( false, "Redline end: non-text-node with content" ); + pEnd->nContent = 0; + } + } + else + { + if( pEnd->nContent > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline end: index after text" ); + pEnd->nContent = pTextNode->Len(); + } + } + } + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { // Do not insert empty redlines + delete pNewRedl; + return AppendResult::IGNORED; + } + bool bCompress = false; + SwRedlineTable::size_type n = 0; + // look up the first Redline for the starting position + if( !GetRedline( *pStt, &n ) && n ) + --n; + bool bDec = false; + + for( ; pNewRedl && n < mpRedlineTable->size(); bDec ? n : ++n ) + { + bDec = false; + + SwRangeRedline* pRedl = (*mpRedlineTable)[ n ]; + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + + // #i8518# remove empty redlines while we're at it + if( ( *pRStt == *pREnd ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy(n); + continue; + } + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + + switch( pNewRedl->GetType() ) + { + case RedlineType::Insert: + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + if( pRedl->IsOwnRedline( *pNewRedl ) ) + { + bool bDelete = false; + + // Merge if applicable? + if( (( SwComparePosition::Behind == eCmpPos && + IsPrevPos( *pREnd, *pStt ) ) || + ( SwComparePosition::CollideStart == eCmpPos ) || + ( SwComparePosition::OverlapBehind == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( n+1 >= mpRedlineTable->size() || + ( *(*mpRedlineTable)[ n+1 ]->Start() >= *pEnd && + *(*mpRedlineTable)[ n+1 ]->Start() != *pREnd ) ) ) + { + pRedl->SetEnd( *pEnd, pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + + bMerged = true; + bDelete = true; + } + else if( (( SwComparePosition::Before == eCmpPos && + IsPrevPos( *pEnd, *pRStt ) ) || + ( SwComparePosition::CollideEnd == eCmpPos ) || + ( SwComparePosition::OverlapBefore == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( !n || + *(*mpRedlineTable)[ n-1 ]->End() != *pRStt )) + { + pRedl->SetStart( *pStt, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + + bMerged = true; + bDelete = true; + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // own insert-over-insert redlines: + // just scrap the inside ones + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + { + *pStt = *pREnd; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = true; + } + else if( SwComparePosition::OverlapBefore == eCmpPos ) + { + *pEnd = *pRStt; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = true; + } + else if( SwComparePosition::Inside == eCmpPos ) + { + bDelete = true; + bMerged = true; + } + else if( SwComparePosition::Equal == eCmpPos ) + bDelete = true; + + if( bDelete ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + mpRedlineTable->Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // handle overlapping redlines in broken documents + + // split up the new redline, since it covers the + // existing redline. Insert the first part, and + // progress with the remainder as usual + SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); + pSplit->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd ); + mpRedlineTable->Insert( pSplit ); + if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if ( SwComparePosition::OverlapBehind == eCmpPos ) + { + // handle overlapping redlines in broken documents + pNewRedl->SetStart( *pREnd ); + } + else if ( SwComparePosition::OverlapBefore == eCmpPos ) + { + // handle overlapping redlines in broken documents + *pEnd = *pRStt; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + break; + case RedlineType::Delete: + if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + mpRedlineTable->Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // handle overlapping redlines in broken documents + + // split up the new redline, since it covers the + // existing redline. Insert the first part, and + // progress with the remainder as usual + SwRangeRedline* pSplit = new SwRangeRedline( *pNewRedl ); + pSplit->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd ); + mpRedlineTable->Insert( pSplit ); + if( *pStt == *pEnd && pNewRedl->GetContentIdx() == nullptr ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + } + } + else if ( SwComparePosition::Equal == eCmpPos ) + { + // handle identical redlines in broken documents + // delete old (delete) redline + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if ( SwComparePosition::OverlapBehind == eCmpPos ) + { // Another workaround for broken redlines + pNewRedl->SetStart( *pREnd ); + } + break; + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pStt, pREnd ); + if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + + case SwComparePosition::Equal: + case SwComparePosition::Outside: + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + break; + + case SwComparePosition::Inside: + // Overlaps the current one completely, + // split or shorten the new one + if( *pEnd != *pREnd ) + { + if( *pEnd != *pRStt ) + { + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) + mpRedlineTable->DeleteAndDestroy( n ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + else + pRedl->SetEnd( *pStt, pREnd ); + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::Delete: + switch( pRedl->GetType() ) + { + case RedlineType::Delete: + switch( eCmpPos ) + { + case SwComparePosition::Outside: + { + // Overlaps the current one completely, + // split the new one + if (*pEnd == *pREnd) + { + pNewRedl->SetEnd(*pRStt, pEnd); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetStart( *pREnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + break; + + case SwComparePosition::Inside: + case SwComparePosition::Equal: + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + break; + + case SwComparePosition::OverlapBefore: + case SwComparePosition::OverlapBehind: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pREnd, pStt ); + else + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::CollideStart: + case SwComparePosition::CollideEnd: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) ) + { + if( IsHideChanges( meRedlineFlags )) + { + // Before we can merge, we make it visible! + // We insert temporarily so that pNew is + // also dealt with when moving the indices. + mpRedlineTable->Insert(pNewRedl); + pRedl->Show(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + pRStt = pRedl->Start(); + pREnd = pRedl->End(); + } + + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::CollideStart == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + + // delete current (below), and restart process with + // previous + SwRedlineTable::size_type nToBeDeleted = n; + bDec = true; + + if( *(pNewRedl->Start()) <= *pREnd ) + { + // Whoooah, we just extended the new 'redline' + // beyond previous redlines, so better start + // again. Of course this is not supposed to + // happen, and in an ideal world it doesn't, + // but unfortunately this code is buggy and + // totally rotten so it does happen and we + // better fix it. + n = 0; + bDec = true; + } + + mpRedlineTable->DeleteAndDestroy( nToBeDeleted ); + } + break; + default: + break; + } + break; + + case RedlineType::Insert: + { + // b62341295: Do not throw away redlines + // even if they are not allowed to be combined + RedlineFlags eOld = meRedlineFlags; + if( !( eOld & RedlineFlags::DontCombineRedlines ) && + pRedl->IsOwnRedline( *pNewRedl ) ) + { + + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); + switch( eCmpPos ) + { + case SwComparePosition::Equal: + bCompress = true; + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + [[fallthrough]]; + + case SwComparePosition::Inside: + if( bCallDelete ) + { + // DeleteAndJoin does not yield the + // desired result if there is no paragraph to + // join with, i.e. at the end of the document. + // For this case, we completely delete the + // paragraphs (if, of course, we also start on + // a paragraph boundary). + if( (pStt->nContent == 0) && + pEnd->nNode.GetNode().IsEndNode() ) + { + pEnd->nNode--; + pEnd->nContent.Assign( + pEnd->nNode.GetNode().GetTextNode(), 0); + m_rDoc.getIDocumentContentOperations().DelFullPara( *pNewRedl ); + } + else + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + + bCompress = true; + } + if( !bCallDelete && !bDec && *pEnd == *pREnd ) + { + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + bCompress = true; + } + else if ( bCallDelete || !bDec ) + { + // delete new redline, except in some cases of fallthrough from previous + // case ::Equal (eg. same portion w:del in w:ins in OOXML import) + delete pNewRedl; + pNewRedl = nullptr; + } + break; + + case SwComparePosition::Outside: + { + mpRedlineTable->Remove( n ); + bDec = true; + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pRedl ); + n = 0; // re-initialize + } + delete pRedl; + } + break; + + case SwComparePosition::OverlapBefore: + { + SwPaM aPam( *pRStt, *pEnd ); + + if( *pEnd == *pREnd ) + mpRedlineTable->DeleteAndDestroy( n ); + else + { + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + n = 0; // re-initialize + } + bDec = true; + } + break; + + case SwComparePosition::OverlapBehind: + { + SwPaM aPam( *pStt, *pREnd ); + + if( *pStt == *pRStt ) + { + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + else + pRedl->SetEnd( *pStt, pREnd ); + + if( bCallDelete ) + { + TemporaryRedlineUpdater const u(m_rDoc, *pNewRedl); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( aPam ); + n = 0; // re-initialize + bDec = true; + } + } + break; + default: + break; + } + + meRedlineFlags = eOld; + } + else + { + // it may be necessary to split the existing redline in + // two. In this case, pRedl will be changed to cover + // only part of its former range, and pNew will cover + // the remainder. + SwRangeRedline* pNew = nullptr; + + switch( eCmpPos ) + { + case SwComparePosition::Equal: + { + pRedl->PushData( *pNewRedl ); + delete pNewRedl; + pNewRedl = nullptr; + if( IsHideChanges( meRedlineFlags )) + { + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + } + bCompress = true; + } + break; + + case SwComparePosition::Inside: + { + if( *pRStt == *pStt ) + { + // #i97421# + // redline w/out extent loops + if (*pStt != *pEnd) + { + pNewRedl->PushData( *pRedl, false ); + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + } + } + else + { + pNewRedl->PushData( *pRedl, false ); + if( *pREnd != *pEnd ) + { + pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + } + } + } + break; + + case SwComparePosition::Outside: + { + pRedl->PushData( *pNewRedl ); + if( *pEnd == *pREnd ) + { + pNewRedl->SetEnd( *pRStt, pEnd ); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetEnd( *pRStt ); + pNewRedl->SetStart( *pREnd, pStt ); + } + bCompress = true; + } + break; + + case SwComparePosition::OverlapBefore: + { + if( *pEnd == *pREnd ) + { + pRedl->PushData( *pNewRedl ); + pNewRedl->SetEnd( *pRStt, pEnd ); + if( IsHideChanges( meRedlineFlags )) + { + mpRedlineTable->Insert(pNewRedl); + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + } + } + else + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PushData( *pNewRedl ); + pNew->SetEnd( *pEnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + pRedl->SetStart( *pNew->End(), pRStt ) ; + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + bDec = true; + } + } + break; + + case SwComparePosition::OverlapBehind: + { + if( *pStt == *pRStt ) + { + pRedl->PushData( *pNewRedl ); + pNewRedl->SetStart( *pREnd, pStt ); + if( IsHideChanges( meRedlineFlags )) + { + mpRedlineTable->Insert( pNewRedl ); + pRedl->Hide(0, mpRedlineTable->GetPos(pRedl)); + mpRedlineTable->Remove( pNewRedl ); + } + } + else + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PushData( *pNewRedl ); + pNew->SetStart( *pStt ); + pNewRedl->SetStart( *pREnd, pStt ); + pRedl->SetEnd( *pNew->Start(), pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + } + } + } + break; + default: + break; + } + + // insert the pNew part (if it exists) + if( pNew ) + { + mpRedlineTable->Insert( pNew ); + + // pNew must be deleted if Insert() wasn't + // successful. But that can't happen, since pNew is + // part of the original pRedl redline. + // OSL_ENSURE( bRet, "Can't insert existing redline?" ); + + // restart (now with pRedl being split up) + n = 0; + bDec = true; + } + } + } + break; + + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pStt, pREnd ); + break; + + case SwComparePosition::Equal: + case SwComparePosition::Outside: + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + break; + + case SwComparePosition::Inside: + // Overlaps the current one completely, + // split or shorten the new one + if( *pEnd != *pREnd ) + { + if( *pEnd != *pRStt ) + { + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + mpRedlineTable->DeleteAndDestroy( n ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + } + else + pRedl->SetEnd( *pStt, pREnd ); + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::Format: + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + case RedlineType::Delete: + switch( eCmpPos ) + { + case SwComparePosition::OverlapBefore: + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::OverlapBehind: + pNewRedl->SetStart( *pREnd, pStt ); + break; + + case SwComparePosition::Equal: + case SwComparePosition::Inside: + delete pNewRedl; + pNewRedl = nullptr; + break; + + case SwComparePosition::Outside: + // Overlaps the current one completely, + // split or shorten the new one + if (*pEnd == *pREnd) + { + pNewRedl->SetEnd(*pRStt, pEnd); + } + else if (*pStt == *pRStt) + { + pNewRedl->SetStart(*pREnd, pStt); + } + else + { + SwRangeRedline* pNew = new SwRangeRedline( *pNewRedl ); + pNew->SetStart( *pREnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + break; + default: + break; + } + break; + case RedlineType::Format: + switch( eCmpPos ) + { + case SwComparePosition::Outside: + case SwComparePosition::Equal: + { + // Overlaps the current one completely or has the + // same dimension, delete the old one + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + + case SwComparePosition::Inside: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // own one can be ignored completely + delete pNewRedl; + pNewRedl = nullptr; + } + else if( *pREnd == *pEnd ) + // or else only shorten the current one + pRedl->SetEnd( *pStt, pREnd ); + else if( *pRStt == *pStt ) + { + // or else only shorten the current one + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl, n ); + bDec = true; + } + else + { + // If it lies completely within the current one + // we need to split it + SwRangeRedline* pNew = new SwRangeRedline( *pRedl ); + pNew->SetStart( *pEnd ); + pRedl->SetEnd( *pStt, pREnd ); + AppendRedline( pNew, bCallDelete ); + n = 0; // re-initialize + bDec = true; + } + break; + + case SwComparePosition::OverlapBefore: + case SwComparePosition::OverlapBehind: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl )) + { + // If that's the case we can merge it, meaning + // the new one covers this well + if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pRStt, pStt ); + else + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = false; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pREnd, pStt ); + else + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::CollideEnd: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) && n && + *(*mpRedlineTable)[ n-1 ]->End() < *pStt ) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetEnd( *pREnd, pEnd ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + case SwComparePosition::CollideStart: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) && + n+1 < mpRedlineTable->size() && + *(*mpRedlineTable)[ n+1 ]->Start() < *pEnd ) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetStart( *pRStt, pStt ); + mpRedlineTable->DeleteAndDestroy( n ); + bDec = true; + } + break; + default: + break; + } + break; + default: + break; + } + break; + + case RedlineType::FmtColl: + // How should we behave here? + // insert as is + break; + default: + break; + } + } + + if( pNewRedl ) + { + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + { // Do not insert empty redlines + delete pNewRedl; + pNewRedl = nullptr; + } + else + { + if ( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + if ( pStt->nContent != 0 ) + { + // tdf#119571 update the style of the joined paragraph + // after a partially deleted paragraph to show its correct style + // in "Show changes" mode, too. All removed paragraphs + // get the style of the first (partially deleted) paragraph + // to avoid text insertion with bad style in the deleted + // area later. + + SwContentNode* pDelNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pTextNd = pEnd->nNode.GetNode().GetContentNode(); + SwTextNode* pDelNode = pStt->nNode.GetNode().GetTextNode(); + SwTextNode* pTextNode; + SwNodeIndex aIdx( pEnd->nNode.GetNode() ); + bool bFirst = true; + + while (pDelNode != nullptr && pTextNd != nullptr && pDelNd->GetIndex() < pTextNd->GetIndex()) + { + pTextNode = pTextNd->GetTextNode(); + if (pTextNode && pDelNode != pTextNode ) + { + SwPosition aPos(aIdx); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + bCompress = true; + + // split redline to store ExtraData per paragraphs + SwRangeRedline* pPar = new SwRangeRedline( *pNewRedl ); + pPar->SetStart( aPos ); + pNewRedl->SetEnd( aPos ); + + // get extradata for reset formatting of the modified paragraph + SwRedlineExtraData_FormatColl* pExtraData = lcl_CopyStyle(aPos, *pStt, false); + if (pExtraData) + { + std::unique_ptr<SwRedlineExtraData_FormatColl> xRedlineExtraData; + if (!bFirst) + pExtraData->SetFormatAll(false); + xRedlineExtraData.reset(pExtraData); + pPar->SetExtraData( xRedlineExtraData.get() ); + } + mpRedlineTable->Insert( pPar ); + } + + // modify paragraph formatting + lcl_CopyStyle(*pStt, aPos); + } + pTextNd = SwNodes::GoPrevious( &aIdx ); + + if (bFirst) + bFirst = false; + } + } + } + bool const ret = mpRedlineTable->Insert( pNewRedl ); + assert(ret || !pNewRedl); + if (ret && !pNewRedl) + { + bMerged = true; // treat InsertWithValidRanges as "merge" + } + } + } + + if( bCompress ) + CompressRedlines(); + } + else + { + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore); + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl; + pNewRedl = nullptr; + } + CHECK_REDLINE( *this ) + + return (nullptr != pNewRedl) + ? AppendResult::APPENDED + : (bMerged ? AppendResult::MERGED : AppendResult::IGNORED); +} + +bool DocumentRedlineManager::AppendTableRowRedline( SwTableRowRedline* pNewRedl ) +{ + // #TODO - equivalent for 'SwTableRowRedline' + /* + CHECK_REDLINE( this ) + */ + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + // #TODO - equivalent for 'SwTableRowRedline' + /* + pNewRedl->InvalidateRange(); + */ + + // Make equivalent of 'AppendRedline' checks inside here too + + mpExtraRedlineTable->Insert( pNewRedl ); + } + else + { + // TO DO - equivalent for 'SwTableRowRedline' + /* + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); + DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl, pNewRedl = 0; + */ + } + // #TODO - equivalent for 'SwTableRowRedline' + /* + CHECK_REDLINE( this ) + */ + + return nullptr != pNewRedl; +} + +bool DocumentRedlineManager::AppendTableCellRedline( SwTableCellRedline* pNewRedl ) +{ + // #TODO - equivalent for 'SwTableCellRedline' + /* + CHECK_REDLINE( this ) + */ + + if (IsRedlineOn() && !IsShowOriginal(meRedlineFlags)) + { + // #TODO - equivalent for 'SwTableCellRedline' + /* + pNewRedl->InvalidateRange(); + */ + + // Make equivalent of 'AppendRedline' checks inside here too + + mpExtraRedlineTable->Insert( pNewRedl ); + } + else + { + // TO DO - equivalent for 'SwTableCellRedline' + /* + if( bCallDelete && RedlineType::Delete == pNewRedl->GetType() ) + { + RedlineFlags eOld = meRedlineFlags; + // Set to NONE, so that the Delete::Redo merges the Redline data correctly! + // The ShowMode needs to be retained! + meRedlineFlags = eOld & ~(RedlineFlags::On | RedlineFlags::Ignore); + DeleteAndJoin( *pNewRedl ); + meRedlineFlags = eOld; + } + delete pNewRedl, pNewRedl = 0; + */ + } + // #TODO - equivalent for 'SwTableCellRedline' + /* + CHECK_REDLINE( this ) + */ + + return nullptr != pNewRedl; +} + +void DocumentRedlineManager::CompressRedlines() +{ + CHECK_REDLINE( *this ) + + void (SwRangeRedline::*pFnc)(sal_uInt16, size_t) = nullptr; + RedlineFlags eShow = RedlineFlags::ShowMask & meRedlineFlags; + if( eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + pFnc = &SwRangeRedline::Show; + else if (eShow == RedlineFlags::ShowInsert) + pFnc = &SwRangeRedline::Hide; + + // Try to merge identical ones + for( SwRedlineTable::size_type n = 1; n < mpRedlineTable->size(); ++n ) + { + SwRangeRedline* pPrev = (*mpRedlineTable)[ n-1 ], + * pCur = (*mpRedlineTable)[ n ]; + const SwPosition* pPrevStt = pPrev->Start(), + * pPrevEnd = pPrevStt == pPrev->GetPoint() + ? pPrev->GetMark() : pPrev->GetPoint(); + const SwPosition* pCurStt = pCur->Start(), + * pCurEnd = pCurStt == pCur->GetPoint() + ? pCur->GetMark() : pCur->GetPoint(); + if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) && + pPrevStt->nNode.GetNode().StartOfSectionNode() == + pCurEnd->nNode.GetNode().StartOfSectionNode() && + !pCurEnd->nNode.GetNode().StartOfSectionNode()->IsTableNode() ) + { + // we then can merge them + SwRedlineTable::size_type nPrevIndex = n-1; + pPrev->Show(0, nPrevIndex); + pCur->Show(0, n); + + pPrev->SetEnd( *pCur->End() ); + mpRedlineTable->DeleteAndDestroy( n ); + --n; + if( pFnc ) + (pPrev->*pFnc)(0, nPrevIndex); + } + } + CHECK_REDLINE( *this ) + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange ) +{ + bool bChg = false; + SwRedlineTable::size_type n = 0; + const SwPosition* pStt = rRange.Start(); + const SwPosition* pEnd = rRange.End(); + GetRedline( *pStt, &n ); + for ( ; n < mpRedlineTable->size(); ++n) + { + SwRangeRedline * pRedline = (*mpRedlineTable)[ n ]; + SwPosition *const pRedlineStart = pRedline->Start(); + SwPosition *const pRedlineEnd = pRedline->End(); + if (*pRedlineStart <= *pStt && *pStt <= *pRedlineEnd && + *pRedlineStart <= *pEnd && *pEnd <= *pRedlineEnd) + { + bChg = true; + int nn = 0; + if (*pStt == *pRedlineStart) + nn += 1; + if (*pEnd == *pRedlineEnd) + nn += 2; + + SwRangeRedline* pNew = nullptr; + switch( nn ) + { + case 0: + pNew = new SwRangeRedline( *pRedline ); + pRedline->SetEnd( *pStt, pRedlineEnd ); + pNew->SetStart( *pEnd ); + break; + + case 1: + *pRedlineStart = *pEnd; + break; + + case 2: + *pRedlineEnd = *pStt; + break; + + case 3: + pRedline->InvalidateRange(SwRangeRedline::Invalidation::Remove); + mpRedlineTable->DeleteAndDestroy( n-- ); + pRedline = nullptr; + break; + } + if (pRedline && !pRedline->HasValidRange()) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedline, n ); + } + if( pNew ) + mpRedlineTable->Insert( pNew, n ); + } + else if (*pEnd < *pRedlineStart) + break; + } + return bChg; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::DeleteRedline( const SwPaM& rRange, bool bSaveInUndo, + RedlineType nDelType ) +{ + if( !rRange.HasMark() || *rRange.GetMark() == *rRange.GetPoint() ) + return false; + + bool bChg = false; + + if (bSaveInUndo && m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoRedline> pUndo(new SwUndoRedline( SwUndoId::REDLINE, rRange )); + if( pUndo->GetRedlSaveCount() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + const SwPosition* pStt = rRange.Start(), + * pEnd = pStt == rRange.GetPoint() ? rRange.GetMark() + : rRange.GetPoint(); + SwRedlineTable::size_type n = 0; + GetRedline( *pStt, &n ); + for( ; n < mpRedlineTable->size() ; ++n ) + { + SwRangeRedline* pRedl = (*mpRedlineTable)[ n ]; + if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() ) + continue; + + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + switch( ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ) ) + { + case SwComparePosition::Equal: + case SwComparePosition::Outside: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + mpRedlineTable->DeleteAndDestroy( n-- ); + bChg = true; + break; + + case SwComparePosition::OverlapBefore: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + pRedl->SetStart( *pEnd, pRStt ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + break; + + case SwComparePosition::OverlapBehind: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + pRedl->SetEnd( *pStt, pREnd ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + break; + + case SwComparePosition::Inside: + { + // this one needs to be split + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + if( *pRStt == *pStt ) + { + pRedl->SetStart( *pEnd, pRStt ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + else + { + SwRangeRedline* pCpy; + if( *pREnd != *pEnd ) + { + pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + pCpy->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + else + pCpy = nullptr; + pRedl->SetEnd( *pStt, pREnd ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + if( !pRedl->HasValidRange() ) + { + // re-insert + mpRedlineTable->Remove( n ); + mpRedlineTable->Insert( pRedl ); + --n; + } + if( pCpy ) + mpRedlineTable->Insert( pCpy ); + } + } + break; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + n = mpRedlineTable->size(); + break; + default: + break; + } + } + + if( bChg ) + m_rDoc.getIDocumentState().SetModified(); + + return bChg; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::DeleteRedline( const SwStartNode& rNode, bool bSaveInUndo, + RedlineType nDelType ) +{ + SwPaM aTemp(*rNode.EndOfSectionNode(), rNode); + return DeleteRedline(aTemp, bSaveInUndo, nDelType); +} + +SwRedlineTable::size_type DocumentRedlineManager::GetRedlinePos( const SwNode& rNd, RedlineType nType ) const +{ + const sal_uLong nNdIdx = rNd.GetIndex(); + for( SwRedlineTable::size_type n = 0; n < mpRedlineTable->size() ; ++n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + if( nPt < nMk ) { long nTmp = nMk; nMk = nPt; nPt = nTmp; } + + if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && + nMk <= nNdIdx && nNdIdx <= nPt ) + return n; + + if( nMk > nNdIdx ) + break; + } + return SwRedlineTable::npos; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos, + SwRedlineTable::size_type* pFndPos ) const +{ + SwRedlineTable::size_type nO = mpRedlineTable->size(), nM, nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + nM = nU + ( nO - nU ) / 2; + const SwRangeRedline* pRedl = (*mpRedlineTable)[ nM ]; + const SwPosition* pStt = pRedl->Start(); + const SwPosition* pEnd = pStt == pRedl->GetPoint() + ? pRedl->GetMark() + : pRedl->GetPoint(); + if( pEnd == pStt + ? *pStt == rPos + : ( *pStt <= rPos && rPos < *pEnd ) ) + { + while( nM && rPos == *(*mpRedlineTable)[ nM - 1 ]->End() && + rPos == *(*mpRedlineTable)[ nM - 1 ]->Start() ) + { + --nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + // if there are format and insert changes in the same position + // show insert change first. + // since the redlines are sorted by position, only check the redline + // before and after the current redline + if( RedlineType::Format == pRedl->GetType() ) + { + if( nM && rPos >= *(*mpRedlineTable)[ nM - 1 ]->Start() && + rPos <= *(*mpRedlineTable)[ nM - 1 ]->End() && + ( RedlineType::Insert == (*mpRedlineTable)[ nM - 1 ]->GetType() ) ) + { + --nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + else if( ( nM + 1 ) <= nO && rPos >= *(*mpRedlineTable)[ nM + 1 ]->Start() && + rPos <= *(*mpRedlineTable)[ nM + 1 ]->End() && + ( RedlineType::Insert == (*mpRedlineTable)[ nM + 1 ]->GetType() ) ) + { + ++nM; + pRedl = (*mpRedlineTable)[ nM ]; + } + } + + if( pFndPos ) + *pFndPos = nM; + return pRedl; + } + else if( *pEnd <= rPos ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pFndPos ) + *pFndPos = nU; + return nullptr; + } + else + nO = nM - 1; + } + } + if( pFndPos ) + *pFndPos = nU; + return nullptr; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::AcceptRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) +{ + bool bRet = false; + + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + SwRangeRedline* pTmp = (*mpRedlineTable)[ nPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::ACCEPT_REDLINE, &aRewriter); + } + + int nLoopCnt = 2; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + + do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp) ); + } + + bRet |= lcl_AcceptRedline( *mpRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? mpRedlineTable->FindNextSeqNo( nSeqNo, nPos ) + : mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = (*mpRedlineTable)[ nPos ]; + } + else + nLoopCnt = 0; + } + else + nLoopCnt = 0; + + } while( nLoopCnt ); + + if( bRet ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + } + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::AcceptRedline( const SwPaM& rPam, bool bCallDelete ) +{ + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + lcl_AdjustRedlineRange( aPam ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoAcceptRedline>( aPam )); + } + + int nRet = lcl_AcceptRejectRedl( lcl_AcceptRedline, *mpRedlineTable, + bCallDelete, aPam ); + if( nRet > 0 ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + OUString aTmpStr; + + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nRet)); + aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::ACCEPT_REDLINE, &aRewriter ); + } + return nRet != 0; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +void DocumentRedlineManager::AcceptRedlineParagraphFormatting( const SwPaM &rPam ) +{ + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + + const sal_uLong nSttIdx = pStt->nNode.GetIndex(); + const sal_uLong nEndIdx = pEnd->nNode.GetIndex(); + + for( SwRedlineTable::size_type n = 0; n < mpRedlineTable->size() ; ++n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + if( nPt < nMk ) { long nTmp = nMk; nMk = nPt; nPt = nTmp; } + + if( RedlineType::ParagraphFormat == pTmp->GetType() && + ( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) ) + AcceptRedline( n, false ); + + if( nMk > nEndIdx ) + break; + } +} + +bool DocumentRedlineManager::RejectRedline( SwRedlineTable::size_type nPos, bool bCallDelete ) +{ + bool bRet = false; + + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + SwRangeRedline* pTmp = (*mpRedlineTable)[ nPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, pTmp->GetDescr()); + m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::REJECT_REDLINE, &aRewriter); + } + + int nLoopCnt = 2; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + + do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRejectRedline>( *pTmp ) ); + } + + bRet |= lcl_RejectRedline( *mpRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? mpRedlineTable->FindNextSeqNo( nSeqNo, nPos ) + : mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + mpRedlineTable->FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = (*mpRedlineTable)[ nPos ]; + } + else + nLoopCnt = 0; + } + else + nLoopCnt = 0; + + } while( nLoopCnt ); + + if( bRet ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + } + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::RejectRedline( const SwPaM& rPam, bool bCallDelete ) +{ + // Switch to visible in any case + if( (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete) != + (RedlineFlags::ShowMask & meRedlineFlags) ) + SetRedlineFlags( RedlineFlags::ShowInsert | RedlineFlags::ShowDelete | meRedlineFlags ); + + // The Selection is only in the ContentSection. If there are Redlines + // to Non-ContentNodes before or after that, then the Selections + // expand to them. + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + lcl_AdjustRedlineRange( aPam ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REJECT_REDLINE, nullptr ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoRejectRedline>(aPam) ); + } + + int nRet = lcl_AcceptRejectRedl( lcl_RejectRedline, *mpRedlineTable, + bCallDelete, aPam ); + if( nRet > 0 ) + { + CompressRedlines(); + m_rDoc.getIDocumentState().SetModified(); + } + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + OUString aTmpStr; + + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nRet)); + aTmpStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + m_rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REJECT_REDLINE, &aRewriter ); + } + + return nRet != 0; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +void DocumentRedlineManager::AcceptAllRedline(bool bAccept) +{ + bool bSuccess = true; + OUString sUndoStr; + IDocumentUndoRedo& rUndoMgr = m_rDoc.GetIDocumentUndoRedo(); + + if (mpRedlineTable->size() > 1) + { + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(mpRedlineTable->size())); + sUndoStr = aRewriter.Apply(SwResId(STR_N_REDLINES)); + } + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, sUndoStr); + rUndoMgr.StartUndo(bAccept ? SwUndoId::ACCEPT_REDLINE : SwUndoId::REJECT_REDLINE, &aRewriter); + } + + while (!mpRedlineTable->empty() && bSuccess) + { + if (bAccept) + bSuccess = AcceptRedline(mpRedlineTable->size() - 1, true); + else + bSuccess = RejectRedline(mpRedlineTable->size() - 1, true); + } + + if (!sUndoStr.isEmpty()) + { + rUndoMgr.EndUndo(SwUndoId::EMPTY, nullptr); + } +} + +const SwRangeRedline* DocumentRedlineManager::SelNextRedline( SwPaM& rPam ) const +{ + rPam.DeleteMark(); + rPam.SetMark(); + + SwPosition& rSttPos = *rPam.GetPoint(); + SwPosition aSavePos( rSttPos ); + bool bRestart; + + // If the starting position points to the last valid ContentNode, + // we take the next Redline in any case. + SwRedlineTable::size_type n = 0; + const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n ); + if( pFnd ) + { + const SwPosition* pEnd = pFnd->End(); + if( !pEnd->nNode.GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pEnd->nNode ); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.nNode && + pCNd->Len() == rSttPos.nContent.GetIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->End(); + } + + do { + bRestart = false; + + for( ; !pFnd && n < mpRedlineTable->size(); ++n ) + { + pFnd = (*mpRedlineTable)[ n ]; + if( pFnd->HasMark() && pFnd->IsVisible() ) + { + *rPam.GetMark() = *pFnd->Start(); + rSttPos = *pFnd->End(); + break; + } + else + pFnd = nullptr; + } + + if( pFnd ) + { + // Merge all of the same type and author that are + // consecutive into one Selection. + const SwPosition* pPrevEnd = pFnd->End(); + while( ++n < mpRedlineTable->size() ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition *pRStt; + if( pFnd->GetType() != pTmp->GetType() || + pFnd->GetAuthor() != pTmp->GetAuthor() ) + break; + pRStt = pTmp->Start(); + if( *pPrevEnd == *pRStt || IsPrevPos( *pPrevEnd, *pRStt ) ) + { + pPrevEnd = pTmp->End(); + rSttPos = *pPrevEnd; + } + else + break; + } + } + } + + if( pFnd ) + { + const SwRangeRedline* pSaveFnd = pFnd; + + SwContentNode* pCNd; + SwNodeIndex* pIdx = &rPam.GetMark()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); + if( pCNd ) + { + if( *pIdx <= rPam.GetPoint()->nNode ) + rPam.GetMark()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pIdx = &rPam.GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pIdx ); + if( pCNd ) + { + if( *pIdx >= rPam.GetMark()->nNode ) + rPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) + { + if( n < mpRedlineTable->size() ) + { + bRestart = true; + *rPam.GetPoint() = *pSaveFnd->End(); + } + else + { + rPam.DeleteMark(); + *rPam.GetPoint() = aSavePos; + } + pFnd = nullptr; + } + } + } while( bRestart ); + + return pFnd; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const SwRangeRedline* DocumentRedlineManager::SelPrevRedline( SwPaM& rPam ) const +{ + rPam.DeleteMark(); + rPam.SetMark(); + + SwPosition& rSttPos = *rPam.GetPoint(); + SwPosition aSavePos( rSttPos ); + bool bRestart; + + // If the starting position points to the last valid ContentNode, + // we take the previous Redline in any case. + SwRedlineTable::size_type n = 0; + const SwRangeRedline* pFnd = GetRedlineTable().FindAtPosition( rSttPos, n, false ); + if( pFnd ) + { + const SwPosition* pStt = pFnd->Start(); + if( !pStt->nNode.GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pStt->nNode ); + SwContentNode* pCNd = m_rDoc.GetNodes().GoNextSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.nNode && + !rSttPos.nContent.GetIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->Start(); + } + + do { + bRestart = false; + + while( !pFnd && 0 < n ) + { + pFnd = (*mpRedlineTable)[ --n ]; + if( pFnd->HasMark() && pFnd->IsVisible() ) + { + *rPam.GetMark() = *pFnd->End(); + rSttPos = *pFnd->Start(); + } + else + pFnd = nullptr; + } + + if( pFnd ) + { + // Merge all of the same type and author that are + // consecutive into one Selection. + const SwPosition* pNextStt = pFnd->Start(); + while( 0 < n ) + { + const SwRangeRedline* pTmp = (*mpRedlineTable)[ --n ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition *pREnd; + if( pFnd->GetType() == pTmp->GetType() && + pFnd->GetAuthor() == pTmp->GetAuthor() && + ( *pNextStt == *( pREnd = pTmp->End() ) || + IsPrevPos( *pREnd, *pNextStt )) ) + { + pNextStt = pTmp->Start(); + rSttPos = *pNextStt; + } + else + { + ++n; + break; + } + } + } + } + + if( pFnd ) + { + const SwRangeRedline* pSaveFnd = pFnd; + + SwContentNode* pCNd; + SwNodeIndex* pIdx = &rPam.GetMark()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pIdx ); + if( pCNd ) + { + if( *pIdx >= rPam.GetPoint()->nNode ) + rPam.GetMark()->nContent.Assign( pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pIdx = &rPam.GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pIdx ); + if( pCNd ) + { + if( *pIdx <= rPam.GetMark()->nNode ) + rPam.GetPoint()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) + { + if( n ) + { + bRestart = true; + *rPam.GetPoint() = *pSaveFnd->Start(); + } + else + { + rPam.DeleteMark(); + *rPam.GetPoint() = aSavePos; + } + pFnd = nullptr; + } + } + } while( bRestart ); + + return pFnd; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +// Set comment at the Redline +bool DocumentRedlineManager::SetRedlineComment( const SwPaM& rPaM, const OUString& rS ) +{ + bool bRet = false; + const SwPosition* pStt = rPaM.Start(), + * pEnd = pStt == rPaM.GetPoint() ? rPaM.GetMark() + : rPaM.GetPoint(); + SwRedlineTable::size_type n = 0; + if( GetRedlineTable().FindAtPosition( *pStt, n ) ) + { + for( ; n < mpRedlineTable->size(); ++n ) + { + bRet = true; + SwRangeRedline* pTmp = (*mpRedlineTable)[ n ]; + if( pStt != pEnd && *pTmp->Start() > *pEnd ) + break; + + pTmp->SetComment( rS ); + if( *pTmp->End() >= *pEnd ) + break; + } + } + if( bRet ) + m_rDoc.getIDocumentState().SetModified(); + + return bRet; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +// Create a new author if necessary +std::size_t DocumentRedlineManager::GetRedlineAuthor() +{ + return SW_MOD()->GetRedlineAuthor(); +} + +/// Insert new author into the Table for the Readers etc. +std::size_t DocumentRedlineManager::InsertRedlineAuthor( const OUString& rNew ) +{ + return SW_MOD()->InsertRedlineAuthor(rNew); +} + +void DocumentRedlineManager::UpdateRedlineAttr() +{ + const SwRedlineTable& rTable = GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->IsVisible() ) + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +const uno::Sequence <sal_Int8>& DocumentRedlineManager::GetRedlinePassword() const +{ + return maRedlinePasswd; +} + +void DocumentRedlineManager::SetRedlinePassword( + /*[in]*/const uno::Sequence <sal_Int8>& rNewPassword) +{ + maRedlinePasswd = rNewPassword; + m_rDoc.getIDocumentState().SetModified(); +} + +/// Set comment text for the Redline, which is inserted later on via +/// AppendRedline. Is used by Autoformat. +/// A null pointer resets the mode. The pointer is not copied, so it +/// needs to stay valid! +void DocumentRedlineManager::SetAutoFormatRedlineComment( const OUString* pText, sal_uInt16 nSeqNo ) +{ + m_rDoc.SetAutoFormatRedline( nullptr != pText ); + if( pText ) + { + mpAutoFormatRedlnComment.reset( new OUString( *pText ) ); + } + else + { + mpAutoFormatRedlnComment.reset(); + } + + mnAutoFormatRedlnCommentNo = nSeqNo; +} + +DocumentRedlineManager::~DocumentRedlineManager() +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentSettingManager.cxx b/sw/source/core/doc/DocumentSettingManager.cxx new file mode 100644 index 000000000..ca68d3e30 --- /dev/null +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -0,0 +1,964 @@ +/* -*- 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 <DocumentSettingManager.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <comphelper/processfactory.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <svx/svdmodel.hxx> +#include <svl/asiancfg.hxx> +#include <unotools/compatibility.hxx> +#include <unotools/configmgr.hxx> +#include <drawdoc.hxx> +#include <swmodule.hxx> +#include <linkenum.hxx> +#include <rootfrm.hxx> +#include <breakit.hxx> +#include <docary.hxx> + +/* IDocumentSettingAccess */ + +sw::DocumentSettingManager::DocumentSettingManager(SwDoc &rDoc) + :m_rDoc(rDoc), + mnLinkUpdMode( GLOBALSETTING ), + meFieldUpdMode( AUTOUPD_GLOBALSETTING ), + meChrCmprType( CharCompressType::NONE ), + mn32DummyCompatibilityOptions1(0), + mn32DummyCompatibilityOptions2(0), + mbHTMLMode(false), + mbIsGlobalDoc(false), + mbGlblDocSaveLinks(false), + mbIsLabelDoc(false), + mbPurgeOLE(true), + mbKernAsianPunctuation(false), + + // COMPATIBILITY FLAGS START + + mbAddFlyOffsets(false), + mbAddVerticalFlyOffsets(false), + mbUseHiResolutionVirtualDevice(true), + mbMathBaselineAlignment(false), // default for *old* documents is 'off' + mbStylesNoDefault(false), + mbFloattableNomargins(false), + mEmbedFonts(false), + mEmbedUsedFonts(false), + mEmbedLatinScriptFonts(true), + mEmbedAsianScriptFonts(true), + mEmbedComplexScriptFonts(true), + mEmbedSystemFonts(false), + mbOldNumbering(false), + mbIgnoreFirstLineIndentInNumbering(false), + mbDoNotResetParaAttrsForNumFont(false), + mbTableRowKeep(false), + mbIgnoreTabsAndBlanksForLineCalculation(false), + mbDoNotCaptureDrawObjsOnPage(false), + mbClipAsCharacterAnchoredWriterFlyFrames(false), + mbUnixForceZeroExtLeading(false), + mbTabRelativeToIndent(true), + mbProtectForm(false), // i#78591# + mbMsWordCompTrailingBlanks(false), // tdf#104349 tdf#104668 + mbMsWordCompMinLineHeightByFly(false), + mbInvertBorderSpacing (false), + mbCollapseEmptyCellPara(true), + mbTabAtLeftIndentForParagraphsInList(false), //#i89181# + mbSmallCapsPercentage66(false), + mbTabOverflow(true), + mbUnbreakableNumberings(false), + mbClippedPictures(false), + mbBackgroundParaOverDrawings(false), + mbTabOverMargin(false), + mbTreatSingleColumnBreakAsPageBreak(false), + mbSurroundTextWrapSmall(false), + mbPropLineSpacingShrinksFirstLine(true), + mbSubtractFlys(false), + mApplyParagraphMarkFormatToNumbering(false), + mbLastBrowseMode( false ), + mbDisableOffPagePositioning ( false ), + mbProtectBookmarks(false), + mbProtectFields(false), + mbHeaderSpacingBelowLastPara(false) + + // COMPATIBILITY FLAGS END +{ + // COMPATIBILITY FLAGS START + + // Note: Any non-hidden compatibility flag should obtain its default + // by asking SvtCompatibilityOptions, see below. + + if (!utl::ConfigManager::IsFuzzing()) + { + const SvtCompatibilityOptions aOptions; + mbParaSpaceMax = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddSpacing ); + mbParaSpaceMaxAtPages = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddSpacingAtPages ); + mbTabCompat = !aOptions.GetDefault( SvtCompatibilityEntry::Index::UseOurTabStops ); + mbUseVirtualDevice = !aOptions.GetDefault( SvtCompatibilityEntry::Index::UsePrtMetrics ); + mbAddExternalLeading = !aOptions.GetDefault( SvtCompatibilityEntry::Index::NoExtLeading ); + mbOldLineSpacing = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseLineSpacing ); + mbAddParaSpacingToTableCells = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddTableSpacing ); + mbAddParaLineSpacingToTableCells = aOptions.GetDefault( SvtCompatibilityEntry::Index::AddTableLineSpacing ); + mbUseFormerObjectPos = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseObjectPositioning ); + mbUseFormerTextWrapping = aOptions.GetDefault( SvtCompatibilityEntry::Index::UseOurTextWrapping ); + mbConsiderWrapOnObjPos = aOptions.GetDefault( SvtCompatibilityEntry::Index::ConsiderWrappingStyle ); + mbDoNotJustifyLinesWithManualBreak = !aOptions.GetDefault( SvtCompatibilityEntry::Index::ExpandWordSpace ); + mbProtectForm = aOptions.GetDefault( SvtCompatibilityEntry::Index::ProtectForm ); + mbMsWordCompTrailingBlanks = aOptions.GetDefault( SvtCompatibilityEntry::Index::MsWordTrailingBlanks ); + mbSubtractFlys = aOptions.GetDefault( SvtCompatibilityEntry::Index::SubtractFlysAnchoredAtFlys ); + mbEmptyDbFieldHidesPara + = aOptions.GetDefault(SvtCompatibilityEntry::Index::EmptyDbFieldHidesPara); + } + else + { + mbParaSpaceMax = false; + mbParaSpaceMaxAtPages = false; + mbTabCompat = true; + mbUseVirtualDevice = true; + mbAddExternalLeading = true; + mbOldLineSpacing = false; + mbAddParaSpacingToTableCells = false; + mbAddParaLineSpacingToTableCells = false; + mbUseFormerObjectPos = false; + mbUseFormerTextWrapping = false; + mbConsiderWrapOnObjPos = false; + mbDoNotJustifyLinesWithManualBreak = true; + mbProtectForm = false; + mbMsWordCompTrailingBlanks = false; + mbSubtractFlys = false; + mbEmptyDbFieldHidesPara = true; + } + + // COMPATIBILITY FLAGS END + +} + + +sw::DocumentSettingManager::~DocumentSettingManager() +{ +} + +/* IDocumentSettingAccess */ +bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const +{ + switch (id) + { + // COMPATIBILITY FLAGS START + case DocumentSettingId::PARA_SPACE_MAX: return mbParaSpaceMax; //(n8Dummy1 & DUMMY_PARASPACEMAX); + case DocumentSettingId::PARA_SPACE_MAX_AT_PAGES: return mbParaSpaceMaxAtPages; //(n8Dummy1 & DUMMY_PARASPACEMAX_AT_PAGES); + case DocumentSettingId::TAB_COMPAT: return mbTabCompat; //(n8Dummy1 & DUMMY_TAB_COMPAT); + case DocumentSettingId::ADD_FLY_OFFSETS: return mbAddFlyOffsets; //(n8Dummy2 & DUMMY_ADD_FLY_OFFSETS); + case DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS: return mbAddVerticalFlyOffsets; + case DocumentSettingId::ADD_EXT_LEADING: return mbAddExternalLeading; //(n8Dummy2 & DUMMY_ADD_EXTERNAL_LEADING); + case DocumentSettingId::USE_VIRTUAL_DEVICE: return mbUseVirtualDevice; //(n8Dummy1 & DUMMY_USE_VIRTUAL_DEVICE); + case DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE: return mbUseHiResolutionVirtualDevice; //(n8Dummy2 & DUMMY_USE_HIRES_VIR_DEV); + case DocumentSettingId::OLD_NUMBERING: return mbOldNumbering; + case DocumentSettingId::OLD_LINE_SPACING: return mbOldLineSpacing; + case DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS: return mbAddParaSpacingToTableCells; + case DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS: return mbAddParaLineSpacingToTableCells; + case DocumentSettingId::USE_FORMER_OBJECT_POS: return mbUseFormerObjectPos; + case DocumentSettingId::USE_FORMER_TEXT_WRAPPING: return mbUseFormerTextWrapping; + case DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION: return mbConsiderWrapOnObjPos; + case DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK: return mbDoNotJustifyLinesWithManualBreak; + case DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING: return mbIgnoreFirstLineIndentInNumbering; + case DocumentSettingId::TABLE_ROW_KEEP: return mbTableRowKeep; + case DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION: return mbIgnoreTabsAndBlanksForLineCalculation; + case DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE: return mbDoNotCaptureDrawObjsOnPage; + // #i68949# + case DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME: return mbClipAsCharacterAnchoredWriterFlyFrames; + case DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING: return mbUnixForceZeroExtLeading; + case DocumentSettingId::TABS_RELATIVE_TO_INDENT : return mbTabRelativeToIndent; + case DocumentSettingId::PROTECT_FORM: return mbProtectForm; + // tdf#104349 tdf#104668 + case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: return mbMsWordCompTrailingBlanks; + case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: return mbMsWordCompMinLineHeightByFly; + // #i89181# + case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: return mbTabAtLeftIndentForParagraphsInList; + case DocumentSettingId::INVERT_BORDER_SPACING: return mbInvertBorderSpacing; + case DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA: return mbCollapseEmptyCellPara; + case DocumentSettingId::SMALL_CAPS_PERCENTAGE_66: return mbSmallCapsPercentage66; + case DocumentSettingId::TAB_OVERFLOW: return mbTabOverflow; + case DocumentSettingId::UNBREAKABLE_NUMBERINGS: return mbUnbreakableNumberings; + case DocumentSettingId::CLIPPED_PICTURES: return mbClippedPictures; + case DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS: return mbBackgroundParaOverDrawings; + case DocumentSettingId::TAB_OVER_MARGIN: return mbTabOverMargin; + case DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK: return mbTreatSingleColumnBreakAsPageBreak; + case DocumentSettingId::SURROUND_TEXT_WRAP_SMALL: return mbSurroundTextWrapSmall; + case DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE: return mbPropLineSpacingShrinksFirstLine; + case DocumentSettingId::SUBTRACT_FLYS: return mbSubtractFlys; + + case DocumentSettingId::BROWSE_MODE: return mbLastBrowseMode; // Attention: normally the SwViewShell has to be asked! + case DocumentSettingId::HTML_MODE: return mbHTMLMode; + case DocumentSettingId::GLOBAL_DOCUMENT: return mbIsGlobalDoc; + case DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS: return mbGlblDocSaveLinks; + case DocumentSettingId::LABEL_DOCUMENT: return mbIsLabelDoc; + case DocumentSettingId::PURGE_OLE: return mbPurgeOLE; + case DocumentSettingId::KERN_ASIAN_PUNCTUATION: return mbKernAsianPunctuation; + case DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT: return mbDoNotResetParaAttrsForNumFont; + case DocumentSettingId::MATH_BASELINE_ALIGNMENT: return mbMathBaselineAlignment; + case DocumentSettingId::STYLES_NODEFAULT: return mbStylesNoDefault; + case DocumentSettingId::FLOATTABLE_NOMARGINS: return mbFloattableNomargins; + case DocumentSettingId::EMBED_FONTS: return mEmbedFonts; + case DocumentSettingId::EMBED_USED_FONTS: return mEmbedUsedFonts; + case DocumentSettingId::EMBED_LATIN_SCRIPT_FONTS: return mEmbedLatinScriptFonts; + case DocumentSettingId::EMBED_ASIAN_SCRIPT_FONTS: return mEmbedAsianScriptFonts; + case DocumentSettingId::EMBED_COMPLEX_SCRIPT_FONTS: return mEmbedComplexScriptFonts; + case DocumentSettingId::EMBED_SYSTEM_FONTS: return mEmbedSystemFonts; + case DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING: return mApplyParagraphMarkFormatToNumbering; + case DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING: return mbDisableOffPagePositioning; + case DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA: return mbEmptyDbFieldHidesPara; + case DocumentSettingId::CONTINUOUS_ENDNOTES: return mbContinuousEndnotes; + case DocumentSettingId::PROTECT_BOOKMARKS: return mbProtectBookmarks; + case DocumentSettingId::PROTECT_FIELDS: return mbProtectFields; + case DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA: return mbHeaderSpacingBelowLastPara; + default: + OSL_FAIL("Invalid setting id"); + } + return false; +} + +void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ bool value) +{ + switch (id) + { + // COMPATIBILITY FLAGS START + case DocumentSettingId::PARA_SPACE_MAX: + mbParaSpaceMax = value; + break; + case DocumentSettingId::PARA_SPACE_MAX_AT_PAGES: + mbParaSpaceMaxAtPages = value; + break; + case DocumentSettingId::TAB_COMPAT: + mbTabCompat = value; + break; + case DocumentSettingId::ADD_FLY_OFFSETS: + mbAddFlyOffsets = value; + break; + case DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS: + mbAddVerticalFlyOffsets = value; + break; + case DocumentSettingId::ADD_EXT_LEADING: + mbAddExternalLeading = value; + break; + case DocumentSettingId::USE_VIRTUAL_DEVICE: + mbUseVirtualDevice = value; + break; + case DocumentSettingId::USE_HIRES_VIRTUAL_DEVICE: + mbUseHiResolutionVirtualDevice = value; + break; + case DocumentSettingId::OLD_NUMBERING: + if (mbOldNumbering != value) + { + mbOldNumbering = value; + + const SwNumRuleTable& rNmTable = m_rDoc.GetNumRuleTable(); + for( SwNumRuleTable::size_type n = 0; n < rNmTable.size(); ++n ) + rNmTable[n]->SetInvalidRule(true); + + m_rDoc.UpdateNumRule(); + + SwNumRule *pOutlineRule = m_rDoc.GetOutlineNumRule(); + if (pOutlineRule) + { + pOutlineRule->Validate(); + // counting of phantoms depends on <IsOldNumbering()> + pOutlineRule->SetCountPhantoms( !mbOldNumbering ); + } + } + break; + case DocumentSettingId::OLD_LINE_SPACING: + mbOldLineSpacing = value; + break; + case DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS: + mbAddParaSpacingToTableCells = value; + break; + case DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS: + mbAddParaLineSpacingToTableCells = value; + break; + case DocumentSettingId::USE_FORMER_OBJECT_POS: + mbUseFormerObjectPos = value; + break; + case DocumentSettingId::USE_FORMER_TEXT_WRAPPING: + mbUseFormerTextWrapping = value; + break; + case DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION: + mbConsiderWrapOnObjPos = value; + break; + case DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK: + mbDoNotJustifyLinesWithManualBreak = value; + break; + case DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING: + mbIgnoreFirstLineIndentInNumbering = value; + break; + + case DocumentSettingId::TABLE_ROW_KEEP: + mbTableRowKeep = value; + break; + + case DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION: + mbIgnoreTabsAndBlanksForLineCalculation = value; + break; + + case DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE: + mbDoNotCaptureDrawObjsOnPage = value; + break; + + // #i68949# + case DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME: + mbClipAsCharacterAnchoredWriterFlyFrames = value; + break; + + case DocumentSettingId::UNIX_FORCE_ZERO_EXT_LEADING: + mbUnixForceZeroExtLeading = value; + break; + + case DocumentSettingId::PROTECT_FORM: + mbProtectForm = value; + break; + + // tdf#140349 + case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: + mbMsWordCompTrailingBlanks = value; + break; + + case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: + mbMsWordCompMinLineHeightByFly = value; + break; + + case DocumentSettingId::TABS_RELATIVE_TO_INDENT: + mbTabRelativeToIndent = value; + break; + // #i89181# + case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: + mbTabAtLeftIndentForParagraphsInList = value; + break; + + case DocumentSettingId::INVERT_BORDER_SPACING: + mbInvertBorderSpacing = value; + break; + + case DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA: + mbCollapseEmptyCellPara = value; + break; + + case DocumentSettingId::SMALL_CAPS_PERCENTAGE_66: + mbSmallCapsPercentage66 = value; + break; + + case DocumentSettingId::TAB_OVERFLOW: + mbTabOverflow = value; + break; + + case DocumentSettingId::UNBREAKABLE_NUMBERINGS: + mbUnbreakableNumberings = value; + break; + + case DocumentSettingId::CLIPPED_PICTURES: + mbClippedPictures = value; + break; + + case DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS: + mbBackgroundParaOverDrawings = value; + break; + + case DocumentSettingId::TAB_OVER_MARGIN: + mbTabOverMargin = value; + break; + + case DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK: + mbTreatSingleColumnBreakAsPageBreak = value; + break; + + case DocumentSettingId::SURROUND_TEXT_WRAP_SMALL: + mbSurroundTextWrapSmall = value; + break; + + case DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE: + mbPropLineSpacingShrinksFirstLine = value; + break; + + case DocumentSettingId::SUBTRACT_FLYS: + mbSubtractFlys = value; + break; + + // COMPATIBILITY FLAGS END + + case DocumentSettingId::BROWSE_MODE: //can be used temporary (load/save) when no SwViewShell is available + mbLastBrowseMode = value; + break; + + case DocumentSettingId::HTML_MODE: + mbHTMLMode = value; + break; + + case DocumentSettingId::GLOBAL_DOCUMENT: + mbIsGlobalDoc = value; + break; + + case DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS: + mbGlblDocSaveLinks = value; + break; + + case DocumentSettingId::LABEL_DOCUMENT: + mbIsLabelDoc = value; + break; + + case DocumentSettingId::PURGE_OLE: + mbPurgeOLE = value; + break; + + case DocumentSettingId::KERN_ASIAN_PUNCTUATION: + mbKernAsianPunctuation = value; + break; + + case DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT: + mbDoNotResetParaAttrsForNumFont = value; + break; + case DocumentSettingId::MATH_BASELINE_ALIGNMENT: + mbMathBaselineAlignment = value; + break; + case DocumentSettingId::STYLES_NODEFAULT: + mbStylesNoDefault = value; + break; + case DocumentSettingId::FLOATTABLE_NOMARGINS: + mbFloattableNomargins = value; + break; + case DocumentSettingId::EMBED_FONTS: + mEmbedFonts = value; + break; + case DocumentSettingId::EMBED_USED_FONTS: + mEmbedUsedFonts = value; + break; + case DocumentSettingId::EMBED_LATIN_SCRIPT_FONTS: + mEmbedLatinScriptFonts = value; + break; + case DocumentSettingId::EMBED_ASIAN_SCRIPT_FONTS: + mEmbedAsianScriptFonts = value; + break; + case DocumentSettingId::EMBED_COMPLEX_SCRIPT_FONTS: + mEmbedComplexScriptFonts = value; + break; + case DocumentSettingId::EMBED_SYSTEM_FONTS: + mEmbedSystemFonts = value; + break; + case DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING: + mApplyParagraphMarkFormatToNumbering = value; + break; + case DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING: + mbDisableOffPagePositioning = value; + break; + case DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA: + mbEmptyDbFieldHidesPara = value; + break; + case DocumentSettingId::CONTINUOUS_ENDNOTES: + mbContinuousEndnotes = value; + break; + case DocumentSettingId::PROTECT_BOOKMARKS: + mbProtectBookmarks = value; + break; + case DocumentSettingId::PROTECT_FIELDS: + mbProtectFields = value; + break; + case DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA: + mbHeaderSpacingBelowLastPara = value; + break; + default: + OSL_FAIL("Invalid setting id"); + } +} + +const css::i18n::ForbiddenCharacters* + sw::DocumentSettingManager::getForbiddenCharacters(/*[in]*/ LanguageType nLang, /*[in]*/ bool bLocaleData ) const +{ + const css::i18n::ForbiddenCharacters* pRet = nullptr; + if (mxForbiddenCharsTable) + pRet = mxForbiddenCharsTable->GetForbiddenCharacters( nLang, false ); + if( bLocaleData && !pRet && g_pBreakIt ) + pRet = &g_pBreakIt->GetForbidden( nLang ); + return pRet; +} + +void sw::DocumentSettingManager::setForbiddenCharacters(/*[in]*/ LanguageType nLang, + /*[in]*/ const css::i18n::ForbiddenCharacters& rFChars ) +{ + if (!mxForbiddenCharsTable) + mxForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + mxForbiddenCharsTable->SetForbiddenCharacters( nLang, rFChars ); + + SdrModel *pDrawModel = m_rDoc.getIDocumentDrawModelAccess().GetDrawModel(); + if( pDrawModel ) + { + pDrawModel->SetForbiddenCharsTable(mxForbiddenCharsTable); + if( !m_rDoc.IsInReading() ) + pDrawModel->ReformatAllTextObjects(); + } + + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && !m_rDoc.IsInReading() ) + { + pTmpRoot->StartAllAction(); + for(SwRootFrame* aLayout : m_rDoc.GetAllLayouts()) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + pTmpRoot->EndAllAction(); + } + m_rDoc.getIDocumentState().SetModified(); +} + +std::shared_ptr<SvxForbiddenCharactersTable>& sw::DocumentSettingManager::getForbiddenCharacterTable() +{ + if (!mxForbiddenCharsTable) + mxForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + return mxForbiddenCharsTable; +} + +const std::shared_ptr<SvxForbiddenCharactersTable>& sw::DocumentSettingManager::getForbiddenCharacterTable() const +{ + return mxForbiddenCharsTable; +} + +sal_uInt16 sw::DocumentSettingManager::getLinkUpdateMode( /*[in]*/bool bGlobalSettings ) const +{ + sal_uInt16 nRet = mnLinkUpdMode; + if( bGlobalSettings && GLOBALSETTING == nRet ) + nRet = SW_MOD()->GetLinkUpdMode(); + return nRet; +} + +void sw::DocumentSettingManager::setLinkUpdateMode( /*[in]*/sal_uInt16 eMode ) +{ + mnLinkUpdMode = eMode; +} + +SwFieldUpdateFlags sw::DocumentSettingManager::getFieldUpdateFlags( /*[in]*/bool bGlobalSettings ) const +{ + SwFieldUpdateFlags eRet = meFieldUpdMode; + if( bGlobalSettings && AUTOUPD_GLOBALSETTING == eRet ) + eRet = SW_MOD()->GetFieldUpdateFlags(); + return eRet; +} + +void sw::DocumentSettingManager::setFieldUpdateFlags(/*[in]*/SwFieldUpdateFlags eMode ) +{ + meFieldUpdMode = eMode; +} + +CharCompressType sw::DocumentSettingManager::getCharacterCompressionType() const +{ + return meChrCmprType; +} + +void sw::DocumentSettingManager::setCharacterCompressionType( /*[in]*/CharCompressType n ) +{ + if( meChrCmprType != n ) + { + meChrCmprType = n; + + SdrModel *pDrawModel = m_rDoc.getIDocumentDrawModelAccess().GetDrawModel(); + if( pDrawModel ) + { + pDrawModel->SetCharCompressType( n ); + if( !m_rDoc.IsInReading() ) + pDrawModel->ReformatAllTextObjects(); + } + + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && !m_rDoc.IsInReading() ) + { + pTmpRoot->StartAllAction(); + for( auto aLayout : m_rDoc.GetAllLayouts() ) + aLayout->InvalidateAllContent(SwInvalidateFlags::Size); + pTmpRoot->EndAllAction(); + } + m_rDoc.getIDocumentState().SetModified(); + } +} + + +void sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSettingManager& rSource) +{ + Setn32DummyCompatibilityOptions1( rSource.Getn32DummyCompatibilityOptions1() ); + Setn32DummyCompatibilityOptions2( rSource.Getn32DummyCompatibilityOptions2() ); + + // No mbHTMLMode + // No mbIsGlobalDoc + // No mbGlblDocSaveLinks + // No mbIsLabelDoc + // No mbPurgeOLE + // No mbKernAsianPunctuation + mbParaSpaceMax = rSource.mbParaSpaceMax; + mbParaSpaceMaxAtPages = rSource.mbParaSpaceMaxAtPages; + mbTabCompat = rSource.mbTabCompat; + mbUseVirtualDevice = rSource.mbUseVirtualDevice; + mbAddFlyOffsets = rSource.mbAddFlyOffsets; + // No mbAddVerticalFlyOffsets + mbAddExternalLeading = rSource.mbAddExternalLeading; + mbUseHiResolutionVirtualDevice = rSource.mbUseHiResolutionVirtualDevice; + mbOldLineSpacing = rSource.mbOldLineSpacing; + mbAddParaSpacingToTableCells = rSource.mbAddParaSpacingToTableCells; + mbAddParaLineSpacingToTableCells = rSource.mbAddParaLineSpacingToTableCells; + mbUseFormerObjectPos = rSource.mbUseFormerObjectPos; + mbUseFormerTextWrapping = rSource.mbUseFormerTextWrapping; + mbConsiderWrapOnObjPos = rSource.mbConsiderWrapOnObjPos; + // No mbMathBaselineAlignment + // No mbStylesNoDefault + // No mbFloattableNomargins + mbOldNumbering = rSource.mbOldNumbering; + mbIgnoreFirstLineIndentInNumbering = rSource.mbIgnoreFirstLineIndentInNumbering; + mbDoNotJustifyLinesWithManualBreak = rSource.mbDoNotJustifyLinesWithManualBreak; + mbDoNotResetParaAttrsForNumFont = rSource.mbDoNotResetParaAttrsForNumFont; + mbTableRowKeep = rSource.mbTableRowKeep; + mbIgnoreTabsAndBlanksForLineCalculation = rSource.mbIgnoreTabsAndBlanksForLineCalculation; + mbDoNotCaptureDrawObjsOnPage = rSource.mbDoNotCaptureDrawObjsOnPage; + mbClipAsCharacterAnchoredWriterFlyFrames = rSource.mbClipAsCharacterAnchoredWriterFlyFrames; + mbUnixForceZeroExtLeading = rSource.mbUnixForceZeroExtLeading; + mbTabRelativeToIndent = rSource.mbTabRelativeToIndent; + // No mbProtectForm + mbMsWordCompTrailingBlanks = rSource.mbMsWordCompTrailingBlanks; + mbMsWordCompMinLineHeightByFly = rSource.mbMsWordCompMinLineHeightByFly; + // No mbInvertBorderSpacing + mbCollapseEmptyCellPara = rSource.mbCollapseEmptyCellPara; + mbTabAtLeftIndentForParagraphsInList = rSource.mbTabAtLeftIndentForParagraphsInList; + // No mbSmallCapsPercentage66 + // No mbTabOverflow + mbUnbreakableNumberings = rSource.mbUnbreakableNumberings; + // No mbClippedPictures + // No mbBackgroundParaOverDrawings + mbTabOverMargin = rSource.mbTabOverMargin; + // No mbTreatSingleColumnBreakAsPageBreak + // No mbSurroundTextWrapSmall + // No mbPropLineSpacingShrinksFirstLine + mbSubtractFlys = rSource.mbSubtractFlys; + // No mbLastBrowseMode + mbDisableOffPagePositioning = rSource.mbDisableOffPagePositioning; + // No mbEmptyDbFieldHidesPara + mbEmptyDbFieldHidesPara = rSource.mbEmptyDbFieldHidesPara; + mbContinuousEndnotes = rSource.mbContinuousEndnotes; + // No mbProtectBookmarks + // No mbProtectFields + mbHeaderSpacingBelowLastPara = rSource.mbHeaderSpacingBelowLastPara; +} + +sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions1() const +{ + return mn32DummyCompatibilityOptions1; +} + +void sw::DocumentSettingManager::Setn32DummyCompatibilityOptions1( const sal_uInt32 CompatibilityOptions1 ) +{ + mn32DummyCompatibilityOptions1 = CompatibilityOptions1; +} + +sal_uInt32 sw::DocumentSettingManager::Getn32DummyCompatibilityOptions2() const +{ + return mn32DummyCompatibilityOptions2; +} + +void sw::DocumentSettingManager::Setn32DummyCompatibilityOptions2( const sal_uInt32 CompatibilityOptions2 ) +{ + mn32DummyCompatibilityOptions2 = CompatibilityOptions2; +} + +void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("DocumentSettingManager")); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbHTMLMode")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHTMLMode).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsGlobalDoc")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsGlobalDoc).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbGlblDocSaveLinks")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbGlblDocSaveLinks).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsLabelDoc")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsLabelDoc).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbPurgeOLE")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPurgeOLE).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbKernAsianPunctuation")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbKernAsianPunctuation).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMax")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMax).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMaxAtPages")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMaxAtPages).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabCompat")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabCompat).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseVirtualDevice")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseVirtualDevice).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddFlyOffsets")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddFlyOffsets).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddVerticalFlyOffsets")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddVerticalFlyOffsets).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddExternalLeading")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddExternalLeading).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseHiResolutionVirtualDevice")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseHiResolutionVirtualDevice).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldLineSpacing")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldLineSpacing).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaSpacingToTableCells")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaSpacingToTableCells).getStr())); + xmlTextWriterEndElement(pWriter); + xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaLineSpacingToTableCells")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaLineSpacingToTableCells).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerObjectPos")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerObjectPos).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerTextWrapping")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerTextWrapping).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbConsiderWrapOnObjPos")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbConsiderWrapOnObjPos).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMathBaselineAlignment")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMathBaselineAlignment).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbStylesNoDefault")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbStylesNoDefault).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbFloattableNomargins")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbFloattableNomargins).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldNumbering")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldNumbering).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreFirstLineIndentInNumbering")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreFirstLineIndentInNumbering).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotJustifyLinesWithManualBreak")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotJustifyLinesWithManualBreak).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotResetParaAttrsForNumFont")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotResetParaAttrsForNumFont).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTableRowKeep")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTableRowKeep).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreTabsAndBlanksForLineCalculation")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreTabsAndBlanksForLineCalculation).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotCaptureDrawObjsOnPage")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotCaptureDrawObjsOnPage).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbClipAsCharacterAnchoredWriterFlyFrames")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClipAsCharacterAnchoredWriterFlyFrames).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnixForceZeroExtLeading")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnixForceZeroExtLeading).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabRelativeToIndent")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabRelativeToIndent).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbProtectForm")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbProtectForm).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompTrailingBlanks")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompTrailingBlanks).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinLineHeightByFly")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompMinLineHeightByFly).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbInvertBorderSpacing")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbInvertBorderSpacing).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbCollapseEmptyCellPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbCollapseEmptyCellPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabAtLeftIndentForParagraphsInList")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabAtLeftIndentForParagraphsInList).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSmallCapsPercentage66")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSmallCapsPercentage66).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverflow")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverflow).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnbreakableNumberings")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnbreakableNumberings).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbClippedPictures")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClippedPictures).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbBackgroundParaOverDrawings")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbBackgroundParaOverDrawings).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverMargin")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverMargin).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbTreatSingleColumnBreakAsPageBreak")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTreatSingleColumnBreakAsPageBreak).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSurroundTextWrapSmall")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSurroundTextWrapSmall).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbPropLineSpacingShrinksFirstLine")); + xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPropLineSpacingShrinksFirstLine).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbSubtractFlys")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSubtractFlys).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbLastBrowseMode")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbLastBrowseMode).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbDisableOffPagePositioning")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDisableOffPagePositioning).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbEmptyDbFieldHidesPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbEmptyDbFieldHidesPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbContinuousEndnotes")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbContinuousEndnotes).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbHeaderSpacingBelowLastPara")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHeaderSpacingBelowLastPara).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStateManager.cxx b/sw/source/core/doc/DocumentStateManager.cxx new file mode 100644 index 000000000..bf965d54b --- /dev/null +++ b/sw/source/core/doc/DocumentStateManager.cxx @@ -0,0 +1,118 @@ +/* -*- 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 <DocumentStateManager.hxx> +#include <doc.hxx> +#include <DocumentStatisticsManager.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentLayoutManager.hxx> +#include <acorrect.hxx> + +namespace sw +{ + +DocumentStateManager::DocumentStateManager( SwDoc& i_rSwdoc ) : + m_rDoc( i_rSwdoc ), + mbEnableSetModified(true), + mbModified(false), + mbUpdateExpField(false), + mbNewDoc(false), + mbInCallModified(false) +{ +} + +void DocumentStateManager::SetModified() +{ + if (!IsEnableSetModified()) + return; + + m_rDoc.GetDocumentLayoutManager().ClearSwLayouterEntries(); + mbModified = true; + m_rDoc.GetDocumentStatisticsManager().SetDocStatModified( true ); + if( m_rDoc.GetOle2Link().IsSet() ) + { + mbInCallModified = true; + m_rDoc.GetOle2Link().Call( true ); + mbInCallModified = false; + } + + if( m_rDoc.GetAutoCorrExceptWord() && !m_rDoc.GetAutoCorrExceptWord()->IsDeleted() ) + m_rDoc.DeleteAutoCorrExceptWord(); +} + +void DocumentStateManager::ResetModified() +{ + // give the old and new modified state to the link + // Bit 0: -> old state + // Bit 1: -> new state + bool bOldModified = mbModified; + mbModified = false; + m_rDoc.GetDocumentStatisticsManager().SetDocStatModified( false ); + m_rDoc.GetIDocumentUndoRedo().SetUndoNoModifiedPosition(); + if( bOldModified && m_rDoc.GetOle2Link().IsSet() ) + { + mbInCallModified = true; + m_rDoc.GetOle2Link().Call( false ); + mbInCallModified = false; + } +} + +bool DocumentStateManager::IsModified() const +{ + return mbModified; +} + +bool DocumentStateManager::IsEnableSetModified() const +{ + return mbEnableSetModified; +} + +void DocumentStateManager::SetEnableSetModified(bool bEnableSetModified) +{ + mbEnableSetModified = bEnableSetModified; +} + +bool DocumentStateManager::IsInCallModified() const +{ + return mbInCallModified; +} + +bool DocumentStateManager::IsUpdateExpField() const +{ + return mbUpdateExpField; +} + +bool DocumentStateManager::IsNewDoc() const +{ + return mbNewDoc; +} + +void DocumentStateManager::SetNewDoc(bool b) +{ + mbNewDoc = b; +} + +void DocumentStateManager::SetUpdateExpFieldStat(bool b) +{ + mbUpdateExpField = b; +} + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStatisticsManager.cxx b/sw/source/core/doc/DocumentStatisticsManager.cxx new file mode 100644 index 000000000..9508e6d72 --- /dev/null +++ b/sw/source/core/doc/DocumentStatisticsManager.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <DocumentStatisticsManager.hxx> +#include <doc.hxx> +#include <fldbas.hxx> +#include <docsh.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <view.hxx> +#include <ndtxt.hxx> +#include <calbck.hxx> +#include <fmtfld.hxx> +#include <rootfrm.hxx> +#include <docufld.hxx> +#include <docstat.hxx> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> + +using namespace ::com::sun::star; + +namespace sw +{ + +DocumentStatisticsManager::DocumentStatisticsManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ), + mpDocStat( new SwDocStat ), + mbInitialized( false ), + maStatsUpdateIdle( i_rSwdoc ) + +{ + maStatsUpdateIdle.SetPriority( TaskPriority::LOWEST ); + maStatsUpdateIdle.SetInvokeHandler( LINK( this, DocumentStatisticsManager, DoIdleStatsUpdate ) ); + maStatsUpdateIdle.SetDebugName( "sw::DocumentStatisticsManager maStatsUpdateIdle" ); +} + +void DocumentStatisticsManager::DocInfoChgd(bool const isEnableSetModified) +{ + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DocInfo )->UpdateFields(); + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::TemplateName )->UpdateFields(); + if (isEnableSetModified) + { + m_rDoc.getIDocumentState().SetModified(); + } +} + +const SwDocStat& DocumentStatisticsManager::GetDocStat() const +{ + return *mpDocStat; +} + +void DocumentStatisticsManager::SetDocStatModified(bool bSet) +{ + mpDocStat->bModified = bSet; +} + +const SwDocStat& DocumentStatisticsManager::GetUpdatedDocStat( bool bCompleteAsync, bool bFields ) +{ + if( mpDocStat->bModified || !mbInitialized) + { + UpdateDocStat( bCompleteAsync, bFields ); + } + return *mpDocStat; +} + +void DocumentStatisticsManager::SetDocStat( const SwDocStat& rStat ) +{ + *mpDocStat = rStat; + mbInitialized = true; +} + +void DocumentStatisticsManager::UpdateDocStat( bool bCompleteAsync, bool bFields ) +{ + if( mpDocStat->bModified || !mbInitialized) + { + if (!bCompleteAsync) + { + maStatsUpdateIdle.Stop(); + while (IncrementalDocStatCalculate( + std::numeric_limits<long>::max(), bFields)) {} + } + else if (IncrementalDocStatCalculate(5000, bFields)) + maStatsUpdateIdle.Start(); + else + maStatsUpdateIdle.Stop(); + } +} + +// returns true while there is more to do +bool DocumentStatisticsManager::IncrementalDocStatCalculate(long nChars, bool bFields) +{ + mbInitialized = true; + mpDocStat->Reset(); + mpDocStat->nPara = 0; // default is 1! + + // This is the inner loop - at least while the paras are dirty. + for( sal_uLong i = m_rDoc.GetNodes().Count(); i > 0 && nChars > 0; ) + { + SwNode* pNd = m_rDoc.GetNodes()[ --i ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + { + long const nOldChars(mpDocStat->nChar); + SwTextNode *pText = static_cast< SwTextNode * >( pNd ); + if (pText->CountWords(*mpDocStat, 0, pText->GetText().getLength())) + { + nChars -= (mpDocStat->nChar - nOldChars); + } + break; + } + case SwNodeType::Table: ++mpDocStat->nTable; break; + case SwNodeType::Grf: ++mpDocStat->nGrf; break; + case SwNodeType::Ole: ++mpDocStat->nOLE; break; + case SwNodeType::Section: break; + default: break; + } + } + + // #i93174#: notes contain paragraphs that are not nodes + { + SwFieldType * const pPostits( m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Postit) ); + std::vector<SwFormatField*> vFields; + pPostits->GatherFields(vFields); + for(auto pFormatField : vFields) + { + const auto pField = static_cast<SwPostItField const*>(pFormatField->GetField()); + mpDocStat->nAllPara += pField->GetNumberOfParagraphs(); + } + } + + mpDocStat->nPage = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ? m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->GetPageNum() : 0; + SetDocStatModified( false ); + + css::uno::Sequence < css::beans::NamedValue > aStat( mpDocStat->nPage ? 8 : 7); + sal_Int32 n=0; + aStat[n].Name = "TableCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nTable); + aStat[n].Name = "ImageCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nGrf); + aStat[n].Name = "ObjectCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nOLE); + if ( mpDocStat->nPage ) + { + aStat[n].Name = "PageCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nPage); + } + aStat[n].Name = "ParagraphCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nPara); + aStat[n].Name = "WordCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nWord); + aStat[n].Name = "CharacterCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nChar); + aStat[n].Name = "NonWhitespaceCharacterCount"; + aStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nCharExcludingSpaces); + + // For e.g. autotext documents there is no pSwgInfo (#i79945) + SwDocShell* pObjShell(m_rDoc.GetDocShell()); + if (pObjShell) + { + const uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pObjShell->GetModel(), uno::UNO_QUERY_THROW); + const uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + // #i96786#: do not set modified flag when updating statistics + const bool bDocWasModified( m_rDoc.getIDocumentState().IsModified() ); + const ModifyBlocker_Impl b(pObjShell); + // rhbz#1081176: don't jump to cursor pos because of (temporary) + // activation of modified flag triggering move to input position + auto aViewGuard(pObjShell->LockAllViews()); + xDocProps->setDocumentStatistics(aStat); + if (!bDocWasModified) + { + m_rDoc.getIDocumentState().ResetModified(); + } + } + + // optionally update stat. fields + if (bFields) + { + SwFieldType *pType = m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocStat); + pType->UpdateFields(); + } + + return nChars < 0; +} + +IMPL_LINK( DocumentStatisticsManager, DoIdleStatsUpdate, Timer *, pIdle, void ) +{ + if (IncrementalDocStatCalculate(32000)) + pIdle->Start(); + SwView* pView = m_rDoc.GetDocShell() ? m_rDoc.GetDocShell()->GetView() : nullptr; + if( pView ) + pView->UpdateDocStats(); +} + +DocumentStatisticsManager::~DocumentStatisticsManager() +{ + maStatsUpdateIdle.Stop(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentStylePoolManager.cxx b/sw/source/core/doc/DocumentStylePoolManager.cxx new file mode 100644 index 000000000..b3ccc8913 --- /dev/null +++ b/sw/source/core/doc/DocumentStylePoolManager.cxx @@ -0,0 +1,2655 @@ +/* -*- 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 <DocumentStylePoolManager.hxx> +#include <SwStyleNameMapper.hxx> +#include <doc.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentState.hxx> +#include <IDocumentUndoRedo.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <paratr.hxx> +#include <poolfmt.hxx> +#include <fmtornt.hxx> +#include <charfmt.hxx> +#include <fmtsrnd.hxx> +#include <docary.hxx> +#include <GetMetricVal.hxx> +#include <pagedesc.hxx> +#include <frmfmt.hxx> +#include <fmtline.hxx> +#include <numrule.hxx> +#include <hints.hxx> +#include <editeng/paperinf.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <strings.hrc> +#include <frmatr.hxx> +#include <frameformats.hxx> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> + +using namespace ::editeng; +using namespace ::com::sun::star; + +bool IsConditionalByPoolId(sal_uInt16 nId) +{ + // TODO: why is this style conditional? + // If it is changed to no longer be conditional, then a style "Text Body" + // will be imported without its conditions from ODF. + return RES_POOLCOLL_TEXT == nId; +} + +namespace +{ + static const sal_uInt16 PT_3 = 3 * 20; // 3 pt + static const sal_uInt16 PT_6 = 6 * 20; // 6 pt + static const sal_uInt16 PT_7 = 7 * 20; // 7 pt + static const sal_uInt16 PT_10 = 10 * 20; // 10 pt + static const sal_uInt16 PT_12 = 12 * 20; // 12 pt + static const sal_uInt16 PT_14 = 14 * 20; // 14 pt + static const sal_uInt16 PT_16 = 16 * 20; // 16 pt + static const sal_uInt16 PT_18 = 18 * 20; // 18 pt + static const sal_uInt16 PT_24 = 24 * 20; // 24 pt + static const sal_uInt16 PT_28 = 28 * 20; // 28 pt + + #define HTML_PARSPACE GetMetricVal( CM_05 ) + + static const sal_uInt16 aHeadlineSizes[ 2 * MAXLEVEL ] = { + // we do everything percentual now: + 130, 115, 101, 95, 85, + 85, 80, 80, 75, 75, // normal + PT_24, PT_18, PT_14, PT_12, PT_10, + PT_7, PT_7, PT_7, PT_7, PT_7 // HTML mode + }; + + long lcl_GetRightMargin( SwDoc& rDoc ) + { + // Make sure that the printer settings are taken over to the standard + // page style + const SwFrameFormat& rPgDscFormat = rDoc.GetPageDesc( 0 ).GetMaster(); + const SvxLRSpaceItem& rLR = rPgDscFormat.GetLRSpace(); + const long nLeft = rLR.GetLeft(); + const long nRight = rLR.GetRight(); + const long nWidth = rPgDscFormat.GetFrameSize().GetWidth(); + return nWidth - nLeft - nRight; + } + + void lcl_SetDfltFont( DefaultFontType nFntType, SfxItemSet& rSet ) + { + static struct { + sal_uInt16 nResLngId; + sal_uInt16 nResFntId; + } aArr[ 3 ] = { + { RES_CHRATR_LANGUAGE, RES_CHRATR_FONT }, + { RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_FONT }, + { RES_CHRATR_CTL_LANGUAGE, RES_CHRATR_CTL_FONT } + }; + for(const auto & n : aArr) + { + LanguageType nLng = static_cast<const SvxLanguageItem&>(rSet.GetPool()->GetDefaultItem( + n.nResLngId )).GetLanguage(); + vcl::Font aFnt( OutputDevice::GetDefaultFont( nFntType, + nLng, GetDefaultFontFlags::OnlyOne ) ); + + rSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), n.nResFntId )); + } + } + + void lcl_SetDfltFont( DefaultFontType nLatinFntType, DefaultFontType nCJKFntType, + DefaultFontType nCTLFntType, SfxItemSet& rSet ) + { + static struct { + sal_uInt16 nResLngId; + sal_uInt16 nResFntId; + DefaultFontType nFntType; + } aArr[ 3 ] = { + { RES_CHRATR_LANGUAGE, RES_CHRATR_FONT, static_cast<DefaultFontType>(0) }, + { RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_FONT, static_cast<DefaultFontType>(0) }, + { RES_CHRATR_CTL_LANGUAGE, RES_CHRATR_CTL_FONT, static_cast<DefaultFontType>(0) } + }; + aArr[0].nFntType = nLatinFntType; + aArr[1].nFntType = nCJKFntType; + aArr[2].nFntType = nCTLFntType; + + for(const auto & n : aArr) + { + LanguageType nLng = static_cast<const SvxLanguageItem&>(rSet.GetPool()->GetDefaultItem( + n.nResLngId )).GetLanguage(); + vcl::Font aFnt( OutputDevice::GetDefaultFont( n.nFntType, + nLng, GetDefaultFontFlags::OnlyOne ) ); + + rSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), n.nResFntId )); + } + } + + void lcl_SetHeadline( SwDoc* pDoc, SwTextFormatColl* pColl, + SfxItemSet& rSet, + sal_uInt16 nOutLvlBits, sal_uInt8 nLevel, bool bItalic ) + { + SetAllScriptItem( rSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SvxFontHeightItem aHItem(240, 100, RES_CHRATR_FONTSIZE); + const bool bHTMLMode = pDoc->GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE); + if( bHTMLMode ) + aHItem.SetHeight( aHeadlineSizes[ MAXLEVEL + nLevel ] ); + else + aHItem.SetHeight( PT_14, aHeadlineSizes[ nLevel ] ); + SetAllScriptItem( rSet, aHItem ); + + if( bItalic && !bHTMLMode ) + SetAllScriptItem( rSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE ) ); + + if( bHTMLMode ) + { + lcl_SetDfltFont( DefaultFontType::LATIN_TEXT, DefaultFontType::CJK_TEXT, + DefaultFontType::CTL_TEXT, rSet ); + } + + if( !pColl ) + return; + + if( !( nOutLvlBits & ( 1 << nLevel )) ) + { + pColl->AssignToListLevelOfOutlineStyle(nLevel); + if( !bHTMLMode ) + { + SwNumRule * pOutlineRule = pDoc->GetOutlineNumRule(); + const SwNumFormat& rNFormat = pOutlineRule->Get( nLevel ); + + if ( rNFormat.GetPositionAndSpaceMode() == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION && + ( rNFormat.GetAbsLSpace() || rNFormat.GetFirstLineOffset() ) ) + { + SvxLRSpaceItem aLR( pColl->GetFormatAttr( RES_LR_SPACE ) ); + aLR.SetTextFirstLineOffsetValue( rNFormat.GetFirstLineOffset() ); + //TODO: overflow + aLR.SetTextLeft( rNFormat.GetAbsLSpace() ); + pColl->SetFormatAttr( aLR ); + } + + // All paragraph styles, which are assigned to a level of the + // outline style has to have the outline style set as its list style. + SwNumRuleItem aItem(pOutlineRule->GetName()); + pColl->SetFormatAttr(aItem); + } + } + pColl->SetNextTextFormatColl( *pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + + void lcl_SetRegister( SwDoc* pDoc, SfxItemSet& rSet, sal_uInt16 nFact, + bool bHeader, bool bTab ) + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + sal_uInt16 nLeft = nFact ? GetMetricVal( CM_05 ) * nFact : 0; + aLR.SetTextLeft( nLeft ); + + rSet.Put( aLR ); + if( bHeader ) + { + SetAllScriptItem( rSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( rSet, SvxFontHeightItem( PT_16, 100, RES_CHRATR_FONTSIZE ) ); + } + if( bTab ) + { + long nRightMargin = lcl_GetRightMargin( *pDoc ); + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( nRightMargin - nLeft, + SvxTabAdjust::Right, + cDfltDecimalChar, '.' )); + rSet.Put( aTStops ); + } + } + + void lcl_SetNumBul( SwDoc* pDoc, SwTextFormatColl* pColl, + SfxItemSet& rSet, + sal_uInt16 nNxt, SwTwips nEZ, SwTwips nLeft, + SwTwips nUpper, SwTwips nLower ) + { + + SvxLRSpaceItem aLR( RES_LR_SPACE ); + SvxULSpaceItem aUL( RES_UL_SPACE ); + aLR.SetTextFirstLineOffset( sal_uInt16(nEZ) ); + aLR.SetTextLeft( sal_uInt16(nLeft) ); + aUL.SetUpper( sal_uInt16(nUpper) ); + aUL.SetLower( sal_uInt16(nLower) ); + rSet.Put( aLR ); + rSet.Put( aUL ); + + if( pColl ) + pColl->SetNextTextFormatColl( *pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nNxt )); + } + + void lcl_PutStdPageSizeIntoItemSet( SwDoc* pDoc, SfxItemSet& rSet ) + { + SwPageDesc* pStdPgDsc = pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + SwFormatFrameSize aFrameSz( pStdPgDsc->GetMaster().GetFrameSize() ); + if( pStdPgDsc->GetLandscape() ) + { + SwTwips nTmp = aFrameSz.GetHeight(); + aFrameSz.SetHeight( aFrameSz.GetWidth() ); + aFrameSz.SetWidth( nTmp ); + } + rSet.Put( aFrameSz ); + } +} + +static const char* STR_POOLCOLL_TEXT_ARY[] = +{ + // Category Text + STR_POOLCOLL_STANDARD, + STR_POOLCOLL_TEXT, + STR_POOLCOLL_TEXT_IDENT, + STR_POOLCOLL_TEXT_NEGIDENT, + STR_POOLCOLL_TEXT_MOVE, + STR_POOLCOLL_GREETING, + STR_POOLCOLL_SIGNATURE, + STR_POOLCOLL_CONFRONTATION, + STR_POOLCOLL_MARGINAL, + // Subcategory Headlines + STR_POOLCOLL_HEADLINE_BASE, + STR_POOLCOLL_HEADLINE1, + STR_POOLCOLL_HEADLINE2, + STR_POOLCOLL_HEADLINE3, + STR_POOLCOLL_HEADLINE4, + STR_POOLCOLL_HEADLINE5, + STR_POOLCOLL_HEADLINE6, + STR_POOLCOLL_HEADLINE7, + STR_POOLCOLL_HEADLINE8, + STR_POOLCOLL_HEADLINE9, + STR_POOLCOLL_HEADLINE10 +}; + +static const char* STR_POOLCOLL_LISTS_ARY[] +{ + // Category Lists + STR_POOLCOLL_NUMBER_BULLET_BASE, + // Subcategory Numbering + STR_POOLCOLL_NUM_LEVEL1S, + STR_POOLCOLL_NUM_LEVEL1, + STR_POOLCOLL_NUM_LEVEL1E, + STR_POOLCOLL_NUM_NONUM1, + STR_POOLCOLL_NUM_LEVEL2S, + STR_POOLCOLL_NUM_LEVEL2, + STR_POOLCOLL_NUM_LEVEL2E, + STR_POOLCOLL_NUM_NONUM2, + STR_POOLCOLL_NUM_LEVEL3S, + STR_POOLCOLL_NUM_LEVEL3, + STR_POOLCOLL_NUM_LEVEL3E, + STR_POOLCOLL_NUM_NONUM3, + STR_POOLCOLL_NUM_LEVEL4S, + STR_POOLCOLL_NUM_LEVEL4, + STR_POOLCOLL_NUM_LEVEL4E, + STR_POOLCOLL_NUM_NONUM4, + STR_POOLCOLL_NUM_LEVEL5S, + STR_POOLCOLL_NUM_LEVEL5, + STR_POOLCOLL_NUM_LEVEL5E, + STR_POOLCOLL_NUM_NONUM5, + + // Subcategory Enumeration + STR_POOLCOLL_BULLET_LEVEL1S, + STR_POOLCOLL_BULLET_LEVEL1, + STR_POOLCOLL_BULLET_LEVEL1E, + STR_POOLCOLL_BULLET_NONUM1, + STR_POOLCOLL_BULLET_LEVEL2S, + STR_POOLCOLL_BULLET_LEVEL2, + STR_POOLCOLL_BULLET_LEVEL2E, + STR_POOLCOLL_BULLET_NONUM2, + STR_POOLCOLL_BULLET_LEVEL3S, + STR_POOLCOLL_BULLET_LEVEL3, + STR_POOLCOLL_BULLET_LEVEL3E, + STR_POOLCOLL_BULLET_NONUM3, + STR_POOLCOLL_BULLET_LEVEL4S, + STR_POOLCOLL_BULLET_LEVEL4, + STR_POOLCOLL_BULLET_LEVEL4E, + STR_POOLCOLL_BULLET_NONUM4, + STR_POOLCOLL_BULLET_LEVEL5S, + STR_POOLCOLL_BULLET_LEVEL5, + STR_POOLCOLL_BULLET_LEVEL5E, + STR_POOLCOLL_BULLET_NONUM5 +}; + +// Special Areas +static const char* STR_POOLCOLL_EXTRA_ARY[] +{ + // Subcategory Header + STR_POOLCOLL_HEADERFOOTER, + STR_POOLCOLL_HEADER, + STR_POOLCOLL_HEADERL, + STR_POOLCOLL_HEADERR, + // Subcategory Footer + STR_POOLCOLL_FOOTER, + STR_POOLCOLL_FOOTERL, + STR_POOLCOLL_FOOTERR, + // Subcategory Table + STR_POOLCOLL_TABLE, + STR_POOLCOLL_TABLE_HDLN, + // Subcategory Labels + STR_POOLCOLL_LABEL, + STR_POOLCOLL_LABEL_ABB, + STR_POOLCOLL_LABEL_TABLE, + STR_POOLCOLL_LABEL_FRAME, + STR_POOLCOLL_LABEL_FIGURE, + // Miscellaneous + STR_POOLCOLL_FRAME, + STR_POOLCOLL_FOOTNOTE, + STR_POOLCOLL_JAKETADRESS, + STR_POOLCOLL_SENDADRESS, + STR_POOLCOLL_ENDNOTE, + STR_POOLCOLL_LABEL_DRAWING +}; + +static const char* STR_POOLCOLL_REGISTER_ARY[] = +{ + // Category Directories + STR_POOLCOLL_REGISTER_BASE, + // Subcategory Index-Directories + STR_POOLCOLL_TOX_IDXH, + STR_POOLCOLL_TOX_IDX1, + STR_POOLCOLL_TOX_IDX2, + STR_POOLCOLL_TOX_IDX3, + STR_POOLCOLL_TOX_IDXBREAK, + // Subcategory Tables of Contents + STR_POOLCOLL_TOX_CNTNTH, + STR_POOLCOLL_TOX_CNTNT1, + STR_POOLCOLL_TOX_CNTNT2, + STR_POOLCOLL_TOX_CNTNT3, + STR_POOLCOLL_TOX_CNTNT4, + STR_POOLCOLL_TOX_CNTNT5, + // Subcategory User-Directories: + STR_POOLCOLL_TOX_USERH, + STR_POOLCOLL_TOX_USER1, + STR_POOLCOLL_TOX_USER2, + STR_POOLCOLL_TOX_USER3, + STR_POOLCOLL_TOX_USER4, + STR_POOLCOLL_TOX_USER5, + // Subcategory Table of Contents more Levels 5 - 10 + STR_POOLCOLL_TOX_CNTNT6, + STR_POOLCOLL_TOX_CNTNT7, + STR_POOLCOLL_TOX_CNTNT8, + STR_POOLCOLL_TOX_CNTNT9, + STR_POOLCOLL_TOX_CNTNT10, + // Illustrations Index + STR_POOLCOLL_TOX_ILLUSH, + STR_POOLCOLL_TOX_ILLUS1, + // Object Index + STR_POOLCOLL_TOX_OBJECTH, + STR_POOLCOLL_TOX_OBJECT1, + // Tables Index + STR_POOLCOLL_TOX_TABLESH, + STR_POOLCOLL_TOX_TABLES1, + // Index of Authorities + STR_POOLCOLL_TOX_AUTHORITIESH, + STR_POOLCOLL_TOX_AUTHORITIES1, + // Subcategory User-Directories more Levels 5 - 10 + STR_POOLCOLL_TOX_USER6, + STR_POOLCOLL_TOX_USER7, + STR_POOLCOLL_TOX_USER8, + STR_POOLCOLL_TOX_USER9, + STR_POOLCOLL_TOX_USER10 +}; + +static const char* STR_POOLCOLL_DOC_ARY[] = +{ + // Category Chapter/Document + STR_POOLCOLL_DOC_TITLE, + STR_POOLCOLL_DOC_SUBTITLE, + STR_POOLCOLL_DOC_APPENDIX +}; + +static const char* STR_POOLCOLL_HTML_ARY[] = +{ + // Category HTML-Templates + STR_POOLCOLL_HTML_BLOCKQUOTE, + STR_POOLCOLL_HTML_PRE, + STR_POOLCOLL_HTML_HR, + STR_POOLCOLL_HTML_DD, + STR_POOLCOLL_HTML_DT +}; + +static const char* STR_POOLCHR_ARY[] = +{ + STR_POOLCHR_FOOTNOTE, + STR_POOLCHR_PAGENO, + STR_POOLCHR_LABEL, + STR_POOLCHR_DROPCAPS, + STR_POOLCHR_NUM_LEVEL, + STR_POOLCHR_BULLET_LEVEL, + STR_POOLCHR_INET_NORMAL, + STR_POOLCHR_INET_VISIT, + STR_POOLCHR_JUMPEDIT, + STR_POOLCHR_TOXJUMP, + STR_POOLCHR_ENDNOTE, + STR_POOLCHR_LINENUM, + STR_POOLCHR_IDX_MAIN_ENTRY, + STR_POOLCHR_FOOTNOTE_ANCHOR, + STR_POOLCHR_ENDNOTE_ANCHOR, + STR_POOLCHR_RUBYTEXT, + STR_POOLCHR_VERT_NUM +}; + +static const char* STR_POOLCHR_HTML_ARY[] = +{ + STR_POOLCHR_HTML_EMPHASIS, + STR_POOLCHR_HTML_CITIATION, + STR_POOLCHR_HTML_STRONG, + STR_POOLCHR_HTML_CODE, + STR_POOLCHR_HTML_SAMPLE, + STR_POOLCHR_HTML_KEYBOARD, + STR_POOLCHR_HTML_VARIABLE, + STR_POOLCHR_HTML_DEFINSTANCE, + STR_POOLCHR_HTML_TELETYPE +}; + +static const char* STR_POOLFRM_ARY[] = +{ + STR_POOLFRM_FRAME, + STR_POOLFRM_GRAPHIC, + STR_POOLFRM_OLE, + STR_POOLFRM_FORMEL, + STR_POOLFRM_MARGINAL, + STR_POOLFRM_WATERSIGN, + STR_POOLFRM_LABEL +}; + +static const char* STR_POOLPAGE_ARY[] = +{ + // Page styles + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_JAKET, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE +}; + +static const char* STR_POOLNUMRULE_NUM_ARY[] = +{ + // Numbering styles + STR_POOLNUMRULE_NUM1, + STR_POOLNUMRULE_NUM2, + STR_POOLNUMRULE_NUM3, + STR_POOLNUMRULE_NUM4, + STR_POOLNUMRULE_NUM5, + STR_POOLNUMRULE_BUL1, + STR_POOLNUMRULE_BUL2, + STR_POOLNUMRULE_BUL3, + STR_POOLNUMRULE_BUL4, + STR_POOLNUMRULE_BUL5 +}; + +// XXX MUST match the entries of TableStyleProgNameTable in +// sw/source/core/doc/SwStyleNameMapper.cxx and MUST match the order of +// RES_POOL_TABLESTYLE_TYPE in sw/inc/poolfmt.hxx +static const char* STR_TABSTYLE_ARY[] = +{ + // XXX MUST be in order, Writer first, then Svx old, then Svx new + // 1 Writer resource string + STR_TABSTYLE_DEFAULT, + // 16 old styles Svx resource strings + RID_SVXSTR_TBLAFMT_3D, + RID_SVXSTR_TBLAFMT_BLACK1, + RID_SVXSTR_TBLAFMT_BLACK2, + RID_SVXSTR_TBLAFMT_BLUE, + RID_SVXSTR_TBLAFMT_BROWN, + RID_SVXSTR_TBLAFMT_CURRENCY, + RID_SVXSTR_TBLAFMT_CURRENCY_3D, + RID_SVXSTR_TBLAFMT_CURRENCY_GRAY, + RID_SVXSTR_TBLAFMT_CURRENCY_LAVENDER, + RID_SVXSTR_TBLAFMT_CURRENCY_TURQUOISE, + RID_SVXSTR_TBLAFMT_GRAY, + RID_SVXSTR_TBLAFMT_GREEN, + RID_SVXSTR_TBLAFMT_LAVENDER, + RID_SVXSTR_TBLAFMT_RED, + RID_SVXSTR_TBLAFMT_TURQUOISE, + RID_SVXSTR_TBLAFMT_YELLOW, + // 10 new styles since LibreOffice 6.0 Svx resource strings + RID_SVXSTR_TBLAFMT_LO6_ACADEMIC, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_BLUE, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_GREEN, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_RED, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_YELLOW, + RID_SVXSTR_TBLAFMT_LO6_ELEGANT, + RID_SVXSTR_TBLAFMT_LO6_FINANCIAL, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_COLUMNS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_ROWS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_LIST_SHADED +}; + +namespace sw +{ + +DocumentStylePoolManager::DocumentStylePoolManager( SwDoc& i_rSwdoc ) : m_rDoc( i_rSwdoc ) +{ +} + +SwTextFormatColl* DocumentStylePoolManager::GetTextCollFromPool( sal_uInt16 nId, bool bRegardLanguage ) +{ + OSL_ENSURE( + (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) || + (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) || + (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) || + (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) || + (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) || + (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END), + "Wrong AutoFormat Id" ); + + SwTextFormatColl* pNewColl; + sal_uInt16 nOutLvlBits = 0; + for (size_t n = 0, nSize = m_rDoc.GetTextFormatColls()->size(); n < nSize; ++n) + { + pNewColl = (*m_rDoc.GetTextFormatColls())[ n ]; + if( nId == pNewColl->GetPoolFormatId() ) + { + return pNewColl; + } + + if( pNewColl->IsAssignedToListLevelOfOutlineStyle()) + nOutLvlBits |= ( 1 << pNewColl->GetAssignedOutlineStyleLevel() ); + } + + // Didn't find it until here -> create anew + const char* pResId = nullptr; + if (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_TEXT_ARY) == RES_POOLCOLL_TEXT_END - RES_POOLCOLL_TEXT_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_TEXT_ARY[nId - RES_POOLCOLL_TEXT_BEGIN]; + } + else if (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_LISTS_ARY) == RES_POOLCOLL_LISTS_END - RES_POOLCOLL_LISTS_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_LISTS_ARY[nId - RES_POOLCOLL_LISTS_BEGIN]; + } + else if (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_EXTRA_ARY) == RES_POOLCOLL_EXTRA_END - RES_POOLCOLL_EXTRA_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_EXTRA_ARY[nId - RES_POOLCOLL_EXTRA_BEGIN]; + } + else if (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_REGISTER_ARY) == RES_POOLCOLL_REGISTER_END - RES_POOLCOLL_REGISTER_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_REGISTER_ARY[nId - RES_POOLCOLL_REGISTER_BEGIN]; + } + else if (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_DOC_ARY) == RES_POOLCOLL_DOC_END - RES_POOLCOLL_DOC_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_DOC_ARY[nId - RES_POOLCOLL_DOC_BEGIN]; + } + else if (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END) + { + static_assert(SAL_N_ELEMENTS(STR_POOLCOLL_HTML_ARY) == RES_POOLCOLL_HTML_END - RES_POOLCOLL_HTML_BEGIN, "### unexpected size!"); + pResId = STR_POOLCOLL_HTML_ARY[nId - RES_POOLCOLL_HTML_BEGIN]; + } + + OSL_ENSURE(pResId, "Invalid Pool ID"); + if (!pResId) + return GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + OUString aNm(SwResId(pResId)); + + // A Set for all to-be-set Attributes + SwAttrSet aSet( m_rDoc.GetAttrPool(), aTextFormatCollSetRange ); + sal_uInt16 nParent = GetPoolParent( nId ); + + { + +//FEATURE::CONDCOLL + if(::IsConditionalByPoolId( nId )) + pNewColl = new SwConditionTextFormatColl( m_rDoc.GetAttrPool(), aNm, !nParent + ? m_rDoc.GetDfltTextFormatColl() + : GetTextCollFromPool( nParent )); + else +//FEATURE::CONDCOLL + pNewColl = new SwTextFormatColl( m_rDoc.GetAttrPool(), aNm, !nParent + ? m_rDoc.GetDfltTextFormatColl() + : GetTextCollFromPool( nParent )); + pNewColl->SetPoolFormatId( nId ); + m_rDoc.GetTextFormatColls()->push_back( pNewColl ); + } + + bool bNoDefault = m_rDoc.GetDocumentSettingManager().get( DocumentSettingId::STYLES_NODEFAULT ); + if ( !bNoDefault ) + { + switch( nId ) + { + // General content forms + case RES_POOLCOLL_STANDARD: + /* koreans do not like SvxScriptItem(TRUE) */ + if (bRegardLanguage) + { + LanguageType nAppLanguage = GetAppLanguage(); + if (GetDefaultFrameDirection(nAppLanguage) == + SvxFrameDirection::Horizontal_RL_TB) + { + SvxAdjustItem aAdjust(SvxAdjust::Right, RES_PARATR_ADJUST ); + aSet.Put(aAdjust); + } + if (nAppLanguage == LANGUAGE_KOREAN) + { + SvxScriptSpaceItem aScriptSpace(false, RES_PARATR_SCRIPTSPACE); + aSet.Put(aScriptSpace); + } + } + break; + + case RES_POOLCOLL_TEXT: // Text body + { + SvxLineSpacingItem aLSpc( LINE_SPACE_DEFAULT_HEIGHT, RES_PARATR_LINESPACING ); + SvxULSpaceItem aUL( 0, PT_7, RES_UL_SPACE ); + aLSpc.SetPropLineSpace( 115 ); + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) aUL.SetLower( HTML_PARSPACE ); + aSet.Put( aUL ); + aSet.Put( aLSpc ); + } + break; + case RES_POOLCOLL_TEXT_IDENT: // Text body indentation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( GetMetricVal( CM_05 )); + aSet.Put( aLR ); + } + break; + case RES_POOLCOLL_TEXT_NEGIDENT: // Text body neg. indentation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( -static_cast<short>(GetMetricVal( CM_05 ))); + aLR.SetTextLeft( GetMetricVal( CM_1 )); + SvxTabStopItem aTStops(RES_PARATR_TABSTOP); + aTStops.Insert( SvxTabStop( 0 )); + + aSet.Put( aLR ); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_TEXT_MOVE: // Text body move + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextLeft( GetMetricVal( CM_05 )); + aSet.Put( aLR ); + } + break; + + case RES_POOLCOLL_CONFRONTATION: // Text body confrontation + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( - short( GetMetricVal( CM_1 ) * 4 + + GetMetricVal( CM_05)) ); + aLR.SetTextLeft( GetMetricVal( CM_1 ) * 5 ); + SvxTabStopItem aTStops( RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( 0 )); + + aSet.Put( aLR ); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_MARGINAL: // Text body marginal + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextLeft( GetMetricVal( CM_1 ) * 4 ); + aSet.Put( aLR ); + } + break; + + case RES_POOLCOLL_HEADLINE_BASE: // Base headline + { + static const sal_uInt16 aFontWhich[] = + { RES_CHRATR_FONT, + RES_CHRATR_CJK_FONT, + RES_CHRATR_CTL_FONT + }; + static const sal_uInt16 aLangTypes[] = + { + RES_CHRATR_LANGUAGE, + RES_CHRATR_CJK_LANGUAGE, + RES_CHRATR_CTL_LANGUAGE + }; + static const LanguageType aLangs[] = + { + LANGUAGE_ENGLISH_US, + LANGUAGE_ENGLISH_US, + LANGUAGE_ARABIC_SAUDI_ARABIA + }; + static const DefaultFontType nFontTypes[] = + { + DefaultFontType::LATIN_HEADING, + DefaultFontType::CJK_HEADING, + DefaultFontType::CTL_HEADING + }; + + for( int i = 0; i < 3; ++i ) + { + LanguageType nLng = static_cast<const SvxLanguageItem&>(m_rDoc.GetDefault( aLangTypes[i] )).GetLanguage(); + if( LANGUAGE_DONTKNOW == nLng ) + nLng = aLangs[i]; + + vcl::Font aFnt( OutputDevice::GetDefaultFont( nFontTypes[i], + nLng, GetDefaultFontFlags::OnlyOne ) ); + + aSet.Put( SvxFontItem( aFnt.GetFamilyType(), aFnt.GetFamilyName(), + OUString(), aFnt.GetPitch(), + aFnt.GetCharSet(), aFontWhich[i] )); + } + + SvxFontHeightItem aFntSize( PT_14, 100, RES_CHRATR_FONTSIZE ); + SvxULSpaceItem aUL( PT_12, PT_6, RES_UL_SPACE ); + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + aUL.SetLower( HTML_PARSPACE ); + aSet.Put( SvxFormatKeepItem( true, RES_KEEP )); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + + aSet.Put( aUL ); + SetAllScriptItem( aSet, aFntSize ); + } + break; + + case RES_POOLCOLL_NUMBER_BULLET_BASE: // Base Numbering + break; + + case RES_POOLCOLL_GREETING: // Greeting + case RES_POOLCOLL_REGISTER_BASE: // Base indexes + case RES_POOLCOLL_SIGNATURE: // Signatures + case RES_POOLCOLL_TABLE: // Tabele content + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + if (nId == RES_POOLCOLL_TABLE) + { + aSet.Put( SvxWidowsItem( 0, RES_PARATR_WIDOWS ) ); + aSet.Put( SvxOrphansItem( 0, RES_PARATR_ORPHANS ) ); + } + } + break; + + case RES_POOLCOLL_HEADLINE1: // Heading 1 + { + SvxULSpaceItem aUL( PT_12, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 0, false ); + } + break; + case RES_POOLCOLL_HEADLINE2: // Heading 2 + { + SvxULSpaceItem aUL( PT_10, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 1, false ); + } + break; + case RES_POOLCOLL_HEADLINE3: // Heading 3 + { + SvxULSpaceItem aUL( PT_7, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 2, false ); + } + break; + case RES_POOLCOLL_HEADLINE4: // Heading 4 + { + SvxULSpaceItem aUL( PT_6, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 3, true ); + } + break; + case RES_POOLCOLL_HEADLINE5: // Heading 5 + { + SvxULSpaceItem aUL( PT_6, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 4, false ); + } + break; + case RES_POOLCOLL_HEADLINE6: // Heading 6 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 5, true ); + } + break; + case RES_POOLCOLL_HEADLINE7: // Heading 7 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 6, false ); + } + break; + case RES_POOLCOLL_HEADLINE8: // Heading 8 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 7, true ); + } + break; + case RES_POOLCOLL_HEADLINE9: // Heading 9 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 8, false ); + } + break; + case RES_POOLCOLL_HEADLINE10: // Heading 10 + { + SvxULSpaceItem aUL( PT_3, PT_3, RES_UL_SPACE ); + aSet.Put( aUL ); + lcl_SetHeadline( &m_rDoc, pNewColl, aSet, nOutLvlBits, 9, false ); + } + break; + + // Special sections: + // Header + case RES_POOLCOLL_HEADERFOOTER: + case RES_POOLCOLL_HEADER: + case RES_POOLCOLL_HEADERL: + case RES_POOLCOLL_HEADERR: + // Footer + case RES_POOLCOLL_FOOTER: + case RES_POOLCOLL_FOOTERL: + case RES_POOLCOLL_FOOTERR: + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + + long nRightMargin = lcl_GetRightMargin( m_rDoc ); + + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( nRightMargin / 2, SvxTabAdjust::Center ) ); + aTStops.Insert( SvxTabStop( nRightMargin, SvxTabAdjust::Right ) ); + + aSet.Put( aTStops ); + + if ( (nId==RES_POOLCOLL_HEADERR) || (nId==RES_POOLCOLL_FOOTERR) ) { + SvxAdjustItem aAdjust(SvxAdjust::Right, RES_PARATR_ADJUST ); + aSet.Put(aAdjust); + } + } + break; + + case RES_POOLCOLL_TABLE_HDLN: + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_FOOTNOTE: // paragraph style Footnote + case RES_POOLCOLL_ENDNOTE: // paragraph style Endnote + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetTextFirstLineOffset( -static_cast<short>( GetMetricVal( CM_05 ) + GetMetricVal( CM_01 ) ) ); + aLR.SetTextLeft( GetMetricVal( CM_05 ) + GetMetricVal( CM_01 ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_10, 100, RES_CHRATR_FONTSIZE ) ); + aSet.Put( aLR ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_LABEL: // basic caption + { + SvxULSpaceItem aUL( RES_UL_SPACE ); + aUL.SetUpper( PT_6 ); + aUL.SetLower( PT_6 ); + aSet.Put( aUL ); + SetAllScriptItem( aSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_10, 100, RES_CHRATR_FONTSIZE ) ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_FRAME: // Frame content + case RES_POOLCOLL_LABEL_ABB: // caption image + case RES_POOLCOLL_LABEL_TABLE: // caption table + case RES_POOLCOLL_LABEL_FRAME: // caption frame + case RES_POOLCOLL_LABEL_DRAWING: // caption drawing + case RES_POOLCOLL_LABEL_FIGURE: + break; + + case RES_POOLCOLL_JAKETADRESS: // envelope address + { + SvxULSpaceItem aUL( RES_UL_SPACE ); + aUL.SetLower( PT_3 ); + aSet.Put( aUL ); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_SENDADRESS: // Sender address + { + if( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + SetAllScriptItem( aSet, SvxPostureItem(ITALIC_NORMAL, RES_CHRATR_POSTURE) ); + else + { + SvxULSpaceItem aUL( RES_UL_SPACE ); aUL.SetLower( PT_3 ); + aSet.Put( aUL ); + } + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + // User defined indexes: + case RES_POOLCOLL_TOX_USERH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_USER1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + case RES_POOLCOLL_TOX_USER2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, true ); + break; + case RES_POOLCOLL_TOX_USER3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, true ); + break; + case RES_POOLCOLL_TOX_USER4: // 4th level + lcl_SetRegister( &m_rDoc, aSet, 3, false, true ); + break; + case RES_POOLCOLL_TOX_USER5: // 5th level + lcl_SetRegister( &m_rDoc, aSet, 4, false, true ); + break; + case RES_POOLCOLL_TOX_USER6: // 6th level + lcl_SetRegister( &m_rDoc, aSet, 5, false, true ); + break; + case RES_POOLCOLL_TOX_USER7: // 7th level + lcl_SetRegister( &m_rDoc, aSet, 6, false, true ); + break; + case RES_POOLCOLL_TOX_USER8: // 8th level + lcl_SetRegister( &m_rDoc, aSet, 7, false, true ); + break; + case RES_POOLCOLL_TOX_USER9: // 9th level + lcl_SetRegister( &m_rDoc, aSet, 8, false, true ); + break; + case RES_POOLCOLL_TOX_USER10: // 10th level + lcl_SetRegister( &m_rDoc, aSet, 9, false, true ); + break; + + // Index + case RES_POOLCOLL_TOX_IDXH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_IDX1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, false ); + break; + case RES_POOLCOLL_TOX_IDX2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, false ); + break; + case RES_POOLCOLL_TOX_IDX3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, false ); + break; + case RES_POOLCOLL_TOX_IDXBREAK: // Separator + lcl_SetRegister( &m_rDoc, aSet, 0, false, false ); + break; + + // Table of Content + case RES_POOLCOLL_TOX_CNTNTH: // Header + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_CNTNT1: // 1st level + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT2: // 2nd level + lcl_SetRegister( &m_rDoc, aSet, 1, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT3: // 3rd level + lcl_SetRegister( &m_rDoc, aSet, 2, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT4: // 4th level + lcl_SetRegister( &m_rDoc, aSet, 3, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT5: // 5th level + lcl_SetRegister( &m_rDoc, aSet, 4, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT6: // 6th level + lcl_SetRegister( &m_rDoc, aSet, 5, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT7: // 7th level + lcl_SetRegister( &m_rDoc, aSet, 6, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT8: // 8th level + lcl_SetRegister( &m_rDoc, aSet, 7, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT9: // 9th level + lcl_SetRegister( &m_rDoc, aSet, 8, false, true ); + break; + case RES_POOLCOLL_TOX_CNTNT10: // 10th level + lcl_SetRegister( &m_rDoc, aSet, 9, false, true ); + break; + + case RES_POOLCOLL_TOX_ILLUSH: + case RES_POOLCOLL_TOX_OBJECTH: + case RES_POOLCOLL_TOX_TABLESH: + case RES_POOLCOLL_TOX_AUTHORITIESH: + lcl_SetRegister( &m_rDoc, aSet, 0, true, false ); + { + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + case RES_POOLCOLL_TOX_ILLUS1: + case RES_POOLCOLL_TOX_OBJECT1: + case RES_POOLCOLL_TOX_TABLES1: + case RES_POOLCOLL_TOX_AUTHORITIES1: + lcl_SetRegister( &m_rDoc, aSet, 0, false, true ); + break; + + case RES_POOLCOLL_NUM_LEVEL1S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL1E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL1, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 0 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM1, + 0, SwNumRule::GetNumIndent( 0 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL2E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL2, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 1 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM2, + 0, SwNumRule::GetNumIndent( 1 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL3E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL3, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 2 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM3, + 0, SwNumRule::GetNumIndent( 2 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL4E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL4, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 3 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM4, + 0, SwNumRule::GetNumIndent( 3 ), 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_NUM_LEVEL5E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_LEVEL5, + lNumberFirstLineOffset, SwNumRule::GetNumIndent( 4 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_NUM_NONUM5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_NUM_NONUM5, + 0, SwNumRule::GetNumIndent( 4 ), 0, PT_6 ); + break; + + case RES_POOLCOLL_BULLET_LEVEL1S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL1E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL1, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 0 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM1: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM1, + 0, SwNumRule::GetBullIndent( 0 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL2E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL2, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 1 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM2: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM2, + 0, SwNumRule::GetBullIndent( 1 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL3E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL3, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 2 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM3: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM3, + 0, SwNumRule::GetBullIndent( 2 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL4E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL4, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 3 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM4: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM4, + 0, SwNumRule::GetBullIndent( 3 ), 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5S: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + PT_12, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + 0, PT_6 ); + break; + case RES_POOLCOLL_BULLET_LEVEL5E: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_LEVEL5, + lBulletFirstLineOffset, SwNumRule::GetBullIndent( 4 ), + 0, PT_12 ); + break; + case RES_POOLCOLL_BULLET_NONUM5: + lcl_SetNumBul( &m_rDoc, pNewColl, aSet, RES_POOLCOLL_BULLET_NONUM5, + 0, SwNumRule::GetBullIndent( 4 ), 0, PT_6 ); + break; + + case RES_POOLCOLL_DOC_TITLE: // Document Title + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_28, 100, RES_CHRATR_FONTSIZE ) ); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_DOC_SUBTITLE: // Document subtitle + { + SvxULSpaceItem aUL( PT_3, PT_6, RES_UL_SPACE ); + aSet.Put( aUL ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_18, 100, RES_CHRATR_FONTSIZE )); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST )); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_DOC_APPENDIX: // Document Appendix tdf#114090 + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( aSet, SvxFontHeightItem( PT_16, 100, RES_CHRATR_FONTSIZE ) ); + + aSet.Put( SvxAdjustItem( SvxAdjust::Center, RES_PARATR_ADJUST ) ); + + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + break; + + case RES_POOLCOLL_HTML_BLOCKQUOTE: + { + SvxLRSpaceItem aLR( RES_LR_SPACE ); + aLR.SetLeft( GetMetricVal( CM_1 )); + aLR.SetRight( GetMetricVal( CM_1 )); + aSet.Put( aLR ); + std::unique_ptr<SvxULSpaceItem> aUL(pNewColl->GetULSpace().Clone()); + aUL->SetLower( HTML_PARSPACE ); + aSet.Put(std::move(aUL)); + } + break; + + case RES_POOLCOLL_HTML_PRE: + { + ::lcl_SetDfltFont( DefaultFontType::FIXED, aSet ); + + // WORKAROUND: Set PRE to 10pt + SetAllScriptItem( aSet, SvxFontHeightItem(PT_10, 100, RES_CHRATR_FONTSIZE) ); + + // The lower paragraph distance is set explicitly (makes + // assigning hard attributes easier) + std::unique_ptr<SvxULSpaceItem> aULSpaceItem(pNewColl->GetULSpace().Clone()); + aULSpaceItem->SetLower( 0 ); + aSet.Put(std::move(aULSpaceItem)); + } + break; + + case RES_POOLCOLL_HTML_HR: + { + SvxBoxItem aBox( RES_BOX ); + Color aColor( COL_GRAY ); + SvxBorderLine aNew(&aColor, 3, SvxBorderLineStyle::DOUBLE); + aBox.SetLine( &aNew, SvxBoxItemLine::BOTTOM ); + + aSet.Put( aBox ); + aSet.Put( SwParaConnectBorderItem( false ) ); + SetAllScriptItem( aSet, SvxFontHeightItem(120, 100, RES_CHRATR_FONTSIZE) ); + + std::unique_ptr<SvxULSpaceItem> aUL; + { + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_TEXT )); + aUL.reset(pNewColl->GetULSpace().Clone()); + } + aUL->SetLower( HTML_PARSPACE ); + aSet.Put(std::move(aUL)); + SwFormatLineNumber aLN; + aLN.SetCountLines( false ); + aSet.Put( aLN ); + } + break; + + case RES_POOLCOLL_HTML_DD: + { + std::unique_ptr<SvxLRSpaceItem> aLR(pNewColl->GetLRSpace().Clone()); + // We indent by 1 cm. The IDs are always 2 away from each other! + aLR->SetLeft( GetMetricVal( CM_1 )); + aSet.Put(std::move(aLR)); + } + break; + case RES_POOLCOLL_HTML_DT: + { + std::unique_ptr<SvxLRSpaceItem> aLR; + { + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_HTML_DD )); + aLR.reset(pNewColl->GetLRSpace().Clone()); + } + // We indent by 0 cm. The IDs are always 2 away from each other! + aLR->SetLeft( 0 ); + aSet.Put( std::move(aLR) ); + } + break; + } + } + + if( aSet.Count() ) + pNewColl->SetFormatAttr( aSet ); + return pNewColl; +} + +/// Return the AutomaticFormat with the supplied Id. If it doesn't +/// exist, create it. +SwFormat* DocumentStylePoolManager::GetFormatFromPool( sal_uInt16 nId ) +{ + SwFormat *pNewFormat = nullptr; + SwFormat *pDeriveFormat = nullptr; + + SwFormatsBase* pArray[ 2 ]; + sal_uInt16 nArrCnt = 1; + const char* pRCId = nullptr; + sal_uInt16 const * pWhichRange = nullptr; + + switch( nId & (COLL_GET_RANGE_BITS + POOLGRP_NOCOLLID) ) + { + case POOLGRP_CHARFMT: + { + pArray[0] = m_rDoc.GetCharFormats(); + pDeriveFormat = m_rDoc.GetDfltCharFormat(); + pWhichRange = aCharFormatSetRange; + + if (nId >= RES_POOLCHR_HTML_BEGIN && nId < RES_POOLCHR_HTML_END) + pRCId = STR_POOLCHR_HTML_ARY[nId - RES_POOLCHR_HTML_BEGIN]; + else if (nId >= RES_POOLCHR_NORMAL_BEGIN && nId < RES_POOLCHR_NORMAL_END) + pRCId = STR_POOLCHR_ARY[nId - RES_POOLCHR_BEGIN]; + else + { + // Fault: unknown Format, but a CharFormat -> return the first one + OSL_ENSURE( false, "invalid Id" ); + pRCId = STR_POOLCHR_ARY[0]; + } + } + break; + case POOLGRP_FRAMEFMT: + { + pArray[0] = m_rDoc.GetFrameFormats(); + pArray[1] = m_rDoc.GetSpzFrameFormats(); + pDeriveFormat = m_rDoc.GetDfltFrameFormat(); + nArrCnt = 2; + pWhichRange = aFrameFormatSetRange; + + // Fault: unknown Format, but a FrameFormat + // -> return the first one + if( RES_POOLFRM_BEGIN > nId || nId >= RES_POOLFRM_END ) + { + OSL_ENSURE( false, "invalid Id" ); + nId = RES_POOLFRM_BEGIN; + } + + pRCId = STR_POOLFRM_ARY[nId - RES_POOLFRM_BEGIN]; + } + break; + + default: + // Fault, unknown Format + OSL_ENSURE( nId, "invalid Id" ); + return nullptr; + } + OSL_ENSURE(pRCId, "invalid Id"); + + while( nArrCnt-- ) + for( size_t n = 0; n < (*pArray[nArrCnt]).GetFormatCount(); ++n ) + { + pNewFormat = (*pArray[ nArrCnt ] ).GetFormat( n ); + if( nId == pNewFormat->GetPoolFormatId() ) + { + return pNewFormat; + } + } + + OUString aNm(SwResId(pRCId)); + SwAttrSet aSet( m_rDoc.GetAttrPool(), pWhichRange ); + + { + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + switch (nId & (COLL_GET_RANGE_BITS + POOLGRP_NOCOLLID) ) + { + case POOLGRP_CHARFMT: + pNewFormat = m_rDoc.MakeCharFormat_(aNm, pDeriveFormat, false, true); + break; + case POOLGRP_FRAMEFMT: + pNewFormat = m_rDoc.MakeFrameFormat_(aNm, pDeriveFormat, false, true); + break; + default: + break; + } + } + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); + pNewFormat->SetPoolFormatId( nId ); + pNewFormat->SetAuto(false); // no AutoFormat + } + + switch( nId ) + { + case RES_POOLCHR_FOOTNOTE: // Footnote + case RES_POOLCHR_PAGENO: // Page/Field + case RES_POOLCHR_LABEL: // Label + case RES_POOLCHR_DROPCAPS: // Dropcaps + case RES_POOLCHR_NUM_LEVEL: // Numbering level + case RES_POOLCHR_TOXJUMP: // Table of contents jump + case RES_POOLCHR_ENDNOTE: // Endnote + case RES_POOLCHR_LINENUM: // Line numbering + break; + + case RES_POOLCHR_ENDNOTE_ANCHOR: // Endnote anchor + case RES_POOLCHR_FOOTNOTE_ANCHOR: // Footnote anchor + { + aSet.Put( SvxEscapementItem( DFLT_ESC_AUTO_SUPER, DFLT_ESC_PROP, RES_CHRATR_ESCAPEMENT ) ); + } + break; + + case RES_POOLCHR_BULLET_LEVEL: // Bullet character + { + const vcl::Font& rBulletFont = numfunc::GetDefBulletFont(); + SetAllScriptItem( aSet, SvxFontItem( rBulletFont.GetFamilyType(), + rBulletFont.GetFamilyName(), rBulletFont.GetStyleName(), + rBulletFont.GetPitch(), rBulletFont.GetCharSet(), RES_CHRATR_FONT )); + } + break; + + case RES_POOLCHR_INET_NORMAL: + { + aSet.Put( SvxColorItem( COL_BLUE, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE ) ); + // i40133: patch submitted by rail: set language to 'none' to prevent spell checking: + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CJK_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CTL_LANGUAGE ) ); + } + break; + case RES_POOLCHR_INET_VISIT: + { + aSet.Put( SvxColorItem( COL_RED, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CJK_LANGUAGE ) ); + aSet.Put( SvxLanguageItem( LANGUAGE_NONE, RES_CHRATR_CTL_LANGUAGE ) ); + } + break; + case RES_POOLCHR_JUMPEDIT: + { + aSet.Put( SvxColorItem( COL_CYAN, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_DOTTED, RES_CHRATR_UNDERLINE ) ); + aSet.Put( SvxCaseMapItem( SvxCaseMap::SmallCaps, RES_CHRATR_CASEMAP ) ); + } + break; + + case RES_POOLCHR_RUBYTEXT: + { + long nH = GetDfltAttr( RES_CHRATR_CJK_FONTSIZE )->GetHeight() / 2; + SetAllScriptItem( aSet, SvxFontHeightItem( nH, 100, RES_CHRATR_FONTSIZE)); + aSet.Put(SvxUnderlineItem( LINESTYLE_NONE, RES_CHRATR_UNDERLINE )); + aSet.Put(SvxEmphasisMarkItem( FontEmphasisMark::NONE, RES_CHRATR_EMPHASIS_MARK) ); + } + break; + + case RES_POOLCHR_HTML_EMPHASIS: + case RES_POOLCHR_HTML_CITIATION: + case RES_POOLCHR_HTML_VARIABLE: + { + SetAllScriptItem( aSet, SvxPostureItem( ITALIC_NORMAL, RES_CHRATR_POSTURE) ); + } + break; + + case RES_POOLCHR_IDX_MAIN_ENTRY: + case RES_POOLCHR_HTML_STRONG: + { + SetAllScriptItem( aSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT )); + } + break; + + case RES_POOLCHR_HTML_CODE: + case RES_POOLCHR_HTML_SAMPLE: + case RES_POOLCHR_HTML_KEYBOARD: + case RES_POOLCHR_HTML_TELETYPE: + { + ::lcl_SetDfltFont( DefaultFontType::FIXED, aSet ); + } + break; + case RES_POOLCHR_VERT_NUM: + aSet.Put( SvxCharRotateItem( 900, false, RES_CHRATR_ROTATE ) ); + break; + + case RES_POOLFRM_FRAME: + { + if ( m_rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ) + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::LINE_CENTER, text::RelOrientation::PRINT_AREA ) ); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + } + else + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_PARALLEL ) ); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::PRINT_AREA ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::PRINT_AREA ) ); + Color aCol( COL_BLACK ); + SvxBorderLine aLine( &aCol, DEF_LINE_WIDTH_0 ); + SvxBoxItem aBox( RES_BOX ); + aBox.SetLine( &aLine, SvxBoxItemLine::TOP ); + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + aBox.SetLine( &aLine, SvxBoxItemLine::LEFT ); + aBox.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + aBox.SetAllDistances( 85 ); + aSet.Put( aBox ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + aSet.Put( SvxULSpaceItem( 114, 114, RES_UL_SPACE ) ); + } + + // for styles of FlyFrames do not set the FillStyle to make it a derived attribute + aSet.ClearItem(XATTR_FILLSTYLE); + } + break; + case RES_POOLFRM_GRAPHIC: + case RES_POOLFRM_OLE: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_DYNAMIC )); + } + break; + case RES_POOLFRM_FORMEL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::CHAR_CENTER, text::RelOrientation::FRAME ) ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + } + break; + case RES_POOLFRM_MARGINAL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PARA )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::LEFT, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_PARALLEL )); + // Set the default width to 3.5 cm, use the minimum value for the height + aSet.Put( SwFormatFrameSize( SwFrameSize::Minimum, + GetMetricVal( CM_1 ) * 3 + GetMetricVal( CM_05 ), + MM50 )); + } + break; + case RES_POOLFRM_WATERSIGN: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AT_PAGE )); + aSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME )); + aSet.Put( SvxOpaqueItem( RES_OPAQUE, false )); + aSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH )); + } + break; + case RES_POOLFRM_LABEL: + { + aSet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR ) ); + aSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ) ); + aSet.Put( SvxLRSpaceItem( 114, 114, 0, 0, RES_LR_SPACE ) ); + + SvxProtectItem aProtect( RES_PROTECT ); + aProtect.SetSizeProtect( true ); + aProtect.SetPosProtect( true ); + aSet.Put( aProtect ); + + pNewFormat->SetAutoUpdateFormat(); + } + break; + } + if( aSet.Count() ) + { + pNewFormat->SetFormatAttr( aSet ); + } + return pNewFormat; +} + +SwFrameFormat* DocumentStylePoolManager::GetFrameFormatFromPool( sal_uInt16 nId ) +{ + return static_cast<SwFrameFormat*>(GetFormatFromPool( nId )); +} + +SwCharFormat* DocumentStylePoolManager::GetCharFormatFromPool( sal_uInt16 nId ) +{ + return static_cast<SwCharFormat*>(GetFormatFromPool( nId )); +} + +SwPageDesc* DocumentStylePoolManager::GetPageDescFromPool( sal_uInt16 nId, bool bRegardLanguage ) +{ + OSL_ENSURE( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END, + "Wrong AutoFormat Id" ); + + for( size_t n = 0; n < m_rDoc.GetPageDescCnt(); ++n ) + { + if ( nId == m_rDoc.GetPageDesc(n).GetPoolFormatId() ) + { + return &m_rDoc.GetPageDesc(n); + } + } + + if( RES_POOLPAGE_BEGIN > nId || nId >= RES_POOLPAGE_END ) + { + // unknown page pool ID + OSL_ENSURE( false, "<SwDoc::GetPageDescFromPool(..)> - unknown page pool ID" ); + nId = RES_POOLPAGE_BEGIN; + } + + SwPageDesc* pNewPgDsc = nullptr; + { + static_assert(SAL_N_ELEMENTS(STR_POOLPAGE_ARY) == RES_POOLPAGE_END - RES_POOLPAGE_BEGIN, "### unexpected size!"); + const OUString aNm(SwResId(STR_POOLPAGE_ARY[nId - RES_POOLPAGE_BEGIN])); + const bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + pNewPgDsc = m_rDoc.MakePageDesc(aNm, nullptr, bRegardLanguage); + } + + pNewPgDsc->SetPoolFormatId( nId ); + if ( !bIsModified ) + { + m_rDoc.getIDocumentState().ResetModified(); + } + } + + SvxLRSpaceItem aLR( RES_LR_SPACE ); + { + aLR.SetLeft( GetMetricVal( CM_1 ) * 2 ); + aLR.SetRight( aLR.GetLeft() ); + } + SvxULSpaceItem aUL( RES_UL_SPACE ); + { + aUL.SetUpper( static_cast<sal_uInt16>(aLR.GetLeft()) ); + aUL.SetLower( static_cast<sal_uInt16>(aLR.GetLeft()) ); + } + + SwAttrSet aSet( m_rDoc.GetAttrPool(), aPgFrameFormatSetRange ); + bool bSetLeft = true; + + switch( nId ) + { + case RES_POOLPAGE_STANDARD: // "Default" + { + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All | UseOnPage::FirstShare ); + } + break; + + case RES_POOLPAGE_FIRST: // "First Page" + case RES_POOLPAGE_REGISTER: // "Index" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + if( RES_POOLPAGE_FIRST == nId ) + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_STANDARD )); + } + break; + + case RES_POOLPAGE_LEFT: // "Left Page" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + bSetLeft = false; + pNewPgDsc->SetUseOn( UseOnPage::Left ); + // this relies on GetPageDescFromPool() not going into infinite recursion + // (by this point RES_POOLPAGE_LEFT will not reach this place again) + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_RIGHT )); + } + break; + case RES_POOLPAGE_RIGHT: // "Right Page" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + bSetLeft = false; + pNewPgDsc->SetUseOn( UseOnPage::Right ); + pNewPgDsc->SetFollow( GetPageDescFromPool( RES_POOLPAGE_LEFT )); + } + break; + + case RES_POOLPAGE_JAKET: // "Envelope" + { + Size aPSize( SvxPaperInfo::GetPaperSize( PAPER_ENV_C65 ) ); + LandscapeSwap( aPSize ); + aSet.Put( SwFormatFrameSize( SwFrameSize::Fixed, aPSize.Width(), aPSize.Height() )); + aLR.SetLeft( 0 ); aLR.SetRight( 0 ); + aUL.SetUpper( 0 ); aUL.SetLower( 0 ); + aSet.Put( aLR ); + aSet.Put( aUL ); + + pNewPgDsc->SetUseOn( UseOnPage::All ); + pNewPgDsc->SetLandscape( true ); + } + break; + + case RES_POOLPAGE_HTML: // "HTML" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aLR.SetRight( GetMetricVal( CM_1 )); + aUL.SetUpper( static_cast<sal_uInt16>(aLR.GetRight()) ); + aUL.SetLower( static_cast<sal_uInt16>(aLR.GetRight()) ); + aSet.Put( aLR ); + aSet.Put( aUL ); + + pNewPgDsc->SetUseOn( UseOnPage::All ); + } + break; + + case RES_POOLPAGE_FOOTNOTE: // "Footnote" + case RES_POOLPAGE_ENDNOTE: // "Endnote" + { + lcl_PutStdPageSizeIntoItemSet( &m_rDoc, aSet ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + SwPageFootnoteInfo aInf( pNewPgDsc->GetFootnoteInfo() ); + aInf.SetLineWidth( 0 ); + aInf.SetTopDist( 0 ); + aInf.SetBottomDist( 0 ); + pNewPgDsc->SetFootnoteInfo( aInf ); + } + break; + + case RES_POOLPAGE_LANDSCAPE: // "Landscape" + { + SwPageDesc* pStdPgDsc = GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + SwFormatFrameSize aFrameSz( pStdPgDsc->GetMaster().GetFrameSize() ); + if ( !pStdPgDsc->GetLandscape() ) + { + const SwTwips nTmp = aFrameSz.GetHeight(); + aFrameSz.SetHeight( aFrameSz.GetWidth() ); + aFrameSz.SetWidth( nTmp ); + } + aSet.Put( aFrameSz ); + aSet.Put( aLR ); + aSet.Put( aUL ); + pNewPgDsc->SetUseOn( UseOnPage::All ); + pNewPgDsc->SetLandscape( true ); + } + break; + + } + + if( aSet.Count() ) + { + if( bSetLeft ) + { + pNewPgDsc->GetLeft().SetFormatAttr( aSet ); + pNewPgDsc->GetFirstLeft().SetFormatAttr( aSet ); + } + pNewPgDsc->GetMaster().SetFormatAttr( aSet ); + pNewPgDsc->GetFirstMaster().SetFormatAttr( aSet ); + } + return pNewPgDsc; +} + +SwNumRule* DocumentStylePoolManager::GetNumRuleFromPool( sal_uInt16 nId ) +{ + OSL_ENSURE( RES_POOLNUMRULE_BEGIN <= nId && nId < RES_POOLNUMRULE_END, + "Wrong AutoFormat Id" ); + + SwNumRule* pNewRule; + + for (size_t n = 0; n < m_rDoc.GetNumRuleTable().size(); ++n ) + { + pNewRule = m_rDoc.GetNumRuleTable()[ n ]; + if (nId == pNewRule->GetPoolFormatId()) + { + return pNewRule; + } + } + + // error: unknown Pool style + if( RES_POOLNUMRULE_BEGIN > nId || nId >= RES_POOLNUMRULE_END ) + { + OSL_ENSURE( false, "invalid Id" ); + nId = RES_POOLNUMRULE_BEGIN; + } + + static_assert(SAL_N_ELEMENTS(STR_POOLNUMRULE_NUM_ARY) == RES_POOLNUMRULE_END - RES_POOLNUMRULE_BEGIN, "### unexpected size!"); + OUString aNm(SwResId(STR_POOLNUMRULE_NUM_ARY[nId - RES_POOLNUMRULE_BEGIN])); + + SwCharFormat *pNumCFormat = nullptr, *pBullCFormat = nullptr; + + const SvxNumberFormat::SvxNumPositionAndSpaceMode eNumberFormatPositionAndSpaceMode + = numfunc::GetDefaultPositionAndSpaceMode(); //#i89178# + { + bool bIsModified = m_rDoc.getIDocumentState().IsModified(); + + sal_uInt16 n = m_rDoc.MakeNumRule( aNm, nullptr, false, eNumberFormatPositionAndSpaceMode ); + + pNewRule = m_rDoc.GetNumRuleTable()[ n ]; + pNewRule->SetPoolFormatId( nId ); + pNewRule->SetAutoRule( false ); + + if( RES_POOLNUMRULE_NUM1 <= nId && nId <= RES_POOLNUMRULE_NUM5 ) + pNumCFormat = GetCharFormatFromPool( RES_POOLCHR_NUM_LEVEL ); + + if( ( RES_POOLNUMRULE_BUL1 <= nId && nId <= RES_POOLNUMRULE_BUL5 ) || + RES_POOLNUMRULE_NUM5 == nId ) + pBullCFormat = GetCharFormatFromPool( RES_POOLCHR_NUM_LEVEL ); + + if( !bIsModified ) + m_rDoc.getIDocumentState().ResetModified(); + } + + switch( nId ) + { + case RES_POOLNUMRULE_NUM1: + { + SwNumFormat aFormat; + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ARABIC); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0.7 cm intervals, with 1 cm = 567 + 397, 794, 1191, 1588, 1985, 2381, 2778, 3175, 3572, 3969 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr + 357 ); // 357 is indent of 0.63 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr + 357 ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + + case RES_POOLNUMRULE_NUM2: + { + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { + 397, 397, 397, 397, // 0.70 cm intervals + 397, 397, 397, 397, + 397, 397 + }; + + const sal_uInt16* pArr = aAbsSpace; + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + + sal_uInt16 nSpace = 357; // indent of 0.63 cm + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nSpace += pArr[ n ]; + aFormat.SetAbsLSpace( nSpace ); + aFormat.SetFirstLineOffset( - pArr[ n ] ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + nSpace += pArr[ n ]; + aFormat.SetListtabPos( nSpace ); + aFormat.SetIndentAt( nSpace ); + aFormat.SetFirstLineIndent( - pArr[ n ] ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM3: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + + long const nOffs = 397; // 0.70 cm + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - nOffs ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( (n+1) * nOffs + 357 ); // 357 is indent of 0.63 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = (n+1) * nOffs; + aFormat.SetListtabPos(nPos + 357); + aFormat.SetIndentAt(nPos + 357); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM4: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ROMAN_UPPER); + aFormat.SetCharFormat( pNumCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + aFormat.SetNumAdjust( SvxAdjust::Right ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 1.33 cm intervals + 754, 1508, 1191, 2262, 3016, 3771, 4525, 5279, 6033, 6787 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( 580 - (*pArr) ); // 1 cm space + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( 580 - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_NUM5: + { + // [ First, LSpace ] + static const sal_uInt16 aAbsSpace0to2[] = + { + 174, 754, // 0.33, 1.33, + 567, 1151, // 1.03, 2.03, + 397, 1548 // 2.03, 2.73 + }; + + const sal_uInt16* pArr0to2 = aAbsSpace0to2; + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetSuffix( "." ); + aFormat.SetNumAdjust( SvxAdjust::Right ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( -pArr0to2[0] ); // == 0.33 cm + aFormat.SetAbsLSpace( pArr0to2[1] ); // == 1.33 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[0] ); + aFormat.SetListtabPos( pArr0to2[1] ); + aFormat.SetIndentAt( pArr0to2[1] ); + } + + aFormat.SetCharFormat( pNumCFormat ); + pNewRule->Set( 0, aFormat ); + + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetStart( 1 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( -pArr0to2[2] ); // == 1.03 cm + aFormat.SetAbsLSpace( pArr0to2[3] ); // == 2.03 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[2] ); + aFormat.SetListtabPos( pArr0to2[3] ); + aFormat.SetIndentAt( pArr0to2[3] ); + } + + pNewRule->Set( 1, aFormat ); + + aFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); + aFormat.SetSuffix(OUString(u')')); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetStart( 3 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - pArr0to2[4] ); // == 2.03 cm + aFormat.SetAbsLSpace( pArr0to2[5] ); // == 2.73 cm + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( -pArr0to2[4] ); + aFormat.SetListtabPos( pArr0to2[5] ); + aFormat.SetIndentAt( pArr0to2[5] ); + } + + pNewRule->Set( 2, aFormat ); + + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( cBulletChar ); + sal_Int16 nOffs = GetMetricVal( CM_01 ) * 4, + nOffs2 = GetMetricVal( CM_1 ) * 2; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( - nOffs ); + } + + aFormat.SetSuffix( OUString() ); + for (sal_uInt16 n = 3; n < MAXLEVEL; ++n) + { + aFormat.SetStart( n+1 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( nOffs2 + ((n-3) * nOffs) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = nOffs2 + ((n-3) * static_cast<long>(nOffs)); + aFormat.SetListtabPos(nPos); + aFormat.SetIndentAt(nPos); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + + case RES_POOLNUMRULE_BUL1: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( cBulletChar ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL2: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + aFormat.SetBulletChar( 0x2013 ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,3 0,6 0,9 1,2 1,5 1,8 2,1 2,4 2,7 3,0 + 170, 340, 510, 680, 850, 1020, 1191, 1361, 1531, 1701 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL3: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + sal_uInt16 nOffs = GetMetricVal( CM_01 ) * 4; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - nOffs ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n) + { + aFormat.SetBulletChar( (n & 1) ? 0x25a1 : 0x2611 ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( ((n & 1) +1) * nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + long nPos = ((n & 1) +1) * static_cast<long>(nOffs); + aFormat.SetListtabPos(nPos); + aFormat.SetIndentAt(nPos); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL4: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::SPACE ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + switch( n ) + { + case 0: aFormat.SetBulletChar( 0x27a2 ); break; + case 1: aFormat.SetBulletChar( 0xE006 ); break; + default: aFormat.SetBulletChar( 0xE004 ); break; + } + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + case RES_POOLNUMRULE_BUL5: + { + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aFormat.SetCharFormat( pBullCFormat ); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetBulletChar( 0x2717 ); + aFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 0,4 0,8 1,2 1,6 2,0 2,4 2,8 3,2 3,6 4,0 + 227, 454, 680, 907, 1134, 1361, 1587, 1814, 2041, 2268 + }; + const sal_uInt16* pArr = aAbsSpace; + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - (*pArr) ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFormat.SetFirstLineIndent( - (*pArr) ); + } + + for (sal_uInt16 n = 0; n < MAXLEVEL; ++n, ++pArr) + { + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace( *pArr ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetListtabPos( *pArr ); + aFormat.SetIndentAt( *pArr ); + } + + pNewRule->Set( n, aFormat ); + } + } + break; + } + + return pNewRule; +} + +/// Check if this AutoCollection is already/still in use in this Document +bool DocumentStylePoolManager::IsPoolTextCollUsed( sal_uInt16 nId ) const +{ + OSL_ENSURE( + (RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END) || + (RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END) || + (RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END) || + (RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END) || + (RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END) || + (RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END), + "Wrong AutoFormat Id" ); + + SwTextFormatColl* pNewColl = nullptr; + bool bFnd = false; + for( SwTextFormatColls::size_type n = 0; !bFnd && n < m_rDoc.GetTextFormatColls()->size(); ++n ) + { + pNewColl = (*m_rDoc.GetTextFormatColls())[ n ]; + if( nId == pNewColl->GetPoolFormatId() ) + bFnd = true; + } + + if( !bFnd || !pNewColl->HasWriterListeners() ) + return false; + + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + return !pNewColl->GetInfo( aGetHt ); +} + +/// Check if this AutoCollection is already/still in use +bool DocumentStylePoolManager::IsPoolFormatUsed( sal_uInt16 nId ) const +{ + const SwFormat *pNewFormat = nullptr; + const SwFormatsBase* pArray[ 2 ]; + sal_uInt16 nArrCnt = 1; + bool bFnd = true; + + if (RES_POOLCHR_BEGIN <= nId && nId < RES_POOLCHR_END) + { + pArray[0] = m_rDoc.GetCharFormats(); + } + else if (RES_POOLFRM_BEGIN <= nId && nId < RES_POOLFRM_END) + { + pArray[0] = m_rDoc.GetFrameFormats(); + pArray[1] = m_rDoc.GetSpzFrameFormats(); + nArrCnt = 2; + } + else + { + SAL_WARN("sw.core", "Invalid Pool Id: " << nId << " should be within " + "[" << int(RES_POOLCHR_BEGIN) << "," << int(RES_POOLCHR_END) << ") or " + "[" << int(RES_POOLFRM_BEGIN) << "," << int(RES_POOLFRM_END) << ")"); + bFnd = false; + } + + if( bFnd ) + { + bFnd = false; + while( nArrCnt-- && !bFnd ) + for( size_t n = 0; !bFnd && n < (*pArray[nArrCnt]).GetFormatCount(); ++n ) + { + pNewFormat = (*pArray[ nArrCnt ] ).GetFormat( n ); + if( nId == pNewFormat->GetPoolFormatId() ) + bFnd = true; + } + } + + // Not found or no dependencies? + if( bFnd && pNewFormat->HasWriterListeners() ) + { + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + bFnd = !pNewFormat->GetInfo( aGetHt ); + } + else + bFnd = false; + + return bFnd; +} + +/// Check if this AutoCollection is already/still in use in this Document +bool DocumentStylePoolManager::IsPoolPageDescUsed( sal_uInt16 nId ) const +{ + OSL_ENSURE( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END, + "Wrong AutoFormat Id" ); + SwPageDesc *pNewPgDsc = nullptr; + bool bFnd = false; + for( size_t n = 0; !bFnd && n < m_rDoc.GetPageDescCnt(); ++n ) + { + pNewPgDsc = &m_rDoc.GetPageDesc(n); + if( nId == pNewPgDsc->GetPoolFormatId() ) + bFnd = true; + } + + // Not found or no dependencies? + if( !bFnd || !pNewPgDsc->HasWriterListeners() ) // ?????? + return false; + + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &m_rDoc.GetNodes() ); + return !pNewPgDsc->GetInfo( aGetHt ); +} + +DocumentStylePoolManager::~DocumentStylePoolManager() +{ +} + +} + +static std::vector<OUString> +lcl_NewUINameArray(const char** pIds, const size_t nLen, const size_t nSvxIds = 0) +{ + assert(nSvxIds <= nLen); + const size_t nWriterIds = nLen - nSvxIds; + std::vector<OUString> aNameArray; + aNameArray.reserve(nLen); + for (size_t i = 0; i < nWriterIds; ++i) + aNameArray.push_back(SwResId(pIds[i])); + for (size_t i = nWriterIds; i < nLen; ++i) + aNameArray.push_back(SvxResId(pIds[i])); + return aNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetTextUINameArray() +{ + static const std::vector<OUString> s_aTextUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_TEXT_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_TEXT_ARY))); + return s_aTextUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetListsUINameArray() +{ + static const std::vector<OUString> s_aListsUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_LISTS_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_LISTS_ARY))); + return s_aListsUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetExtraUINameArray() +{ + static const std::vector<OUString> s_aExtraUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_EXTRA_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_EXTRA_ARY))); + return s_aExtraUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetRegisterUINameArray() +{ + static const std::vector<OUString> s_aRegisterUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_REGISTER_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_REGISTER_ARY))); + return s_aRegisterUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetDocUINameArray() +{ + static const std::vector<OUString> s_aDocUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_DOC_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_DOC_ARY))); + return s_aDocUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLUINameArray() +{ + static const std::vector<OUString> s_aHTMLUINameArray( + lcl_NewUINameArray(STR_POOLCOLL_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_HTML_ARY))); + return s_aHTMLUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetFrameFormatUINameArray() +{ + static const std::vector<OUString> s_aFrameFormatUINameArray( + lcl_NewUINameArray(STR_POOLFRM_ARY, SAL_N_ELEMENTS(STR_POOLFRM_ARY))); + return s_aFrameFormatUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetChrFormatUINameArray() +{ + static const std::vector<OUString> s_aChrFormatUINameArray( + lcl_NewUINameArray(STR_POOLCHR_ARY, SAL_N_ELEMENTS(STR_POOLCHR_ARY))); + return s_aChrFormatUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLChrFormatUINameArray() +{ + static const std::vector<OUString> s_aHTMLChrFormatUINameArray( + lcl_NewUINameArray(STR_POOLCHR_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCHR_HTML_ARY))); + return s_aHTMLChrFormatUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetPageDescUINameArray() +{ + static const std::vector<OUString> s_aPageDescUINameArray( + lcl_NewUINameArray(STR_POOLPAGE_ARY, SAL_N_ELEMENTS(STR_POOLPAGE_ARY))); + return s_aPageDescUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetNumRuleUINameArray() +{ + static const std::vector<OUString> s_aNumRuleUINameArray( + lcl_NewUINameArray(STR_POOLNUMRULE_NUM_ARY, SAL_N_ELEMENTS(STR_POOLNUMRULE_NUM_ARY))); + return s_aNumRuleUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetTableStyleUINameArray() +{ + static const std::vector<OUString> s_aTableStyleUINameArray( + // 1 Writer resource string (XXX if this ever changes rather use offset math) + lcl_NewUINameArray(STR_TABSTYLE_ARY, SAL_N_ELEMENTS(STR_TABSTYLE_ARY), + static_cast<size_t>(SAL_N_ELEMENTS(STR_TABSTYLE_ARY) - 1))); + return s_aTableStyleUINameArray; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/DocumentTimerManager.cxx b/sw/source/core/doc/DocumentTimerManager.cxx new file mode 100644 index 000000000..842c1262d --- /dev/null +++ b/sw/source/core/doc/DocumentTimerManager.cxx @@ -0,0 +1,236 @@ +/* -*- 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 <DocumentTimerManager.hxx> + +#include <doc.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> +#include <viewsh.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> +#include <set> +#include <fldupde.hxx> +#include <sfx2/progress.hxx> +#include <viewopt.hxx> +#include <docsh.hxx> +#include <docfld.hxx> +#include <fldbas.hxx> +#include <vcl/scheduler.hxx> +#include <comphelper/lok.hxx> +#include <editsh.hxx> + +namespace sw +{ +DocumentTimerManager::DocumentTimerManager(SwDoc& i_rSwdoc) + : m_rDoc(i_rSwdoc) + , m_nIdleBlockCount(0) + , m_bStartOnUnblock(false) + , m_aDocIdle(i_rSwdoc) + , m_aFireIdleJobsTimer("sw::DocumentTimerManager m_aFireIdleJobsTimer") + , m_bWaitForLokInit(true) +{ + m_aDocIdle.SetPriority(TaskPriority::LOWEST); + m_aDocIdle.SetInvokeHandler(LINK(this, DocumentTimerManager, DoIdleJobs)); + m_aDocIdle.SetDebugName("sw::DocumentTimerManager m_aDocIdle"); + + m_aFireIdleJobsTimer.SetInvokeHandler(LINK(this, DocumentTimerManager, FireIdleJobsTimeout)); + m_aFireIdleJobsTimer.SetTimeout(1000); // Enough time for LOK to render the first tiles. +} + +void DocumentTimerManager::StartIdling() +{ + if (m_bWaitForLokInit && comphelper::LibreOfficeKit::isActive()) + { + // Start the idle jobs only after a certain delay. + m_bWaitForLokInit = false; + StopIdling(); + m_aFireIdleJobsTimer.Start(); + return; + } + + m_bWaitForLokInit = false; + m_bStartOnUnblock = true; + if (0 == m_nIdleBlockCount) + { + if (!m_aDocIdle.IsActive()) + m_aDocIdle.Start(); + else + Scheduler::Wakeup(); + } +} + +void DocumentTimerManager::StopIdling() +{ + m_bStartOnUnblock = false; + m_aDocIdle.Stop(); +} + +void DocumentTimerManager::BlockIdling() +{ + assert(SAL_MAX_UINT32 != m_nIdleBlockCount); + ++m_nIdleBlockCount; +} + +void DocumentTimerManager::UnblockIdling() +{ + assert(0 != m_nIdleBlockCount); + --m_nIdleBlockCount; + + if ((0 == m_nIdleBlockCount) && m_bStartOnUnblock) + { + if (!m_aDocIdle.IsActive()) + m_aDocIdle.Start(); + else + Scheduler::Wakeup(); + } +} + +IMPL_LINK(DocumentTimerManager, FireIdleJobsTimeout, Timer*, , void) +{ + // Now we can run the idle jobs, assuming we finished LOK initialization. + StartIdling(); +} + +DocumentTimerManager::IdleJob DocumentTimerManager::GetNextIdleJob() const +{ + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot && + !SfxProgress::GetActiveProgress( m_rDoc.GetDocShell() ) ) + { + SwViewShell* pShell(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()); + for(const SwViewShell& rSh : pShell->GetRingContainer()) + if( rSh.ActionPend() ) + return IdleJob::Busy; + + if( pTmpRoot->IsNeedGrammarCheck() ) + { + bool bIsOnlineSpell = pShell->GetViewOptions()->IsOnlineSpell(); + bool bIsAutoGrammar = false; + SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bIsAutoGrammar; + + if( bIsOnlineSpell && bIsAutoGrammar && m_rDoc.StartGrammarChecking( true ) ) + return IdleJob::Grammar; + } + + // If we're dragging re-layout doesn't occur so avoid a busy loop. + if (!pShell->HasDrawViewDrag()) + { + for ( auto pLayout : m_rDoc.GetAllLayouts() ) + { + if( pLayout->IsIdleFormat() ) + return IdleJob::Layout; + } + } + + SwFieldUpdateFlags nFieldUpdFlag = m_rDoc.GetDocumentSettingManager().getFieldUpdateFlags(true); + if( ( AUTOUPD_FIELD_ONLY == nFieldUpdFlag + || AUTOUPD_FIELD_AND_CHARTS == nFieldUpdFlag ) + && m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().IsFieldsDirty() ) + { + if( m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().IsInUpdateFields() + || m_rDoc.getIDocumentFieldsAccess().IsExpFieldsLocked() ) + return IdleJob::Busy; + return IdleJob::Fields; + } + } + + return IdleJob::None; +} + +IMPL_LINK_NOARG( DocumentTimerManager, DoIdleJobs, Timer*, void ) +{ +#ifdef TIMELOG + static ::rtl::Logfile* pModLogFile = new ::rtl::Logfile( "First DoIdleJobs" ); +#endif + BlockIdling(); + StopIdling(); + + IdleJob eJob = GetNextIdleJob(); + + switch ( eJob ) + { + case IdleJob::Grammar: + m_rDoc.StartGrammarChecking(); + break; + + case IdleJob::Layout: + for ( auto pLayout : m_rDoc.GetAllLayouts() ) + if( pLayout->IsIdleFormat() ) + { + pLayout->GetCurrShell()->LayoutIdle(); + break; + } + break; + + case IdleJob::Fields: + { + SwViewShell* pShell( m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ); + SwRootFrame* pTmpRoot = m_rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + + // Action brackets! + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetInUpdateFields( true ); + + pTmpRoot->StartAllAction(); + + // no jump on update of fields #i85168# + const bool bOldLockView = pShell->IsViewLocked(); + pShell->LockView( true ); + + m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Chapter )->ModifyNotification( nullptr, nullptr ); // ChapterField + m_rDoc.getIDocumentFieldsAccess().UpdateExpFields( nullptr, false ); // Updates ExpressionFields + m_rDoc.getIDocumentFieldsAccess().UpdateTableFields(nullptr); // Tables + m_rDoc.getIDocumentFieldsAccess().UpdateRefFields(); // References + + // Validate and update the paragraph signatures. + if (m_rDoc.GetEditShell()) + m_rDoc.GetEditShell()->ValidateAllParagraphSignatures(true); + + pTmpRoot->EndAllAction(); + + pShell->LockView( bOldLockView ); + + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetInUpdateFields( false ); + m_rDoc.getIDocumentFieldsAccess().GetUpdateFields().SetFieldsDirty( false ); + break; + } + + case IdleJob::Busy: + break; + case IdleJob::None: + break; + } + + if ( IdleJob::None != eJob ) + StartIdling(); + UnblockIdling(); + +#ifdef TIMELOG + if( pModLogFile && 1 != (long)pModLogFile ) + delete pModLogFile, static_cast<long&>(pModLogFile) = 1; +#endif +} + +DocumentTimerManager::~DocumentTimerManager() {} + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/SwDocIdle.cxx b/sw/source/core/doc/SwDocIdle.cxx new file mode 100644 index 000000000..24f51c90e --- /dev/null +++ b/sw/source/core/doc/SwDocIdle.cxx @@ -0,0 +1,62 @@ +/* -*- 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 <view.hxx> +#include <wrtsh.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <viewopt.hxx> +#include <vcl/scheduler.hxx> + +#include <SwDocIdle.hxx> +#include <IDocumentTimerAccess.hxx> + +namespace sw +{ + +sal_uInt64 SwDocIdle::UpdateMinPeriod( sal_uInt64 /* nTimeNow */ ) const +{ + bool bReadyForSchedule = true; + + SwView* pView = m_rDoc.GetDocShell() ? m_rDoc.GetDocShell()->GetView() : nullptr; + if( pView ) + { + SwWrtShell& rWrtShell = pView->GetWrtShell(); + bReadyForSchedule = rWrtShell.GetViewOptions()->IsIdle(); + } + + if( bReadyForSchedule && !m_rDoc.getIDocumentTimerAccess().IsDocIdle() ) + bReadyForSchedule = false; + + return bReadyForSchedule + ? Scheduler::ImmediateTimeoutMs : Scheduler::InfiniteTimeoutMs; +} + +SwDocIdle::SwDocIdle( SwDoc &doc ) + : m_rDoc( doc ) +{ +} + +SwDocIdle::~SwDocIdle() +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/SwStyleNameMapper.cxx b/sw/source/core/doc/SwStyleNameMapper.cxx new file mode 100644 index 000000000..9f2f88c6d --- /dev/null +++ b/sw/source/core/doc/SwStyleNameMapper.cxx @@ -0,0 +1,772 @@ +/* -*- 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 <SwStyleNameMapper.hxx> +#include <poolfmt.hxx> +#include <strings.hrc> +#include <swtypes.hxx> + +#ifdef _NEED_TO_DEBUG_MAPPING +#include <stdlib.h> +#endif + +namespace +{ + +const OUString & +lcl_GetSpecialExtraName(const OUString& rExtraName, const bool bIsUIName ) +{ + const std::vector<OUString>& rExtraArr = bIsUIName + ? SwStyleNameMapper::GetExtraUINameArray() + : SwStyleNameMapper::GetExtraProgNameArray(); + static const sal_uInt16 nIds[] = + { + RES_POOLCOLL_LABEL_DRAWING - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_ABB - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_TABLE - RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FRAME- RES_POOLCOLL_EXTRA_BEGIN, + RES_POOLCOLL_LABEL_FIGURE-RES_POOLCOLL_EXTRA_BEGIN, + 0 + }; + const sal_uInt16 * pIds; + for ( pIds = nIds; *pIds; ++pIds) + { + if (rExtraName == rExtraArr[ *pIds ]) + { + return bIsUIName + ? SwStyleNameMapper::GetExtraProgNameArray()[*pIds] + : SwStyleNameMapper::GetExtraUINameArray()[*pIds]; + } + } + return rExtraName; +} + +bool lcl_SuffixIsUser(const OUString & rString) +{ + const sal_Unicode *pChar = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + bool bRet = false; + if( nLen > 8 && + pChar[nLen-7] == ' ' && + pChar[nLen-6] == '(' && + pChar[nLen-5] == 'u' && + pChar[nLen-4] == 's' && + pChar[nLen-3] == 'e' && + pChar[nLen-2] == 'r' && + pChar[nLen-1] == ')' ) + bRet = true; + return bRet; +} + +void lcl_CheckSuffixAndDelete(OUString & rString) +{ + if (lcl_SuffixIsUser(rString)) + { + rString = rString.copy(0, rString.getLength() - 7); + } +} + +NameToIdHash HashFromRange(sal_uInt16 nAcc) { return NameToIdHash(nAcc); } +template <typename... Rest> +NameToIdHash HashFromRange(sal_uInt16 nAcc, sal_uInt16 nBegin, sal_uInt16 nEnd, + const std::vector<OUString>& (*pFunc)(), Rest... rest) +{ + NameToIdHash hash(HashFromRange(nAcc + nEnd - nBegin, rest...)); + sal_uInt16 nIndex, nId; + const std::vector<OUString>& rStrings = pFunc(); + for (nIndex = 0, nId = nBegin; nId < nEnd; nId++, nIndex++) + hash[rStrings[nIndex]] = nId; + return hash; +} + +template <auto initFunc> struct TablePair +{ + static const NameToIdHash& getMap(bool bProgName) + { + if (bProgName) + { + static const NameToIdHash s_aProgMap(initFunc(true)); + return s_aProgMap; + } + static const NameToIdHash s_aUIMap(initFunc(false)); + return s_aUIMap; + } +}; + +NameToIdHash GetParaMap (bool bProgName) +{ + return HashFromRange(0, + RES_POOLCOLL_TEXT_BEGIN, RES_POOLCOLL_TEXT_END, bProgName ? &SwStyleNameMapper::GetTextProgNameArray : &SwStyleNameMapper::GetTextUINameArray, + RES_POOLCOLL_LISTS_BEGIN, RES_POOLCOLL_LISTS_END, bProgName ? &SwStyleNameMapper::GetListsProgNameArray : &SwStyleNameMapper::GetListsUINameArray, + RES_POOLCOLL_EXTRA_BEGIN, RES_POOLCOLL_EXTRA_END, bProgName ? &SwStyleNameMapper::GetExtraProgNameArray : &SwStyleNameMapper::GetExtraUINameArray, + RES_POOLCOLL_REGISTER_BEGIN, RES_POOLCOLL_REGISTER_END, bProgName ? &SwStyleNameMapper::GetRegisterProgNameArray : &SwStyleNameMapper::GetRegisterUINameArray, + RES_POOLCOLL_DOC_BEGIN, RES_POOLCOLL_DOC_END, bProgName ? &SwStyleNameMapper::GetDocProgNameArray : &SwStyleNameMapper::GetDocUINameArray, + RES_POOLCOLL_HTML_BEGIN, RES_POOLCOLL_HTML_END, bProgName ? &SwStyleNameMapper::GetHTMLProgNameArray : &SwStyleNameMapper::GetHTMLUINameArray + ); +}; + +NameToIdHash GetCharMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLCHR_NORMAL_BEGIN, RES_POOLCHR_NORMAL_END, bProgName ? &SwStyleNameMapper::GetChrFormatProgNameArray : &SwStyleNameMapper::GetChrFormatUINameArray, + RES_POOLCHR_HTML_BEGIN, RES_POOLCHR_HTML_END, bProgName ? &SwStyleNameMapper::GetHTMLChrFormatProgNameArray : &SwStyleNameMapper::GetHTMLChrFormatUINameArray + ); +}; + +NameToIdHash GetFrameMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLFRM_BEGIN, RES_POOLFRM_END, bProgName ? &SwStyleNameMapper::GetFrameFormatProgNameArray : &SwStyleNameMapper::GetFrameFormatUINameArray + ); +}; + +NameToIdHash GetPageMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLPAGE_BEGIN, RES_POOLPAGE_END, bProgName ? &SwStyleNameMapper::GetPageDescProgNameArray : &SwStyleNameMapper::GetPageDescUINameArray + ); +}; + +NameToIdHash GetNumRuleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLNUMRULE_BEGIN, RES_POOLNUMRULE_END, bProgName ? &SwStyleNameMapper::GetNumRuleProgNameArray : &SwStyleNameMapper::GetNumRuleUINameArray + ); +}; + +NameToIdHash GetTableStyleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLTABLESTYLE_BEGIN, RES_POOLTABLESTYLE_END, bProgName ? &SwStyleNameMapper::GetTableStyleProgNameArray : &SwStyleNameMapper::GetTableStyleUINameArray + ); +}; + +NameToIdHash GetCellStyleMap(bool bProgName) +{ + return HashFromRange(0, + RES_POOLCELLSTYLE_BEGIN, RES_POOLCELLSTYLE_END, bProgName ? &SwStyleNameMapper::GetCellStyleProgNameArray : &SwStyleNameMapper::GetCellStyleUINameArray + ); +}; + +} // namespace + +#ifdef _NEED_TO_DEBUG_MAPPING +void SwStyleNameMapper::testNameTable( SwGetPoolIdFromName const nFamily, sal_uInt16 const nStartIndex, sal_uInt16 const nEndIndex ) +{ + sal_uInt16 nIndex; + sal_uInt16 nId; + + for ( nIndex = 0, nId = nStartIndex ; nId < nEndIndex ; nId++,nIndex++ ) + { + OUString aString, bString; + FillUIName ( nId, aString ); + bString = GetProgName ( nFamily, aString ); + sal_uInt16 nNewId = GetPoolIdFromProgName ( bString, nFamily ); + FillProgName ( nNewId, aString ); + bString = GetUIName ( aString, nFamily ); + nNewId = GetPoolIdFromUIName ( aString, nFamily ); + if ( nNewId != nId ) + abort(); + } +} +#endif + +const NameToIdHash & SwStyleNameMapper::getHashTable ( SwGetPoolIdFromName eFlags, bool bProgName ) +{ +#ifdef _NEED_TO_DEBUG_MAPPING + static bool bTested = false; + if ( !bTested ) + { + bTested = true; + + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_TEXT_BEGIN, RES_POOLCOLL_TEXT_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_LISTS_BEGIN, RES_POOLCOLL_LISTS_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_EXTRA_BEGIN, RES_POOLCOLL_EXTRA_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_REGISTER_BEGIN, RES_POOLCOLL_REGISTER_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_DOC_BEGIN, RES_POOLCOLL_DOC_END ); + testNameTable( SwGetPoolIdFromName::TxtColl, RES_POOLCOLL_HTML_BEGIN, RES_POOLCOLL_HTML_END ); + testNameTable( SwGetPoolIdFromName::ChrFmt, RES_POOLCHR_NORMAL_BEGIN, RES_POOLCHR_NORMAL_END ); + testNameTable( SwGetPoolIdFromName::ChrFmt, RES_POOLCHR_HTML_BEGIN, RES_POOLCHR_HTML_END ); + testNameTable( SwGetPoolIdFromName::FrmFmt, RES_POOLFRM_BEGIN, RES_POOLFRM_END ); + testNameTable( SwGetPoolIdFromName::PageDesc, RES_POOLPAGE_BEGIN, RES_POOLPAGE_END ); + testNameTable( SwGetPoolIdFromName::NumRule, RES_POOLNUMRULE_BEGIN, RES_POOLNUMRULE_END ); + } +#endif + + switch ( eFlags ) + { + case SwGetPoolIdFromName::TxtColl: + return TablePair<GetParaMap>::getMap(bProgName); + case SwGetPoolIdFromName::ChrFmt: + return TablePair<GetCharMap>::getMap(bProgName); + case SwGetPoolIdFromName::FrmFmt: + return TablePair<GetFrameMap>::getMap(bProgName); + case SwGetPoolIdFromName::PageDesc: + return TablePair<GetPageMap>::getMap(bProgName); + case SwGetPoolIdFromName::NumRule: + return TablePair<GetNumRuleMap>::getMap(bProgName); + case SwGetPoolIdFromName::TabStyle: + return TablePair<GetTableStyleMap>::getMap(bProgName); + case SwGetPoolIdFromName::CellStyle: + return TablePair<GetCellStyleMap>::getMap(bProgName); + } + + assert(false); // must not reach here + abort(); +} + +// This gets the UI name from the programmatic name +const OUString& SwStyleNameMapper::GetUIName(const OUString& rName, + SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromProgName ( rName, eFlags ); + return nId != USHRT_MAX ? GetUIName( nId, rName ) : rName; +} + +// Get the programmatic name from the UI name +const OUString& SwStyleNameMapper::GetProgName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromUIName ( rName, eFlags ); + return nId != USHRT_MAX ? GetProgName( nId, rName ) : rName; +} + +// Get the programmatic name from the UI name in rName and put it into rFillName +void SwStyleNameMapper::FillProgName( + const OUString& rName, OUString& rFillName, + SwGetPoolIdFromName const eFlags) +{ + sal_uInt16 nId = GetPoolIdFromUIName ( rName, eFlags ); + if ( nId == USHRT_MAX ) + { + // rName isn't in our UI name table...check if it's in the programmatic one + nId = GetPoolIdFromProgName ( rName, eFlags ); + + rFillName = rName; + if (nId == USHRT_MAX ) + { + // It isn't ...make sure the suffix isn't already " (user)"...if it is, + // we need to add another one + if (lcl_SuffixIsUser(rFillName)) + rFillName += " (user)"; + } + else + { + // It's in the programmatic name table...append suffix + rFillName += " (user)"; + } + } + else + { + // If we aren't trying to disambiguate, then just do a normal fill + fillNameFromId(nId, rFillName, true); + } + + if (eFlags == SwGetPoolIdFromName::ChrFmt && rName == SwResId(STR_POOLCHR_STANDARD)) + rFillName = "Standard"; +} + +// Get the UI name from the programmatic name in rName and put it into rFillName +void SwStyleNameMapper::FillUIName( + const OUString& rName, OUString& rFillName, + SwGetPoolIdFromName const eFlags) +{ + OUString aName = rName; + if (eFlags == SwGetPoolIdFromName::ChrFmt && rName == "Standard") + aName = SwResId(STR_POOLCHR_STANDARD); + + sal_uInt16 nId = GetPoolIdFromProgName ( aName, eFlags ); + if ( nId == USHRT_MAX ) + { + rFillName = aName; + // aName isn't in our Prog name table...check if it has a " (user)" suffix, if so remove it + lcl_CheckSuffixAndDelete ( rFillName ); + } + else + { + // If we aren't trying to disambiguate, then just do a normal fill + fillNameFromId(nId, rFillName, false); + } +} + +const OUString& SwStyleNameMapper::getNameFromId( + sal_uInt16 const nId, const OUString& rFillName, bool const bProgName) +{ + sal_uInt16 nStt = 0; + const std::vector<OUString>* pStrArr = nullptr; + + switch( (USER_FMT | COLL_GET_RANGE_BITS | POOLGRP_NOCOLLID) & nId ) + { + case COLL_TEXT_BITS: + if( RES_POOLCOLL_TEXT_BEGIN <= nId && nId < RES_POOLCOLL_TEXT_END ) + { + pStrArr = bProgName ? &GetTextProgNameArray() : &GetTextUINameArray(); + nStt = RES_POOLCOLL_TEXT_BEGIN; + } + break; + case COLL_LISTS_BITS: + if( RES_POOLCOLL_LISTS_BEGIN <= nId && nId < RES_POOLCOLL_LISTS_END ) + { + pStrArr = bProgName ? &GetListsProgNameArray() : &GetListsUINameArray(); + nStt = RES_POOLCOLL_LISTS_BEGIN; + } + break; + case COLL_EXTRA_BITS: + if( RES_POOLCOLL_EXTRA_BEGIN <= nId && nId < RES_POOLCOLL_EXTRA_END ) + { + pStrArr = bProgName ? &GetExtraProgNameArray() : &GetExtraUINameArray(); + nStt = RES_POOLCOLL_EXTRA_BEGIN; + } + break; + case COLL_REGISTER_BITS: + if( RES_POOLCOLL_REGISTER_BEGIN <= nId && nId < RES_POOLCOLL_REGISTER_END ) + { + pStrArr = bProgName ? &GetRegisterProgNameArray() : &GetRegisterUINameArray(); + nStt = RES_POOLCOLL_REGISTER_BEGIN; + } + break; + case COLL_DOC_BITS: + if( RES_POOLCOLL_DOC_BEGIN <= nId && nId < RES_POOLCOLL_DOC_END ) + { + pStrArr = bProgName ? &GetDocProgNameArray() : &GetDocUINameArray(); + nStt = RES_POOLCOLL_DOC_BEGIN; + } + break; + case COLL_HTML_BITS: + if( RES_POOLCOLL_HTML_BEGIN <= nId && nId < RES_POOLCOLL_HTML_END ) + { + pStrArr = bProgName ? &GetHTMLProgNameArray() : &GetHTMLUINameArray(); + nStt = RES_POOLCOLL_HTML_BEGIN; + } + break; + case POOLGRP_CHARFMT: + if( RES_POOLCHR_NORMAL_BEGIN <= nId && nId < RES_POOLCHR_NORMAL_END ) + { + pStrArr = bProgName ? &GetChrFormatProgNameArray() : &GetChrFormatUINameArray(); + nStt = RES_POOLCHR_NORMAL_BEGIN; + } + else if( RES_POOLCHR_HTML_BEGIN <= nId && nId < RES_POOLCHR_HTML_END ) + { + pStrArr = bProgName ? &GetHTMLChrFormatProgNameArray() : &GetHTMLChrFormatUINameArray(); + nStt = RES_POOLCHR_HTML_BEGIN; + } + break; + case POOLGRP_FRAMEFMT: + if( RES_POOLFRM_BEGIN <= nId && nId < RES_POOLFRM_END ) + { + pStrArr = bProgName ? &GetFrameFormatProgNameArray() : &GetFrameFormatUINameArray(); + nStt = RES_POOLFRM_BEGIN; + } + break; + case POOLGRP_PAGEDESC: + if( RES_POOLPAGE_BEGIN <= nId && nId < RES_POOLPAGE_END ) + { + pStrArr = bProgName ? &GetPageDescProgNameArray() : &GetPageDescUINameArray(); + nStt = RES_POOLPAGE_BEGIN; + } + break; + case POOLGRP_NUMRULE: + if( RES_POOLNUMRULE_BEGIN <= nId && nId < RES_POOLNUMRULE_END ) + { + pStrArr = bProgName ? &GetNumRuleProgNameArray() : &GetNumRuleUINameArray(); + nStt = RES_POOLNUMRULE_BEGIN; + } + break; + case POOLGRP_TABSTYLE: + if( RES_POOLTABLESTYLE_BEGIN <= nId && nId < RES_POOLTABLESTYLE_END ) + { + pStrArr = bProgName ? &GetTableStyleProgNameArray() : &GetTableStyleUINameArray(); + nStt = RES_POOLTABLESTYLE_BEGIN; + } + break; + } + return pStrArr ? pStrArr->operator[](nId - nStt) : rFillName; +} + +void SwStyleNameMapper::fillNameFromId( + sal_uInt16 const nId, OUString& rFillName, bool bProgName) +{ + rFillName = getNameFromId(nId, rFillName, bProgName); +} + +// Get the UI name from the pool ID +void SwStyleNameMapper::FillUIName(sal_uInt16 const nId, OUString& rFillName) +{ + fillNameFromId(nId, rFillName, false); +} + +// Get the UI name from the pool ID +const OUString& SwStyleNameMapper::GetUIName( + sal_uInt16 const nId, const OUString& rName) +{ + return getNameFromId(nId, rName, false); +} + +// Get the programmatic name from the pool ID +void SwStyleNameMapper::FillProgName(sal_uInt16 nId, OUString& rFillName) +{ + fillNameFromId(nId, rFillName, true); +} + +// Get the programmatic name from the pool ID +const OUString& +SwStyleNameMapper::GetProgName(sal_uInt16 const nId, const OUString& rName) +{ + return getNameFromId(nId, rName, true); +} + +// This gets the PoolId from the UI Name +sal_uInt16 SwStyleNameMapper::GetPoolIdFromUIName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + const NameToIdHash & rHashMap = getHashTable ( eFlags, false ); + NameToIdHash::const_iterator aIter = rHashMap.find(rName); + return aIter != rHashMap.end() ? (*aIter).second : USHRT_MAX; +} + +// Get the Pool ID from the programmatic name +sal_uInt16 SwStyleNameMapper::GetPoolIdFromProgName( + const OUString& rName, SwGetPoolIdFromName const eFlags) +{ + const NameToIdHash & rHashMap = getHashTable ( eFlags, true ); + NameToIdHash::const_iterator aIter = rHashMap.find(rName); + return aIter != rHashMap.end() ? (*aIter).second : USHRT_MAX; +} + +// Hard coded Programmatic Name tables + +/// returns an empty array because Cell Names aren't translated +const std::vector<OUString>& SwStyleNameMapper::GetCellStyleUINameArray() +{ + static const std::vector<OUString> s_aCellStyleUINameArray; + return s_aCellStyleUINameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetTextProgNameArray() +{ + static const std::vector<OUString> s_aTextProgNameArray = { + "Standard", // RES_POOLCOLL_STANDARD + "Text body", + "First line indent", + "Hanging indent", + "Text body indent", + "Salutation", + "Signature", + "List Indent", // RES_POOLCOLL_CONFRONTATION + "Marginalia", + "Heading", + "Heading 1", + "Heading 2", + "Heading 3", + "Heading 4", + "Heading 5", + "Heading 6", + "Heading 7", + "Heading 8", + "Heading 9", + "Heading 10", // RES_POOLCOLL_TEXT_END + }; + return s_aTextProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetListsProgNameArray() +{ + static const std::vector<OUString> s_aListsProgNameArray = { + "List", // STR_POCO_PRGM_NUMBER_BULLET_BASE + "Numbering 1 Start", // STR_POCO_PRGM_NUM_LEVEL1S + "Numbering 1", + "Numbering 1 End", + "Numbering 1 Cont.", + "Numbering 2 Start", + "Numbering 2", + "Numbering 2 End", + "Numbering 2 Cont.", + "Numbering 3 Start", + "Numbering 3", + "Numbering 3 End", + "Numbering 3 Cont.", + "Numbering 4 Start", + "Numbering 4", + "Numbering 4 End", + "Numbering 4 Cont.", + "Numbering 5 Start", + "Numbering 5", + "Numbering 5 End", + "Numbering 5 Cont.", + "List 1 Start", + "List 1", + "List 1 End", + "List 1 Cont.", + "List 2 Start", + "List 2", + "List 2 End", + "List 2 Cont.", + "List 3 Start", + "List 3", + "List 3 End", + "List 3 Cont.", + "List 4 Start", + "List 4", + "List 4 End", + "List 4 Cont.", + "List 5 Start", + "List 5", + "List 5 End", + "List 5 Cont.", // STR_POCO_PRGM_BULLET_NONUM5 + }; + return s_aListsProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetExtraProgNameArray() +{ + static const std::vector<OUString> s_aExtraProgNameArray = { + "Header and Footer", // RES_POOLCOLL_EXTRA_BEGIN + "Header", + "Header left", + "Header right", + "Footer", + "Footer left", + "Footer right", + "Table Contents", + "Table Heading", + "Caption", + "Illustration", + "Table", + "Text", + "Figure", // RES_POOLCOLL_LABEL_FIGURE + "Frame contents", + "Footnote", + "Addressee", + "Sender", + "Endnote", + "Drawing", // RES_POOLCOLL_LABEL_DRAWING + }; + return s_aExtraProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetRegisterProgNameArray() +{ + static const std::vector<OUString> s_aRegisterProgNameArray = { + "Index", // STR_POCO_PRGM_REGISTER_BASE + "Index Heading", // STR_POCO_PRGM_TOX_IDXH + "Index 1", + "Index 2", + "Index 3", + "Index Separator", + "Contents Heading", + "Contents 1", + "Contents 2", + "Contents 3", + "Contents 4", + "Contents 5", + "User Index Heading", + "User Index 1", + "User Index 2", + "User Index 3", + "User Index 4", + "User Index 5", + "Contents 6", + "Contents 7", + "Contents 8", + "Contents 9", + "Contents 10", + "Figure Index Heading", + "Figure Index 1", + "Object index heading", + "Object index 1", + "Table index heading", + "Table index 1", + "Bibliography Heading", + "Bibliography 1", + "User Index 6", + "User Index 7", + "User Index 8", + "User Index 9", + "User Index 10", // STR_POCO_PRGM_TOX_USER10 + }; + return s_aRegisterProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetDocProgNameArray() +{ + static const std::vector<OUString> s_aDocProgNameArray = { + "Title", // STR_POCO_PRGM_DOC_TITLE + "Subtitle", + "Appendix", + }; + return s_aDocProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLProgNameArray() +{ + static const std::vector<OUString> s_aHTMLProgNameArray = { + "Quotations", + "Preformatted Text", + "Horizontal Line", + "List Contents", + "List Heading", // STR_POCO_PRGM_HTML_DT + }; + return s_aHTMLProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetFrameFormatProgNameArray() +{ + static const std::vector<OUString> s_aFrameFormatProgNameArray = { + "Frame", // RES_POOLFRM_FRAME + "Graphics", + "OLE", + "Formula", + "Marginalia", + "Watermark", + "Labels", // RES_POOLFRM_LABEL + }; + return s_aFrameFormatProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetChrFormatProgNameArray() +{ + static const std::vector<OUString> s_aChrFormatProgNameArray = { + "Footnote Symbol", // RES_POOLCHR_FOOTNOTE + "Page Number", + "Caption characters", + "Drop Caps", + "Numbering Symbols", + "Bullet Symbols", + "Internet link", + "Visited Internet Link", + "Placeholder", + "Index Link", + "Endnote Symbol", + "Line numbering", + "Main index entry", + "Footnote anchor", + "Endnote anchor", + "Rubies", // RES_POOLCHR_RUBYTEXT + "Vertical Numbering Symbols", // RES_POOLCHR_VERT_NUMBER + }; + return s_aChrFormatProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLChrFormatProgNameArray() +{ + static const std::vector<OUString> s_aHTMLChrFormatProgNameArray = { + "Emphasis", // RES_POOLCHR_HTML_EMPHASIS + "Citation", + "Strong Emphasis", + "Source Text", + "Example", + "User Entry", + "Variable", + "Definition", + "Teletype", // RES_POOLCHR_HTML_TELETYPE + }; + return s_aHTMLChrFormatProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetPageDescProgNameArray() +{ + static const std::vector<OUString> s_aPageDescProgNameArray = { + "Standard", // STR_POOLPAGE_PRGM_STANDARD + "First Page", + "Left Page", + "Right Page", + "Envelope", + "Index", + "HTML", + "Footnote", + "Endnote", // STR_POOLPAGE_PRGM_ENDNOTE + "Landscape", + }; + return s_aPageDescProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetNumRuleProgNameArray() +{ + static const std::vector<OUString> s_aNumRuleProgNameArray = { + "Numbering 123", // STR_POOLNUMRULE_PRGM_NUM1 + "Numbering ABC", + "Numbering abc", + "Numbering IVX", + "Numbering ivx", + "List 1", + "List 2", + "List 3", + "List 4", + "List 5", // STR_POOLNUMRULE_PRGM_BUL5 + }; + return s_aNumRuleProgNameArray; +} + +const std::vector<OUString>& SwStyleNameMapper::GetTableStyleProgNameArray() +{ + // XXX MUST match the entries of STR_TABSTYLE_ARY in + // sw/source/core/doc/DocumentStylePoolManager.cxx and MUST match the order of + // RES_POOL_TABLESTYLE_TYPE in sw/inc/poolfmt.hxx + static const std::vector<OUString> s_aTableStyleProgNameArray = { + "Default Style", // RES_POOLTABLESTYLE_DEFAULT + "3D", // RES_POOLTABLESTYLE_3D + "Black 1", // RES_POOLTABLESTYLE_BLACK1 + "Black 2", // RES_POOLTABLESTYLE_BLACK2 + "Blue", // RES_POOLTABLESTYLE_BLUE + "Brown", // RES_POOLTABLESTYLE_BROWN + "Currency", // RES_POOLTABLESTYLE_CURRENCY + "Currency 3D", // RES_POOLTABLESTYLE_CURRENCY_3D + "Currency Gray", // RES_POOLTABLESTYLE_CURRENCY_GRAY + "Currency Lavender", // RES_POOLTABLESTYLE_CURRENCY_LAVENDER + "Currency Turquoise", // RES_POOLTABLESTYLE_CURRENCY_TURQUOISE + "Gray", // RES_POOLTABLESTYLE_GRAY + "Green", // RES_POOLTABLESTYLE_GREEN + "Lavender", // RES_POOLTABLESTYLE_LAVENDER + "Red", // RES_POOLTABLESTYLE_RED + "Turquoise", // RES_POOLTABLESTYLE_TURQUOISE + "Yellow", // RES_POOLTABLESTYLE_YELLOW + "Academic", // RES_POOLTABLESTYLE_LO6_ACADEMIC + "Box List Blue", // RES_POOLTABLESTYLE_LO6_BOX_LIST_BLUE + "Box List Green", // RES_POOLTABLESTYLE_LO6_BOX_LIST_GREEN + "Box List Red", // RES_POOLTABLESTYLE_LO6_BOX_LIST_RED + "Box List Yellow", // RES_POOLTABLESTYLE_LO6_BOX_LIST_YELLOW + "Elegant", // RES_POOLTABLESTYLE_LO6_ELEGANT + "Financial", // RES_POOLTABLESTYLE_LO6_FINANCIAL + "Simple Grid Columns", // RES_POOLTABLESTYLE_LO6_SIMPLE_GRID_COLUMNS + "Simple Grid Rows", // RES_POOLTABLESTYLE_LO6_SIMPLE_GRID_ROWS + "Simple List Shaded", // RES_POOLTABLESTYLE_LO6_SIMPLE_LIST_SHADED + }; + return s_aTableStyleProgNameArray; +} + +/// returns an empty array because Cell Names aren't translated +const std::vector<OUString>& SwStyleNameMapper::GetCellStyleProgNameArray() +{ + static const std::vector<OUString> s_aCellStyleProgNameArray; + return s_aCellStyleProgNameArray; +} + +const OUString & +SwStyleNameMapper::GetSpecialExtraProgName(const OUString& rExtraUIName) +{ + return lcl_GetSpecialExtraName( rExtraUIName, true ); +} + +const OUString & +SwStyleNameMapper::GetSpecialExtraUIName(const OUString& rExtraProgName) +{ + return lcl_GetSpecialExtraName( rExtraProgName, false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/acmplwrd.cxx b/sw/source/core/doc/acmplwrd.cxx new file mode 100644 index 000000000..b256c5065 --- /dev/null +++ b/sw/source/core/doc/acmplwrd.cxx @@ -0,0 +1,402 @@ +/* -*- 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 <tools/urlobj.hxx> +#include <hintids.hxx> +#include <hints.hxx> +#include <acmplwrd.hxx> +#include <doc.hxx> +#include <pagedesc.hxx> +#include <poolfmt.hxx> +#include <calbck.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <editeng/svxacorr.hxx> +#include <osl/diagnose.h> + +#include <editeng/acorrcfg.hxx> +#include <sfx2/docfile.hxx> +#include <docsh.hxx> + +#include <cassert> +#include <vector> + +class SwAutoCompleteClient : public SwClient +{ + SwAutoCompleteWord* m_pAutoCompleteWord; + SwDoc* m_pDoc; +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong s_nSwAutoCompleteClientCount; +#endif +public: + SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc); + SwAutoCompleteClient(const SwAutoCompleteClient& rClient); + virtual ~SwAutoCompleteClient() override; + + SwAutoCompleteClient& operator=(const SwAutoCompleteClient& rClient); + + const SwDoc& GetDoc() const {return *m_pDoc;} +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong GetElementCount() {return s_nSwAutoCompleteClientCount;} +#endif +protected: + virtual void Modify( const SfxPoolItem* pOld, const SfxPoolItem *pNew) override; +}; + +class SwAutoCompleteWord_Impl +{ + std::vector<SwAutoCompleteClient> + m_aClientVector; + SwAutoCompleteWord& m_rAutoCompleteWord; +public: + explicit SwAutoCompleteWord_Impl(SwAutoCompleteWord& rParent) : + m_rAutoCompleteWord(rParent){} + void AddDocument(SwDoc& rDoc); + void RemoveDocument(const SwDoc& rDoc); +}; + +class SwAutoCompleteString + : public editeng::IAutoCompleteString +{ +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong s_nSwAutoCompleteStringCount; +#endif + std::vector<const SwDoc*> m_aSourceDocs; + public: + SwAutoCompleteString(const OUString& rStr, sal_Int32 nLen); + + virtual ~SwAutoCompleteString() override; + void AddDocument(const SwDoc& rDoc); + //returns true if last document reference has been removed + bool RemoveDocument(const SwDoc& rDoc); +#if OSL_DEBUG_LEVEL > 0 + static sal_uLong GetElementCount() {return s_nSwAutoCompleteStringCount;} +#endif +}; +#if OSL_DEBUG_LEVEL > 0 + sal_uLong SwAutoCompleteClient::s_nSwAutoCompleteClientCount = 0; + sal_uLong SwAutoCompleteString::s_nSwAutoCompleteStringCount = 0; +#endif + +SwAutoCompleteClient::SwAutoCompleteClient(SwAutoCompleteWord& rToTell, SwDoc& rSwDoc) : + m_pAutoCompleteWord(&rToTell), + m_pDoc(&rSwDoc) +{ + m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this); +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteClientCount; +#endif +} + +SwAutoCompleteClient::SwAutoCompleteClient(const SwAutoCompleteClient& rClient) : + SwClient(), + m_pAutoCompleteWord(rClient.m_pAutoCompleteWord), + m_pDoc(rClient.m_pDoc) +{ + m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->Add(this); +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteClientCount; +#endif +} + +SwAutoCompleteClient::~SwAutoCompleteClient() +{ +#if OSL_DEBUG_LEVEL > 0 + --s_nSwAutoCompleteClientCount; +#else + (void) this; +#endif +} + +SwAutoCompleteClient& SwAutoCompleteClient::operator=(const SwAutoCompleteClient& rClient) +{ + m_pAutoCompleteWord = rClient.m_pAutoCompleteWord; + m_pDoc = rClient.m_pDoc; + StartListeningToSameModifyAs(rClient); + return *this; +} + +void SwAutoCompleteClient::Modify( const SfxPoolItem* pOld, const SfxPoolItem *) +{ + switch( pOld ? pOld->Which() : 0 ) + { + case RES_REMOVE_UNO_OBJECT: + case RES_OBJECTDYING: + if( static_cast<void*>(GetRegisteredIn()) == static_cast<const SwPtrMsgPoolItem *>(pOld)->pObject ) + EndListeningAll(); + m_pAutoCompleteWord->DocumentDying(*m_pDoc); + break; + } +} + +void SwAutoCompleteWord_Impl::AddDocument(SwDoc& rDoc) +{ + if (std::any_of(m_aClientVector.begin(), m_aClientVector.end(), + [&rDoc](SwAutoCompleteClient& rClient) { return &rClient.GetDoc() == &rDoc; })) + return; + m_aClientVector.emplace_back(m_rAutoCompleteWord, rDoc); +} + +void SwAutoCompleteWord_Impl::RemoveDocument(const SwDoc& rDoc) +{ + auto aIt = std::find_if(m_aClientVector.begin(), m_aClientVector.end(), + [&rDoc](SwAutoCompleteClient& rClient) { return &rClient.GetDoc() == &rDoc; }); + if (aIt != m_aClientVector.end()) + m_aClientVector.erase(aIt); +} + +SwAutoCompleteString::SwAutoCompleteString( + const OUString& rStr, sal_Int32 const nLen) + : editeng::IAutoCompleteString(rStr.copy(0, nLen)) +{ +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteStringCount; +#endif +} + +SwAutoCompleteString::~SwAutoCompleteString() +{ +#if OSL_DEBUG_LEVEL > 0 + --s_nSwAutoCompleteStringCount; +#else + (void) this; +#endif +} + +void SwAutoCompleteString::AddDocument(const SwDoc& rDoc) +{ + auto aIt = std::find(m_aSourceDocs.begin(), m_aSourceDocs.end(), &rDoc); + if (aIt != m_aSourceDocs.end()) + return; + m_aSourceDocs.push_back(&rDoc); +} + +bool SwAutoCompleteString::RemoveDocument(const SwDoc& rDoc) +{ + auto aIt = std::find(m_aSourceDocs.begin(), m_aSourceDocs.end(), &rDoc); + if (aIt != m_aSourceDocs.end()) + { + m_aSourceDocs.erase(aIt); + return m_aSourceDocs.empty(); + } + return false; +} + +SwAutoCompleteWord::SwAutoCompleteWord( + editeng::SortedAutoCompleteStrings::size_type nWords, sal_uInt16 nMWrdLen ): + m_pImpl(new SwAutoCompleteWord_Impl(*this)), + m_nMaxCount( nWords ), + m_nMinWordLen( nMWrdLen ), + m_bLockWordList( false ) +{ +} + +SwAutoCompleteWord::~SwAutoCompleteWord() +{ + m_WordList.DeleteAndDestroyAll(); // so the assertion below works +#if OSL_DEBUG_LEVEL > 0 + sal_uLong nStrings = SwAutoCompleteString::GetElementCount(); + sal_uLong nClients = SwAutoCompleteClient::GetElementCount(); + OSL_ENSURE(!nStrings && !nClients, "AutoComplete: clients or string count mismatch"); +#endif +} + +bool SwAutoCompleteWord::InsertWord( const OUString& rWord, SwDoc& rDoc ) +{ + SwDocShell* pDocShell = rDoc.GetDocShell(); + SfxMedium* pMedium = pDocShell ? pDocShell->GetMedium() : nullptr; + // strings from help module should not be added + if( pMedium ) + { + const INetURLObject& rURL = pMedium->GetURLObject(); + if ( rURL.GetProtocol() == INetProtocol::VndSunStarHelp ) + return false; + } + + OUString aNewWord = rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), "") + .replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), ""); + + m_pImpl->AddDocument(rDoc); + bool bRet = false; + sal_Int32 nWrdLen = aNewWord.getLength(); + while( nWrdLen && '.' == aNewWord[ nWrdLen-1 ]) + --nWrdLen; + + if( !m_bLockWordList && nWrdLen >= m_nMinWordLen ) + { + SwAutoCompleteString* pNew = new SwAutoCompleteString( aNewWord, nWrdLen ); + pNew->AddDocument(rDoc); + std::pair<editeng::SortedAutoCompleteStrings::const_iterator, bool> + aInsPair = m_WordList.insert(pNew); + + m_LookupTree.insert( aNewWord.copy(0, nWrdLen) ); + + if (aInsPair.second) + { + bRet = true; + if (m_aLRUList.size() >= m_nMaxCount) + { + // the last one needs to be removed + // so that there is space for the first one + SwAutoCompleteString* pDel = m_aLRUList.back(); + m_aLRUList.pop_back(); + m_WordList.erase(pDel); + delete pDel; + } + m_aLRUList.push_front(pNew); + } + else + { + delete pNew; + // then move "up" + pNew = static_cast<SwAutoCompleteString*>(*aInsPair.first); + + // add the document to the already inserted string + pNew->AddDocument(rDoc); + + // move pNew to the front of the LRU list + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pNew ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + if ( m_aLRUList.begin() != it && m_aLRUList.end() != it ) + { + m_aLRUList.erase( it ); + m_aLRUList.push_front( pNew ); + } + } + } + return bRet; +} + +void SwAutoCompleteWord::SetMaxCount( + editeng::SortedAutoCompleteStrings::size_type nNewMax ) +{ + if( nNewMax < m_nMaxCount && m_aLRUList.size() > nNewMax ) + { + // remove the trailing ones + SwAutoCompleteStringPtrDeque::size_type nLRUIndex = nNewMax-1; + while (nNewMax < m_WordList.size() && nLRUIndex < m_aLRUList.size()) + { + editeng::SortedAutoCompleteStrings::const_iterator it = + m_WordList.find(m_aLRUList[ nLRUIndex++ ]); + OSL_ENSURE( m_WordList.end() != it, "String not found" ); + editeng::IAutoCompleteString *const pDel = *it; + m_WordList.erase(it - m_WordList.begin()); + delete pDel; + } + m_aLRUList.erase( m_aLRUList.begin() + nNewMax - 1, m_aLRUList.end() ); + } + m_nMaxCount = nNewMax; +} + +void SwAutoCompleteWord::SetMinWordLen( sal_uInt16 n ) +{ + // Do you really want to remove all words that are less than the minWrdLen? + if( n < m_nMinWordLen ) + { + for (size_t nPos = 0; nPos < m_WordList.size(); ++nPos) + if (m_WordList[ nPos ]->GetAutoCompleteString().getLength() < n) + { + SwAutoCompleteString *const pDel = + dynamic_cast<SwAutoCompleteString*>(m_WordList[nPos]); + m_WordList.erase(nPos); + + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + --nPos; + delete pDel; + } + } + + m_nMinWordLen = n; +} + +/** Return all words matching a given prefix + * + * @param aMatch the prefix to search for + * @param rWords the words found matching + */ +bool SwAutoCompleteWord::GetWordsMatching(const OUString& aMatch, std::vector<OUString>& rWords) const +{ + assert(rWords.empty()); + m_LookupTree.findSuggestions(aMatch, rWords); + return !rWords.empty(); +} + +void SwAutoCompleteWord::CheckChangedList( + const editeng::SortedAutoCompleteStrings& rNewLst) +{ + size_t nMyLen = m_WordList.size(), nNewLen = rNewLst.size(); + size_t nMyPos = 0, nNewPos = 0; + + for( ; nMyPos < nMyLen && nNewPos < nNewLen; ++nMyPos, ++nNewPos ) + { + const editeng::IAutoCompleteString * pStr = rNewLst[ nNewPos ]; + while (m_WordList[nMyPos] != pStr) + { + SwAutoCompleteString *const pDel = + dynamic_cast<SwAutoCompleteString*>(m_WordList[nMyPos]); + m_WordList.erase(nMyPos); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + delete pDel; + if( nMyPos >= --nMyLen ) + break; + } + } + // remove the elements at the end of the array + if( nMyPos < nMyLen ) + { + // clear LRU array first then delete the string object + for( ; nNewPos < nMyLen; ++nNewPos ) + { + SwAutoCompleteString *const pDel = + dynamic_cast<SwAutoCompleteString*>(m_WordList[nNewPos]); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pDel ); + OSL_ENSURE( m_aLRUList.end() != it, "String not found" ); + m_aLRUList.erase( it ); + delete pDel; + } + // remove from array + m_WordList.erase(m_WordList.begin() + nMyPos, + m_WordList.begin() + nMyLen); + } +} + +void SwAutoCompleteWord::DocumentDying(const SwDoc& rDoc) +{ + m_pImpl->RemoveDocument(rDoc); + + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + const bool bDelete = !pACorr->GetSwFlags().bAutoCmpltKeepList; + for (size_t nPos = m_WordList.size(); nPos; nPos--) + { + SwAutoCompleteString *const pCurrent = dynamic_cast<SwAutoCompleteString*>(m_WordList[nPos - 1]); + if(pCurrent && pCurrent->RemoveDocument(rDoc) && bDelete) + { + m_WordList.erase(nPos - 1); + SwAutoCompleteStringPtrDeque::iterator it = std::find( m_aLRUList.begin(), m_aLRUList.end(), pCurrent ); + OSL_ENSURE( m_aLRUList.end() != it, "word not found in LRU list" ); + m_aLRUList.erase( it ); + delete pCurrent; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx new file mode 100644 index 000000000..3fb9c460b --- /dev/null +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -0,0 +1,846 @@ +/* -*- 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 . + */ + +#ifdef DBG_UTIL + +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <svl/poolitem.hxx> +#include <svl/itemiter.hxx> +#include <string> +#include <map> +#include <node.hxx> +#include <ndtxt.hxx> +#include <ndhints.hxx> +#include <txatbase.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <undobj.hxx> +#include <numrule.hxx> +#include <doc.hxx> +#include <frmfmt.hxx> +#include <fmtanchr.hxx> +#include <swrect.hxx> +#include <ndarr.hxx> +#include <paratr.hxx> +#include <SwNodeNum.hxx> +#include <dbgoutsw.hxx> +#include <frameformats.hxx> +#include <iostream> +#include <cstdio> + +using namespace std; + +static OString aDbgOutResult; +bool bDbgOutStdErr = false; +bool bDbgOutPrintAttrSet = false; + +template<class T> +static OUString lcl_dbg_out_SvPtrArr(const T & rArr) +{ + OUStringBuffer aStr("[ "); + + for (typename T::const_iterator i(rArr.begin()); i != rArr.end(); ++i) + { + if (i != rArr.begin()) + aStr.append(", "); + + if (*i) + aStr.append(lcl_dbg_out(**i)); + else + aStr.append("(null)"); + } + + aStr.append(" ]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const void * pVoid) +{ + char sBuffer[1024]; + + sprintf(sBuffer, "%p", pVoid); + + OUString aTmpStr(sBuffer, strlen(sBuffer), RTL_TEXTENCODING_ASCII_US); + + return dbg_out(aTmpStr); +} + +const char * dbg_out(const OUString & aStr) +{ + aDbgOutResult = OUStringToOString(aStr, RTL_TEXTENCODING_ASCII_US); + + if (bDbgOutStdErr) + fprintf(stderr, "%s", aDbgOutResult.getStr()); + + return aDbgOutResult.getStr(); +} + +static map<sal_uInt16,OUString> & GetItemWhichMap() +{ + static map<sal_uInt16,OUString> aItemWhichMap + { + { RES_CHRATR_CASEMAP , "CHRATR_CASEMAP" }, + { RES_CHRATR_CHARSETCOLOR , "CHRATR_CHARSETCOLOR" }, + { RES_CHRATR_COLOR , "CHRATR_COLOR" }, + { RES_CHRATR_CONTOUR , "CHRATR_CONTOUR" }, + { RES_CHRATR_CROSSEDOUT , "CHRATR_CROSSEDOUT" }, + { RES_CHRATR_ESCAPEMENT , "CHRATR_ESCAPEMENT" }, + { RES_CHRATR_FONT , "CHRATR_FONT" }, + { RES_CHRATR_FONTSIZE , "CHRATR_FONTSIZE" }, + { RES_CHRATR_KERNING , "CHRATR_KERNING" }, + { RES_CHRATR_LANGUAGE , "CHRATR_LANGUAGE" }, + { RES_CHRATR_POSTURE , "CHRATR_POSTURE" }, + { RES_CHRATR_SHADOWED , "CHRATR_SHADOWED" }, + { RES_CHRATR_UNDERLINE , "CHRATR_UNDERLINE" }, + { RES_CHRATR_OVERLINE , "CHRATR_OVERLINE" }, + { RES_CHRATR_WEIGHT , "CHRATR_WEIGHT" }, + { RES_CHRATR_WORDLINEMODE , "CHRATR_WORDLINEMODE" }, + { RES_CHRATR_AUTOKERN , "CHRATR_AUTOKERN" }, + { RES_CHRATR_BLINK , "CHRATR_BLINK" }, + { RES_CHRATR_NOHYPHEN , "CHRATR_NOHYPHEN" }, + { RES_CHRATR_BACKGROUND , "CHRATR_BACKGROUND" }, + { RES_CHRATR_HIGHLIGHT , "CHRATR_HIGHLIGHT" }, + { RES_CHRATR_CJK_FONT , "CHRATR_CJK_FONT" }, + { RES_CHRATR_CJK_FONTSIZE , "CHRATR_CJK_FONTSIZE" }, + { RES_CHRATR_CJK_LANGUAGE , "CHRATR_CJK_LANGUAGE" }, + { RES_CHRATR_CJK_POSTURE , "CHRATR_CJK_POSTURE" }, + { RES_CHRATR_CJK_WEIGHT , "CHRATR_CJK_WEIGHT" }, + { RES_CHRATR_CTL_FONT , "CHRATR_CTL_FONT" }, + { RES_CHRATR_CTL_FONTSIZE , "CHRATR_CTL_FONTSIZE" }, + { RES_CHRATR_CTL_LANGUAGE , "CHRATR_CTL_LANGUAGE" }, + { RES_CHRATR_CTL_POSTURE , "CHRATR_CTL_POSTURE" }, + { RES_CHRATR_CTL_WEIGHT , "CHRATR_CTL_WEIGHT" }, + { RES_CHRATR_ROTATE , "CHRATR_ROTATE" }, + { RES_CHRATR_EMPHASIS_MARK , "CHRATR_EMPHASIS_MARK" }, + { RES_CHRATR_TWO_LINES , "CHRATR_TWO_LINES" }, + { RES_CHRATR_SCALEW , "CHRATR_SCALEW" }, + { RES_CHRATR_RELIEF , "CHRATR_RELIEF" }, + { RES_CHRATR_HIDDEN , "CHRATR_HIDDEN" }, + { RES_CHRATR_BOX , "CHRATR_BOX" }, + { RES_CHRATR_SHADOW , "CHRATR_SHADOW" }, + { RES_TXTATR_AUTOFMT , "TXTATR_AUTOFMT" }, + { RES_TXTATR_INETFMT , "TXTATR_INETFMT" }, + { RES_TXTATR_REFMARK , "TXTATR_REFMARK" }, + { RES_TXTATR_TOXMARK , "TXTATR_TOXMARK" }, + { RES_TXTATR_CHARFMT , "TXTATR_CHARFMT" }, + { RES_TXTATR_INPUTFIELD , "RES_TXTATR_INPUTFIELD" }, + { RES_TXTATR_CJK_RUBY , "TXTATR_CJK_RUBY" }, + { RES_TXTATR_UNKNOWN_CONTAINER , "TXTATR_UNKNOWN_CONTAINER" }, + { RES_TXTATR_META , "TXTATR_META" }, + { RES_TXTATR_METAFIELD , "TXTATR_METAFIELD" }, + { RES_TXTATR_FIELD , "TXTATR_FIELD" }, + { RES_TXTATR_FLYCNT , "TXTATR_FLYCNT" }, + { RES_TXTATR_FTN , "TXTATR_FTN" }, + { RES_TXTATR_ANNOTATION , "TXTATR_ANNOTATION" }, + { RES_TXTATR_DUMMY3 , "TXTATR_DUMMY3" }, + { RES_TXTATR_DUMMY1 , "TXTATR_DUMMY1" }, + { RES_TXTATR_DUMMY2 , "TXTATR_DUMMY2" }, + { RES_PARATR_LINESPACING , "PARATR_LINESPACING" }, + { RES_PARATR_ADJUST , "PARATR_ADJUST" }, + { RES_PARATR_SPLIT , "PARATR_SPLIT" }, + { RES_PARATR_ORPHANS , "PARATR_ORPHANS" }, + { RES_PARATR_WIDOWS , "PARATR_WIDOWS" }, + { RES_PARATR_TABSTOP , "PARATR_TABSTOP" }, + { RES_PARATR_HYPHENZONE , "PARATR_HYPHENZONE" }, + { RES_PARATR_DROP , "PARATR_DROP" }, + { RES_PARATR_REGISTER , "PARATR_REGISTER" }, + { RES_PARATR_NUMRULE , "PARATR_NUMRULE" }, + { RES_PARATR_SCRIPTSPACE , "PARATR_SCRIPTSPACE" }, + { RES_PARATR_HANGINGPUNCTUATION , "PARATR_HANGINGPUNCTUATION" }, + { RES_PARATR_FORBIDDEN_RULES , "PARATR_FORBIDDEN_RULES" }, + { RES_PARATR_VERTALIGN , "PARATR_VERTALIGN" }, + { RES_PARATR_SNAPTOGRID , "PARATR_SNAPTOGRID" }, + { RES_PARATR_CONNECT_BORDER , "PARATR_CONNECT_BORDER" }, + { RES_FILL_ORDER , "FILL_ORDER" }, + { RES_FRM_SIZE , "FRM_SIZE" }, + { RES_PAPER_BIN , "PAPER_BIN" }, + { RES_LR_SPACE , "LR_SPACE" }, + { RES_UL_SPACE , "UL_SPACE" }, + { RES_PAGEDESC , "PAGEDESC" }, + { RES_BREAK , "BREAK" }, + { RES_CNTNT , "CNTNT" }, + { RES_HEADER , "HEADER" }, + { RES_FOOTER , "FOOTER" }, + { RES_PRINT , "PRINT" }, + { RES_OPAQUE , "OPAQUE" }, + { RES_PROTECT , "PROTECT" }, + { RES_SURROUND , "SURROUND" }, + { RES_VERT_ORIENT , "VERT_ORIENT" }, + { RES_HORI_ORIENT , "HORI_ORIENT" }, + { RES_ANCHOR , "ANCHOR" }, + { RES_BACKGROUND , "BACKGROUND" }, + { RES_BOX , "BOX" }, + { RES_SHADOW , "SHADOW" }, + { RES_FRMMACRO , "FRMMACRO" }, + { RES_COL , "COL" }, + { RES_KEEP , "KEEP" }, + { RES_URL , "URL" }, + { RES_EDIT_IN_READONLY , "EDIT_IN_READONLY" }, + { RES_LAYOUT_SPLIT , "LAYOUT_SPLIT" }, + { RES_CHAIN , "CHAIN" }, + { RES_TEXTGRID , "TEXTGRID" }, + { RES_LINENUMBER , "LINENUMBER" }, + { RES_FTN_AT_TXTEND , "FTN_AT_TXTEND" }, + { RES_END_AT_TXTEND , "END_AT_TXTEND" }, + { RES_COLUMNBALANCE , "COLUMNBALANCE" }, + { RES_FRAMEDIR , "FRAMEDIR" }, + { RES_HEADER_FOOTER_EAT_SPACING , "HEADER_FOOTER_EAT_SPACING" }, + { RES_ROW_SPLIT , "ROW_SPLIT" }, + { RES_GRFATR_MIRRORGRF , "GRFATR_MIRRORGRF" }, + { RES_GRFATR_CROPGRF , "GRFATR_CROPGRF" }, + { RES_GRFATR_ROTATION , "GRFATR_ROTATION" }, + { RES_GRFATR_LUMINANCE , "GRFATR_LUMINANCE" }, + { RES_GRFATR_CONTRAST , "GRFATR_CONTRAST" }, + { RES_GRFATR_CHANNELR , "GRFATR_CHANNELR" }, + { RES_GRFATR_CHANNELG , "GRFATR_CHANNELG" }, + { RES_GRFATR_CHANNELB , "GRFATR_CHANNELB" }, + { RES_GRFATR_GAMMA , "GRFATR_GAMMA" }, + { RES_GRFATR_INVERT , "GRFATR_INVERT" }, + { RES_GRFATR_TRANSPARENCY , "GRFATR_TRANSPARENCY" }, + { RES_GRFATR_DRAWMODE , "GRFATR_DRAWMODE" }, + { RES_BOXATR_FORMAT , "BOXATR_FORMAT" }, + { RES_BOXATR_FORMULA , "BOXATR_FORMULA" }, + { RES_BOXATR_VALUE , "BOXATR_VALUE" }, + }; + + return aItemWhichMap; +} + +static OUString lcl_dbg_out(const SfxPoolItem & rItem) +{ + OUString aStr("[ "); + + if (GetItemWhichMap().find(rItem.Which()) != GetItemWhichMap().end()) + aStr += GetItemWhichMap()[rItem.Which()]; + else + aStr += OUString::number(rItem.Which()); + + aStr += " ]"; + + return aStr; +} + +const char * dbg_out(const SfxPoolItem & rItem) +{ + return dbg_out(lcl_dbg_out(rItem)); +} + +const char * dbg_out(const SfxPoolItem * pItem) +{ + return dbg_out(pItem ? lcl_dbg_out(*pItem) : OUString("(nil)")); +} + +static OUString lcl_dbg_out(const SfxItemSet & rSet) +{ + SfxItemIter aIter(rSet); + bool bFirst = true; + OUStringBuffer aStr = "[ "; + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if (!bFirst) + aStr.append(", "); + + if (reinterpret_cast<sal_uIntPtr>(pItem) != SAL_MAX_SIZE) + aStr.append(lcl_dbg_out(*pItem)); + else + aStr.append("invalid"); + + bFirst = false; + } + + aStr.append(" ]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SfxItemSet & rSet) +{ + return dbg_out(lcl_dbg_out(rSet)); +} + +static OUString lcl_dbg_out(const SwTextAttr & rAttr) +{ + OUString aStr = + "[ " + + OUString::number(rAttr.GetStart()) + + "->" + + OUString::number(*rAttr.End()) + + " " + + lcl_dbg_out(rAttr.GetAttr()) + + " ]"; + + return aStr; +} + +const char * dbg_out(const SwTextAttr & rAttr) +{ + return dbg_out(lcl_dbg_out(rAttr)); +} + +static OUString lcl_dbg_out(const SwpHints & rHints) +{ + OUStringBuffer aStr("[ SwpHints\n"); + + for (size_t i = 0; i < rHints.Count(); ++i) + { + aStr.append(" "); + aStr.append(lcl_dbg_out(*rHints.Get(i))); + aStr.append("\n"); + } + + aStr.append("]\n"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SwpHints &rHints) +{ + return dbg_out(lcl_dbg_out(rHints)); +} + +static OUString lcl_dbg_out(const SwPosition & rPos) +{ + OUString aStr = + "( " + + OUString::number(rPos.nNode.GetIndex()) + + ", " + + OUString::number(rPos.nContent.GetIndex()) + + ": " + + OUString::number(reinterpret_cast<sal_IntPtr>(rPos.nContent.GetIdxReg()), 16) + + " )"; + + return aStr; +} + +const char * dbg_out(const SwPosition & rPos) +{ + return dbg_out(lcl_dbg_out(rPos)); +} + +static OUString lcl_dbg_out(const SwPaM & rPam) +{ + OUString aStr = + "[ Pt: " + + lcl_dbg_out(*rPam.GetPoint()); + + if (rPam.HasMark()) + { + aStr += ", Mk: " + lcl_dbg_out(*rPam.GetMark()); + } + + aStr += " ]"; + + return aStr; +} + +const char * dbg_out(const SwPaM & rPam) +{ + return dbg_out(lcl_dbg_out(rPam)); +} + +static OUString lcl_dbg_out(const SwNodeNum & ) +{ + return OUString();/*rNum.ToString();*/ +} + +const char * dbg_out(const SwNodeNum & rNum) +{ + return dbg_out(lcl_dbg_out(rNum)); +} + +static OUString lcl_dbg_out(const SwRect & rRect) +{ + OUString aResult = + "[ [" + + OUString::number(rRect.Left()) + + ", " + + OUString::number(rRect.Top()) + + "], [" + + OUString::number(rRect.Right()) + + ", " + + OUString::number(rRect.Bottom()) + + "] ]"; + + return aResult; +} + +const char * dbg_out(const SwRect & rRect) +{ + return dbg_out(lcl_dbg_out(rRect)); +} + +static OUString lcl_dbg_out(const SwFrameFormat & rFrameFormat) +{ + char sBuffer[256]; + sprintf(sBuffer, "%p", &rFrameFormat); + + OUString aResult = "[ " + + OUString(sBuffer, strlen(sBuffer), RTL_TEXTENCODING_ASCII_US) + + "(" + + rFrameFormat.GetName() + ")"; + + if (rFrameFormat.IsAuto()) + aResult += "*"; + + aResult += " ," + lcl_dbg_out(rFrameFormat.FindLayoutRect()) + " ]"; + + return aResult; +} + +const char * dbg_out(const SwFrameFormat & rFrameFormat) +{ + return dbg_out(lcl_dbg_out(rFrameFormat)); +} + +static OUString lcl_AnchoredFrames(const SwNode & rNode) +{ + OUStringBuffer aResult("["); + + const SwDoc * pDoc = rNode.GetDoc(); + if (pDoc) + { + const SwFrameFormats * pFrameFormats = pDoc->GetSpzFrameFormats(); + + if (pFrameFormats) + { + bool bFirst = true; + for (SwFrameFormats::const_iterator i(pFrameFormats->begin()); + i != pFrameFormats->end(); ++i) + { + const SwFormatAnchor & rAnchor = (*i)->GetAnchor(); + const SwPosition * pPos = rAnchor.GetContentAnchor(); + + if (pPos && &pPos->nNode.GetNode() == &rNode) + { + if (! bFirst) + aResult.append(", "); + + if (*i) + aResult.append(lcl_dbg_out(**i)); + bFirst = false; + } + } + } + } + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +static OUString lcl_dbg_out_NumType(sal_Int16 nType) +{ + OUString aTmpStr; + + switch (nType) + { + case SVX_NUM_NUMBER_NONE: + aTmpStr += " NONE"; + + break; + case SVX_NUM_CHARS_UPPER_LETTER: + aTmpStr += " CHARS_UPPER_LETTER"; + + break; + case SVX_NUM_CHARS_LOWER_LETTER: + aTmpStr += " CHARS_LOWER_LETTER"; + + break; + case SVX_NUM_ROMAN_UPPER: + aTmpStr += " ROMAN_UPPER"; + + break; + case SVX_NUM_ROMAN_LOWER: + aTmpStr += " ROMAN_LOWER"; + + break; + case SVX_NUM_ARABIC: + aTmpStr += " ARABIC"; + + break; + default: + aTmpStr += " ??"; + + break; + } + + return aTmpStr; +} + +static OUString lcl_dbg_out(const SwNode & rNode) +{ + char aBuffer[128]; + sprintf(aBuffer, "%p", &rNode); + + OUString aTmpStr = "<node " + "index=\"" + + OUString::number(rNode.GetIndex()) + + "\"" + " serial=\"" + + OUString::number(rNode.GetSerial()) + + "\"" + " type=\"" + + OUString::number(sal_Int32( rNode.GetNodeType() ) ) + + "\"" + " pointer=\"" + + OUString(aBuffer, strlen(aBuffer), RTL_TEXTENCODING_ASCII_US) + + "\">"; + + const SwTextNode * pTextNode = rNode.GetTextNode(); + + if (rNode.IsTextNode()) + { + const SfxItemSet * pAttrSet = pTextNode->GetpSwAttrSet(); + + aTmpStr += "<txt>" + (pTextNode->GetText().getLength() > 10 ? pTextNode->GetText().copy(0, 10) : pTextNode->GetText()) + "</txt>"; + + if (rNode.IsTableNode()) + aTmpStr += "<tbl/>"; + + aTmpStr += "<outlinelevel>" + OUString::number(pTextNode->GetAttrOutlineLevel()-1) + "</outlinelevel>"; + + const SwNumRule * pNumRule = pTextNode->GetNumRule(); + + if (pNumRule != nullptr) + { + aTmpStr += "<number>"; + if ( pTextNode->GetNum() ) + { + aTmpStr += lcl_dbg_out(*(pTextNode->GetNum())); + } + aTmpStr += "</number><rule>" + + pNumRule->GetName(); + + const SfxPoolItem * pItem = nullptr; + + if (pAttrSet && SfxItemState::SET == + pAttrSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem)) + { + aTmpStr += "(" + + static_cast<const SwNumRuleItem *>(pItem)->GetValue() + ")*"; + } + + const SwNumFormat * pNumFormat = nullptr; + aTmpStr += "</rule>"; + + if (pTextNode->GetActualListLevel() > 0) + pNumFormat = pNumRule->GetNumFormat( static_cast< sal_uInt16 >(pTextNode->GetActualListLevel()) ); + + if (pNumFormat) + { + aTmpStr += "<numformat>" + + lcl_dbg_out_NumType(pNumFormat->GetNumberingType()) + "</numformat>"; + } + } + + if (pTextNode->IsCountedInList()) + aTmpStr += "<counted/>"; + + SwFormatColl * pColl = pTextNode->GetFormatColl(); + + if (pColl) + { + aTmpStr += "<coll>" + pColl->GetName() + "("; + + SwTextFormatColl *pTextColl = static_cast<SwTextFormatColl*>(pColl); + if (pTextColl->IsAssignedToListLevelOfOutlineStyle()) + { + aTmpStr += OUString::number(pTextColl->GetAssignedOutlineStyleLevel()); + } + else + { + aTmpStr += OUString::number(-1); + } + + const SwNumRuleItem & rItem = + pColl->GetFormatAttr(RES_PARATR_NUMRULE); + const OUString& sNumruleName = rItem.GetValue(); + + if (!sNumruleName.isEmpty()) + { + aTmpStr += ", " + sNumruleName; + } + aTmpStr += ")" + "</coll>"; + } + + SwFormatColl * pCColl = pTextNode->GetCondFormatColl(); + + if (pCColl) + { + aTmpStr += "<ccoll>" + pCColl->GetName() + "</ccoll>"; + } + + aTmpStr += "<frms>" + lcl_AnchoredFrames(rNode) + "</frms>"; + + if (bDbgOutPrintAttrSet) + { + aTmpStr += "<attrs>" + lcl_dbg_out(pTextNode->GetSwAttrSet()) + "</attrs>"; + } + } + else if (rNode.IsStartNode()) + { + aTmpStr += "<start end=\""; + + const SwStartNode * pStartNode = dynamic_cast<const SwStartNode *> (&rNode); + if (pStartNode != nullptr) + aTmpStr += OUString::number(pStartNode->EndOfSectionNode()->GetIndex()); + + aTmpStr += "\"/>"; + } + else if (rNode.IsEndNode()) + aTmpStr += "<end/>"; + + aTmpStr += "</node>"; + + return aTmpStr; +} + +const char * dbg_out(const SwNode & rNode) +{ + return dbg_out(lcl_dbg_out(rNode)); +} + +const char * dbg_out(const SwNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +const char * dbg_out(const SwContentNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +const char * dbg_out(const SwTextNode * pNode) +{ + if (nullptr != pNode) + return dbg_out(*pNode); + else + return nullptr; +} + +static OUString lcl_dbg_out(const SwUndo & rUndo) +{ + return "[ " + OUString::number(static_cast<int>(rUndo.GetId())) + + ": " + rUndo.GetComment() + " ]"; +} + +const char * dbg_out(const SwUndo & rUndo) +{ + return dbg_out(lcl_dbg_out(rUndo)); +} + +static OUString lcl_dbg_out(SwOutlineNodes const & rNodes) +{ + OUStringBuffer aStr("[\n"); + + for (size_t i = 0; i < rNodes.size(); i++) + { + aStr.append(lcl_dbg_out(*rNodes[i])); + aStr.append("\n"); + } + + aStr.append("]\n"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out( SwOutlineNodes const & rNodes) +{ + return dbg_out(lcl_dbg_out(rNodes)); +} + +static OUString lcl_dbg_out(const SvxNumberFormat & rFormat) +{ + OUString aResult = lcl_dbg_out_NumType(rFormat.GetNumberingType()); + return aResult; +} + +static OUString lcl_dbg_out(const SwNumRule & rRule) +{ + OUStringBuffer aResult("[ "); + + aResult.append(rRule.GetName()); + aResult.append(" ["); + + for (sal_uInt8 n = 0; n < MAXLEVEL; n++) + { + if (n > 0) + aResult.append(", "); + + aResult.append(lcl_dbg_out(rRule.Get(n))); + } + + aResult.append("]"); + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +const char * dbg_out(const SwNumRule & rRule) +{ + return dbg_out(lcl_dbg_out(rRule)); +} + +static OUString lcl_dbg_out(const SwTextFormatColl & rFormat) +{ + return rFormat.GetName() + "(" + + OUString::number(rFormat.GetAttrOutlineLevel()) + ")"; +} + +const char * dbg_out(const SwTextFormatColl & rFormat) +{ + return dbg_out(lcl_dbg_out(rFormat)); +} + +static OUString lcl_dbg_out(const SwFrameFormats & rFrameFormats) +{ + return lcl_dbg_out_SvPtrArr<SwFrameFormats>(rFrameFormats); +} + +const char * dbg_out(const SwFrameFormats & rFrameFormats) +{ + return dbg_out(lcl_dbg_out(rFrameFormats)); +} + +static OUString lcl_dbg_out(const SwNumRuleTable & rTable) +{ + OUStringBuffer aResult("["); + + for (size_t n = 0; n < rTable.size(); n++) + { + if (n > 0) + aResult.append(", "); + + aResult.append(rTable[n]->GetName()); + + char sBuffer[256]; + sprintf(sBuffer, "(%p)", rTable[n]); + aResult.appendAscii(sBuffer); + } + + aResult.append("]"); + + return aResult.makeStringAndClear(); +} + +const char * dbg_out(const SwNumRuleTable & rTable) +{ + return dbg_out(lcl_dbg_out(rTable)); +} + +static OUString lcl_TokenType2Str(FormTokenType nType) +{ + switch(nType) + { + case TOKEN_ENTRY_NO: + return "NO"; + case TOKEN_ENTRY_TEXT: + return "ENTRY_TEXT"; + case TOKEN_ENTRY: + return "ENTRY"; + case TOKEN_TAB_STOP: + return "TAB_STOP"; + case TOKEN_TEXT: + return "TOKEN_TEXT"; + case TOKEN_PAGE_NUMS: + return "NUMS"; + case TOKEN_CHAPTER_INFO: + return "CHAPTER_INFO"; + case TOKEN_LINK_START: + return "LINK_START"; + case TOKEN_LINK_END: + return "LINK_END"; + case TOKEN_AUTHORITY: + return "AUTHORITY"; + case TOKEN_END: + return "END"; + default: + OSL_FAIL("should not be reached"); + return "??"; + } +} + +static OUString lcl_dbg_out(const SwFormToken & rToken) +{ + return rToken.GetString(); +} + +const char * dbg_out(const SwFormToken & rToken) +{ + return dbg_out(lcl_dbg_out(rToken)); +} + +static OUString lcl_dbg_out(const SwFormTokens & rTokens) +{ + OUStringBuffer aStr("["); + + SwFormTokens::const_iterator aIt; + + for (aIt = rTokens.begin(); aIt != rTokens.end(); ++aIt) + { + if (aIt != rTokens.begin()) + aStr.append(", "); + + aStr.append(lcl_TokenType2Str(aIt->eTokenType)); + aStr.append(": "); + aStr.append(lcl_dbg_out(*aIt)); + } + + aStr.append("]"); + + return aStr.makeStringAndClear(); +} + +const char * dbg_out(const SwFormTokens & rTokens) +{ + return dbg_out(lcl_dbg_out(rTokens)); +} + +static OUString lcl_dbg_out(const SwNodeRange & rRange) +{ + OUString aStr = + "[" + + lcl_dbg_out(SwPosition(rRange.aStart)) + + ", " + + lcl_dbg_out(SwPosition(rRange.aEnd)) + + "]"; + + return aStr; +} + +const char * dbg_out(const SwNodeRange & rRange) +{ + return dbg_out(lcl_dbg_out(rRange)); +} + +#endif // DEBUG + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doc.cxx b/sw/source/core/doc/doc.cxx new file mode 100644 index 000000000..b850b2c8a --- /dev/null +++ b/sw/source/core/doc/doc.cxx @@ -0,0 +1,1830 @@ +/* -*- 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_features.h> + +#include <doc.hxx> +#include <com/sun/star/script/vba/XVBAEventProcessor.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <DocumentFieldsManager.hxx> +#include <DocumentSettingManager.hxx> +#include <DocumentDrawModelManager.hxx> +#include <DocumentTimerManager.hxx> +#include <DocumentDeviceManager.hxx> +#include <DocumentChartDataProviderManager.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <DocumentListItemsManager.hxx> +#include <DocumentListsManager.hxx> +#include <DocumentOutlineNodesManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <DocumentRedlineManager.hxx> +#include <DocumentStatisticsManager.hxx> +#include <DocumentStateManager.hxx> +#include <DocumentStylePoolManager.hxx> +#include <DocumentLayoutManager.hxx> +#include <DocumentExternalDataManager.hxx> +#include <UndoManager.hxx> +#include <dbmgr.hxx> +#include <hintids.hxx> + +#include <comphelper/random.hxx> +#include <tools/multisel.hxx> +#include <rtl/ustring.hxx> +#include <svl/poolitem.hxx> +#include <unotools/syslocale.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/pbinitem.hxx> +#include <unotools/localedatawrapper.hxx> + +#include <swatrset.hxx> +#include <swmodule.hxx> +#include <fmtrfmrk.hxx> +#include <fmtinfmt.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <dbfld.hxx> +#include <txtinet.hxx> +#include <txtrfmrk.hxx> +#include <frmatr.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swundo.hxx> +#include <UndoCore.hxx> +#include <UndoTable.hxx> +#include <pagedesc.hxx> +#include <doctxm.hxx> +#include <poolfmt.hxx> +#include <SwGrammarMarkUp.hxx> +#include <scriptinfo.hxx> +#include <mdiexp.hxx> +#include <docary.hxx> +#include <printdata.hxx> +#include <strings.hrc> +#include <SwUndoTOXChange.hxx> +#include <unocrsr.hxx> +#include <docfld.hxx> +#include <docufld.hxx> +#include <viewsh.hxx> +#include <shellres.hxx> +#include <txtfrm.hxx> +#include <attrhint.hxx> + +#include <vector> +#include <map> +#include <osl/diagnose.h> +#include <osl/interlck.h> +#include <vbahelper/vbaaccesshelper.hxx> +#include <calbck.hxx> +#include <crsrsh.hxx> + +/* @@@MAINTAINABILITY-HORROR@@@ + Probably unwanted dependency on SwDocShell +*/ +#include <docsh.hxx> + +using namespace ::com::sun::star; + +sal_Int32 SwDoc::acquire() +{ + assert(mReferenceCount >= 0); + return osl_atomic_increment(&mReferenceCount); +} + +sal_Int32 SwDoc::release() +{ + assert(mReferenceCount >= 1); + auto x = osl_atomic_decrement(&mReferenceCount); + if (x == 0) + delete this; + return x; +} + +sal_Int32 SwDoc::getReferenceCount() const +{ + assert(mReferenceCount >= 0); + return mReferenceCount; +} + +::sw::MetaFieldManager & SwDoc::GetMetaFieldManager() +{ + return *m_pMetaFieldManager; +} + +::sw::UndoManager & SwDoc::GetUndoManager() +{ + return *m_pUndoManager; +} + +::sw::UndoManager const & SwDoc::GetUndoManager() const +{ + return *m_pUndoManager; +} + + +IDocumentUndoRedo & SwDoc::GetIDocumentUndoRedo() +{ + return *m_pUndoManager; +} + +IDocumentUndoRedo const & SwDoc::GetIDocumentUndoRedo() const +{ + return *m_pUndoManager; +} + +/* IDocumentDrawModelAccess */ +IDocumentDrawModelAccess const & SwDoc::getIDocumentDrawModelAccess() const +{ + return GetDocumentDrawModelManager(); +} + +IDocumentDrawModelAccess & SwDoc::getIDocumentDrawModelAccess() +{ + return GetDocumentDrawModelManager(); +} + +::sw::DocumentDrawModelManager const & SwDoc::GetDocumentDrawModelManager() const +{ + return *m_pDocumentDrawModelManager; +} + +::sw::DocumentDrawModelManager & SwDoc::GetDocumentDrawModelManager() +{ + return *m_pDocumentDrawModelManager; +} + +/* IDocumentSettingAccess */ +IDocumentSettingAccess const & SwDoc::getIDocumentSettingAccess() const +{ + return GetDocumentSettingManager(); +} + +IDocumentSettingAccess & SwDoc::getIDocumentSettingAccess() +{ + return GetDocumentSettingManager(); +} + +::sw::DocumentSettingManager & SwDoc::GetDocumentSettingManager() +{ + return *m_pDocumentSettingManager; +} + +::sw::DocumentSettingManager const & SwDoc::GetDocumentSettingManager() const +{ + return *m_pDocumentSettingManager; +} + +sal_uInt32 SwDoc::getRsid() const +{ + return mnRsid; +} + +void SwDoc::setRsid( sal_uInt32 nVal ) +{ + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + sal_uInt32 nIncrease = 0; + if (!bHack) + { + // Increase the rsid with a random number smaller than 2^17. This way we + // expect to be able to edit a document 2^12 times before rsid overflows. + // start from 1 to ensure the new rsid is not the same + nIncrease = comphelper::rng::uniform_uint_distribution(1, (1 << 17) - 1); + } + mnRsid = nVal + nIncrease; +} + +sal_uInt32 SwDoc::getRsidRoot() const +{ + return mnRsidRoot; +} + +void SwDoc::setRsidRoot( sal_uInt32 nVal ) +{ + mnRsidRoot = nVal; +} + +/* IDocumentChartDataProviderAccess */ +IDocumentChartDataProviderAccess const & SwDoc::getIDocumentChartDataProviderAccess() const +{ + return *m_pDocumentChartDataProviderManager; +} + +IDocumentChartDataProviderAccess & SwDoc::getIDocumentChartDataProviderAccess() +{ + return *m_pDocumentChartDataProviderManager; +} + +// IDocumentDeviceAccess +IDocumentDeviceAccess const & SwDoc::getIDocumentDeviceAccess() const +{ + return *m_pDeviceAccess; +} + +IDocumentDeviceAccess & SwDoc::getIDocumentDeviceAccess() +{ + return *m_pDeviceAccess; +} + +//IDocumentTimerAccess +IDocumentTimerAccess const & SwDoc::getIDocumentTimerAccess() const +{ + return *m_pDocumentTimerManager; +} + +IDocumentTimerAccess & SwDoc::getIDocumentTimerAccess() +{ + return *m_pDocumentTimerManager; +} + +// IDocumentLinksAdministration +IDocumentLinksAdministration const & SwDoc::getIDocumentLinksAdministration() const +{ + return *m_pDocumentLinksAdministrationManager; +} + +IDocumentLinksAdministration & SwDoc::getIDocumentLinksAdministration() +{ + return *m_pDocumentLinksAdministrationManager; +} + +::sw::DocumentLinksAdministrationManager const & SwDoc::GetDocumentLinksAdministrationManager() const +{ + return *m_pDocumentLinksAdministrationManager; +} + +::sw::DocumentLinksAdministrationManager & SwDoc::GetDocumentLinksAdministrationManager() +{ + return *m_pDocumentLinksAdministrationManager; +} + +//IDocumentListItems +IDocumentListItems const & SwDoc::getIDocumentListItems() const +{ + return *m_pDocumentListItemsManager; +} + +//IDocumentListItems +IDocumentListItems & SwDoc::getIDocumentListItems() +{ + return *m_pDocumentListItemsManager; +} + +//IDocumentListsAccess +IDocumentListsAccess const & SwDoc::getIDocumentListsAccess() const +{ + return *m_pDocumentListsManager; +} + +IDocumentListsAccess & SwDoc::getIDocumentListsAccess() +{ + return *m_pDocumentListsManager; +} + +//IDocumentOutlinesNodes +IDocumentOutlineNodes const & SwDoc::getIDocumentOutlineNodes() const +{ + return *m_pDocumentOutlineNodesManager; +} + +IDocumentOutlineNodes & SwDoc::getIDocumentOutlineNodes() +{ + return *m_pDocumentOutlineNodesManager; +} + +//IDocumentContentOperations +IDocumentContentOperations const & SwDoc::getIDocumentContentOperations() const +{ + return *m_pDocumentContentOperationsManager; +} + +IDocumentContentOperations & SwDoc::getIDocumentContentOperations() +{ + return *m_pDocumentContentOperationsManager; +} + +::sw::DocumentContentOperationsManager const & SwDoc::GetDocumentContentOperationsManager() const +{ + return *m_pDocumentContentOperationsManager; +} +::sw::DocumentContentOperationsManager & SwDoc::GetDocumentContentOperationsManager() +{ + return *m_pDocumentContentOperationsManager; +} + +//IDocumentRedlineAccess +IDocumentRedlineAccess const & SwDoc::getIDocumentRedlineAccess() const +{ + return *m_pDocumentRedlineManager; +} + +IDocumentRedlineAccess& SwDoc::getIDocumentRedlineAccess() +{ + return *m_pDocumentRedlineManager; +} + +::sw::DocumentRedlineManager const & SwDoc::GetDocumentRedlineManager() const +{ + return *m_pDocumentRedlineManager; +} + +::sw::DocumentRedlineManager& SwDoc::GetDocumentRedlineManager() +{ + return *m_pDocumentRedlineManager; +} + +//IDocumentFieldsAccess + +IDocumentFieldsAccess const & SwDoc::getIDocumentFieldsAccess() const +{ + return *m_pDocumentFieldsManager; +} + +IDocumentFieldsAccess & SwDoc::getIDocumentFieldsAccess() +{ + return *m_pDocumentFieldsManager; +} + +::sw::DocumentFieldsManager & SwDoc::GetDocumentFieldsManager() +{ + return *m_pDocumentFieldsManager; +} + +//IDocumentStatistics +IDocumentStatistics const & SwDoc::getIDocumentStatistics() const +{ + return *m_pDocumentStatisticsManager; +} + +IDocumentStatistics & SwDoc::getIDocumentStatistics() +{ + return *m_pDocumentStatisticsManager; +} + +::sw::DocumentStatisticsManager const & SwDoc::GetDocumentStatisticsManager() const +{ + return *m_pDocumentStatisticsManager; +} + +::sw::DocumentStatisticsManager & SwDoc::GetDocumentStatisticsManager() +{ + return *m_pDocumentStatisticsManager; +} + +//IDocumentState +IDocumentState const & SwDoc::getIDocumentState() const +{ + return *m_pDocumentStateManager; +} + +IDocumentState & SwDoc::getIDocumentState() +{ + return *m_pDocumentStateManager; +} + +//IDocumentLayoutAccess +IDocumentLayoutAccess const & SwDoc::getIDocumentLayoutAccess() const +{ + return *m_pDocumentLayoutManager; +} + +IDocumentLayoutAccess & SwDoc::getIDocumentLayoutAccess() +{ + return *m_pDocumentLayoutManager; +} + +::sw::DocumentLayoutManager const & SwDoc::GetDocumentLayoutManager() const +{ + return *m_pDocumentLayoutManager; +} + +::sw::DocumentLayoutManager & SwDoc::GetDocumentLayoutManager() +{ + return *m_pDocumentLayoutManager; +} + +//IDocumentStylePoolAccess +IDocumentStylePoolAccess const & SwDoc::getIDocumentStylePoolAccess() const +{ + return *m_pDocumentStylePoolManager; +} + +IDocumentStylePoolAccess & SwDoc::getIDocumentStylePoolAccess() +{ + return *m_pDocumentStylePoolManager; +} + +//IDocumentExternalData +IDocumentExternalData const & SwDoc::getIDocumentExternalData() const +{ + return *m_pDocumentExternalDataManager; +} + +IDocumentExternalData & SwDoc::getIDocumentExternalData() +{ + return *m_pDocumentExternalDataManager; +} + +/* Implementations the next Interface here */ + +/* + * Document editing (Doc-SS) to fill the document + * by the RTF parser and for the EditShell. + */ +void SwDoc::ChgDBData(const SwDBData& rNewData) +{ + if( rNewData != maDBData ) + { + maDBData = rNewData; + getIDocumentState().SetModified(); + if (m_pDBManager) + m_pDBManager->CommitLastRegistrations(); + } + getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DatabaseName)->UpdateFields(); +} + +namespace { + +struct PostItField_ : public SetGetExpField +{ + PostItField_( const SwNodeIndex& rNdIdx, const SwTextField* pField ) + : SetGetExpField( rNdIdx, pField, nullptr ) {} + + sal_uInt16 GetPageNo( const StringRangeEnumerator &rRangeEnum, + const std::set< sal_Int32 > &rPossiblePages, + sal_uInt16& rVirtPgNo, sal_uInt16& rLineNo ); + + const SwPostItField* GetPostIt() const + { + return static_cast<const SwPostItField*>( GetTextField()->GetFormatField().GetField() ); + } +}; + +} + +sal_uInt16 PostItField_::GetPageNo( + const StringRangeEnumerator &rRangeEnum, + const std::set< sal_Int32 > &rPossiblePages, + /* out */ sal_uInt16& rVirtPgNo, /* out */ sal_uInt16& rLineNo ) +{ + //Problem: If a PostItField is contained in a Node that is represented + //by more than one layout instance, + //we have to decide whether it should be printed once or n-times. + //Probably only once. For the page number we don't select a random one, + //but the PostIt's first occurrence in the selected area. + rVirtPgNo = 0; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(GetTextField()->GetTextNode()); + for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + TextFrameIndex const nPos = pFrame->MapModelToView( + &GetTextField()->GetTextNode(), GetContent()); + if( pFrame->GetOffset() > nPos || + (pFrame->HasFollow() && pFrame->GetFollow()->GetOffset() <= nPos) ) + continue; + sal_uInt16 nPgNo = pFrame->GetPhyPageNum(); + if( rRangeEnum.hasValue( nPgNo, &rPossiblePages )) + { + rLineNo = static_cast<sal_uInt16>(pFrame->GetLineCount( nPos ) + + pFrame->GetAllLines() - pFrame->GetThisLines()); + rVirtPgNo = pFrame->GetVirtPageNum(); + return nPgNo; + } + } + return 0; +} + +bool sw_GetPostIts(IDocumentFieldsAccess const* pIDFA, SetGetExpFields* pSrtLst) +{ + SwFieldType* pFieldType = pIDFA->GetSysFieldType(SwFieldIds::Postit); + assert(pFieldType); + + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + if(pSrtLst) + for(auto pField: vFields) + { + auto pTextField = pField->GetTextField(); + SwNodeIndex aIdx(pTextField->GetTextNode()); + std::unique_ptr<PostItField_> pNew(new PostItField_(aIdx, pTextField)); + pSrtLst->insert(std::move(pNew)); + + } + return vFields.size()>0; +} + +static void lcl_FormatPostIt( + IDocumentContentOperations* pIDCO, + SwPaM& aPam, + const SwPostItField* pField, + bool bNewPage, bool bIsFirstPostIt, + sal_uInt16 nPageNo, sal_uInt16 nLineNo ) +{ + static char const sTmp[] = " : "; + + assert(SwViewShell::GetShellRes()); + + if (bNewPage) + { + pIDCO->InsertPoolItem( aPam, SvxFormatBreakItem( SvxBreak::PageAfter, RES_BREAK ) ); + pIDCO->SplitNode( *aPam.GetPoint(), false ); + } + else if (!bIsFirstPostIt) + { + // add an empty line between different notes + pIDCO->SplitNode( *aPam.GetPoint(), false ); + pIDCO->SplitNode( *aPam.GetPoint(), false ); + } + + OUString aStr( SwViewShell::GetShellRes()->aPostItPage ); + aStr += sTmp + + OUString::number( nPageNo ) + + " "; + if( nLineNo ) + { + aStr += SwViewShell::GetShellRes()->aPostItLine; + aStr += sTmp + + OUString::number( nLineNo ) + + " "; + } + aStr += SwViewShell::GetShellRes()->aPostItAuthor; + aStr += sTmp; + aStr += pField->GetPar1() + " "; + SvtSysLocale aSysLocale; + aStr += /*(LocaleDataWrapper&)*/aSysLocale.GetLocaleData().getDate( pField->GetDate() ); + if(pField->GetResolved()) + aStr += " " + SwResId(STR_RESOLVED); + pIDCO->InsertString( aPam, aStr ); + + pIDCO->SplitNode( *aPam.GetPoint(), false ); + aStr = pField->GetPar2(); +#if defined(_WIN32) + // Throw out all CR in Windows + aStr = aStr.replaceAll("\r", ""); +#endif + pIDCO->InsertString( aPam, aStr ); +} + +/// provide the paper tray to use according to the page style in use, +/// but do that only if the respective item is NOT just the default item +static sal_Int32 lcl_GetPaperBin( const SwPageFrame *pStartFrame ) +{ + sal_Int32 nRes = -1; + + const SwFrameFormat &rFormat = pStartFrame->GetPageDesc()->GetMaster(); + const SfxPoolItem *pItem = nullptr; + SfxItemState eState = rFormat.GetItemState( RES_PAPER_BIN, false, &pItem ); + const SvxPaperBinItem *pPaperBinItem = dynamic_cast< const SvxPaperBinItem * >(pItem); + if (eState > SfxItemState::DEFAULT && pPaperBinItem) + nRes = pPaperBinItem->GetValue(); + + return nRes; +} + +namespace +{ +// tdf#:114663 Translates a range string from user input (with page numbering possibly not +// taking blank pages into account) to equivalent string which references physical page numbers. +// rUIPages2PhyPagesMap must contain a contiguous sequence of UI page numbers +OUString UIPages2PhyPages(const OUString& rUIPageRange, const std::map< sal_Int32, sal_Int32 >& rUIPages2PhyPagesMap) +{ + if (rUIPages2PhyPagesMap.empty()) + return OUString(); + auto iMin = rUIPages2PhyPagesMap.begin(); + const sal_Int32 nUIPageMin = iMin->first, nPhyPageMin = iMin->second; + auto iMax = rUIPages2PhyPagesMap.rbegin(); + const sal_Int32 nUIPageMax = iMax->first, nPhyPageMax = iMax->second; + OUStringBuffer aOut(rUIPageRange.getLength()); + OUStringBuffer aNumber(16); + const sal_Unicode* pInput = rUIPageRange.getStr(); + while (*pInput) + { + while (*pInput >= '0' && *pInput <= '9') + aNumber.append(*pInput++); + if (!aNumber.isEmpty()) + { + sal_Int32 nNumber = aNumber.makeStringAndClear().toInt32(); + if (nNumber < nUIPageMin) + nNumber = nPhyPageMin-1; + else if (nNumber > nUIPageMax) + nNumber = nPhyPageMax+1; + else + nNumber = rUIPages2PhyPagesMap.at(nNumber); + aOut.append(nNumber); + } + + while (*pInput && (*pInput < '0' || *pInput > '9')) + aOut.append(*pInput++); + } + + return aOut.makeStringAndClear(); +} +} + +// tdf#52316 remove blank pages from page count and actual page number +void SwDoc::CalculateNonBlankPages( + const SwRootFrame& rLayout, + sal_uInt16& nDocPageCount, + sal_uInt16& nActualPage) +{ + sal_uInt16 nDocPageCountWithBlank = nDocPageCount; + sal_uInt16 nActualPageWithBlank = nActualPage; + sal_uInt16 nPageNum = 1; + const SwPageFrame *pStPage = dynamic_cast<const SwPageFrame*>( rLayout.Lower() ); + while (pStPage && nPageNum <= nDocPageCountWithBlank) + { + if ( pStPage->getFrameArea().Height() == 0 ) + { + --nDocPageCount; + if (nPageNum <= nActualPageWithBlank) + --nActualPage; + } + ++nPageNum; + pStPage = static_cast<const SwPageFrame*>(pStPage->GetNext()); + } +} + +void SwDoc::CalculatePagesForPrinting( + const SwRootFrame& rLayout, + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + bool bIsPDFExport, + sal_Int32 nDocPageCount ) +{ + const sal_Int64 nContent = rOptions.getIntValue( "PrintContent", 0 ); + const bool bPrintSelection = nContent == 2; + + // properties to take into account when calculating the set of pages + // (PDF export UI does not allow for selecting left or right pages only) + bool bPrintLeftPages = bIsPDFExport || rOptions.IsPrintLeftPages(); + bool bPrintRightPages = bIsPDFExport || rOptions.IsPrintRightPages(); + // #i103700# printing selections should not allow for automatic inserting empty pages + bool bPrintEmptyPages = !bPrintSelection && rOptions.IsPrintEmptyPages( bIsPDFExport ); + + std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); + std::set< sal_Int32 > &rValidPages = rData.GetValidPagesSet(); + // Map page numbers from user input (possibly ignoring blanks) to physical page numbers + std::map< sal_Int32, sal_Int32 > aUIPages2PhyPagesMap; + rValidPages.clear(); + + sal_Int32 nPageNum = 1, nUIPageNum = 1; + const SwPageFrame *pStPage = dynamic_cast<const SwPageFrame*>( rLayout.Lower() ); + while (pStPage && nPageNum <= nDocPageCount) + { + const bool bNonEmptyPage = pStPage->getFrameArea().Height() != 0; + const bool bPrintThisPage = + ( (bPrintRightPages && pStPage->OnRightPage()) || + (bPrintLeftPages && !pStPage->OnRightPage()) ) && + ( bPrintEmptyPages || bNonEmptyPage ); + + if (bPrintThisPage) + { + rValidPages.insert( nPageNum ); + rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage ); + } + + if ( bPrintEmptyPages || bNonEmptyPage ) + { + aUIPages2PhyPagesMap[nUIPageNum++] = nPageNum; + } + ++nPageNum; + pStPage = static_cast<const SwPageFrame*>(pStPage->GetNext()); + } + + // now that we have identified the valid pages for printing according + // to the print settings we need to get the PageRange to use and + // use both results to get the actual pages to be printed + // (post-it settings need to be taken into account later on!) + + // get PageRange value to use + OUString aPageRange; + // #i116085# - adjusting fix for i113919 + if ( !bIsPDFExport ) + { + // PageContent : + // 0 -> print all pages (default if aPageRange is empty) + // 1 -> print range according to PageRange + // 2 -> print selection + if (1 == nContent) + aPageRange = rOptions.getStringValue( "PageRange" ); + + if (2 == nContent) + { + // note that printing selections is actually implemented by copying + // the selection to a new temporary document and printing all of that one. + // Thus for Writer "PrintContent" must never be 2. + // See SwXTextDocument::GetRenderDoc for evaluating if a selection is to be + // printed and for creating the temporary document. + } + + // please note + } + if (aPageRange.isEmpty()) // empty string -> print all + { + // set page range to print to 'all pages' + aPageRange = OUString::number( 1 ) + "-" + OUString::number( nDocPageCount ); + } + else + { + // Convert page numbers from user input to physical page numbers + aPageRange = UIPages2PhyPages(aPageRange, aUIPages2PhyPagesMap); + } + rData.SetPageRange( aPageRange ); + + // get vector of pages to print according to PageRange and valid pages set from above + // (result may be an empty vector, for example if the range string is not correct) + // If excluding empty pages, allow range to specify range of printable pages + StringRangeEnumerator::getRangesFromString( aPageRange, rData.GetPagesToPrint(), + 1, nDocPageCount, 0, &rData.GetValidPagesSet() ); +} + +void SwDoc::UpdatePagesForPrintingWithPostItData( + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + sal_Int32 nDocPageCount ) +{ + + SwPostItMode nPostItMode = static_cast<SwPostItMode>( rOptions.getIntValue( "PrintAnnotationMode", 0 ) ); + assert((nPostItMode == SwPostItMode::NONE || rData.HasPostItData()) + && "print post-its without post-it data?"); + const SetGetExpFields::size_type nPostItCount = + rData.HasPostItData() ? rData.m_pPostItFields->size() : 0; + if (nPostItMode == SwPostItMode::NONE || nPostItCount <= 0) + return; + + SET_CURR_SHELL( rData.m_pPostItShell.get() ); + + // clear document and move to end of it + SwDoc & rPostItDoc(*rData.m_pPostItShell->GetDoc()); + SwPaM aPam(rPostItDoc.GetNodes().GetEndOfContent()); + aPam.Move( fnMoveBackward, GoInDoc ); + aPam.SetMark(); + aPam.Move( fnMoveForward, GoInDoc ); + rPostItDoc.getIDocumentContentOperations().DeleteRange( aPam ); + + const StringRangeEnumerator aRangeEnum( rData.GetPageRange(), 1, nDocPageCount, 0 ); + + // For mode SwPostItMode::EndPage: + // maps a physical page number to the page number in post-it document that holds + // the first post-it for that physical page . Needed to relate the correct start frames + // from the post-it doc to the physical page of the document + std::map< sal_Int32, sal_Int32 > aPostItLastStartPageNum; + + // add all post-its on valid pages within the page range to the + // temporary post-it document. + // Since the array of post-it fields is sorted by page and line number we will + // already get them in the correct order + sal_uInt16 nVirtPg = 0, nLineNo = 0, nLastPageNum = 0, nPhyPageNum = 0; + bool bIsFirstPostIt = true; + for (SetGetExpFields::size_type i = 0; i < nPostItCount; ++i) + { + PostItField_& rPostIt = static_cast<PostItField_&>(*(*rData.m_pPostItFields)[ i ]); + nLastPageNum = nPhyPageNum; + nPhyPageNum = rPostIt.GetPageNo( + aRangeEnum, rData.GetValidPagesSet(), nVirtPg, nLineNo ); + if (nPhyPageNum) + { + // need to insert a page break? + // In SwPostItMode::EndPage mode for each document page the following + // post-it page needs to start on a new page + const bool bNewPage = nPostItMode == SwPostItMode::EndPage && + !bIsFirstPostIt && nPhyPageNum != nLastPageNum; + + lcl_FormatPostIt( &rData.m_pPostItShell->GetDoc()->getIDocumentContentOperations(), aPam, + rPostIt.GetPostIt(), bNewPage, bIsFirstPostIt, nVirtPg, nLineNo ); + bIsFirstPostIt = false; + + if (nPostItMode == SwPostItMode::EndPage) + { + // get the correct number of current pages for the post-it document + rData.m_pPostItShell->CalcLayout(); + const sal_Int32 nPages = rData.m_pPostItShell->GetPageCount(); + aPostItLastStartPageNum[ nPhyPageNum ] = nPages; + } + } + } + + // format post-it doc to get correct number of pages + rData.m_pPostItShell->CalcLayout(); + + SwRootFrame* pPostItRoot = rData.m_pPostItShell->GetLayout(); + //tdf#103313 print dialog maxes out cpu as Idles never get to + //complete this postitshell's desire to complete formatting + pPostItRoot->ResetIdleFormat(); + + const sal_Int32 nPostItDocPageCount = rData.m_pPostItShell->GetPageCount(); + + if (nPostItMode == SwPostItMode::Only || nPostItMode == SwPostItMode::EndDoc) + { + // now add those post-it pages to the vector of pages to print + // or replace them if only post-its should be printed + + if (nPostItMode == SwPostItMode::Only) + { + // no document page to be printed + rData.GetPagesToPrint().clear(); + } + + // now we just need to add the post-it pages to be printed to the + // end of the vector of pages to print + sal_Int32 nPageNum = 0; + const SwPageFrame * pPageFrame = static_cast<SwPageFrame*>(pPostItRoot->Lower()); + while( pPageFrame && nPageNum < nPostItDocPageCount ) + { + ++nPageNum; + // negative page number indicates page is from the post-it doc + rData.GetPagesToPrint().push_back( -nPageNum ); + pPageFrame = static_cast<const SwPageFrame*>(pPageFrame->GetNext()); + } + OSL_ENSURE( nPageNum == nPostItDocPageCount, "unexpected number of pages" ); + } + else if (nPostItMode == SwPostItMode::EndPage) + { + // the next step is to find all the pages from the post-it + // document that should be printed for a given physical page + // of the document + + std::vector< sal_Int32 > aTmpPagesToPrint; + sal_Int32 nLastPostItPage(0); + const size_t nNum = rData.GetPagesToPrint().size(); + for (size_t i = 0 ; i < nNum; ++i) + { + // add the physical page to print from the document + const sal_Int32 nPhysPage = rData.GetPagesToPrint()[i]; + aTmpPagesToPrint.push_back( nPhysPage ); + + // add the post-it document pages to print, i.e those + // post-it pages that have the data for the above physical page + std::map<sal_Int32, sal_Int32>::const_iterator const iter( + aPostItLastStartPageNum.find(nPhysPage)); + if (iter != aPostItLastStartPageNum.end()) + { + for (sal_Int32 j = nLastPostItPage + 1; + j <= iter->second; ++j) + { + // negative page number indicates page is from the + aTmpPagesToPrint.push_back(-j); // post-it document + } + nLastPostItPage = iter->second; + } + } + + // finally we need to assign those vectors to the resulting ones. + // swapping the data should be more efficient than assigning since + // we won't need the temporary vectors anymore + rData.GetPagesToPrint().swap( aTmpPagesToPrint ); + } + +} + +void SwDoc::CalculatePagePairsForProspectPrinting( + const SwRootFrame& rLayout, + /* out */ SwRenderData &rData, + const SwPrintUIOptions &rOptions, + sal_Int32 nDocPageCount ) +{ + std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); + std::set< sal_Int32 > &rValidPagesSet = rData.GetValidPagesSet(); + std::vector< std::pair< sal_Int32, sal_Int32 > > &rPagePairs = rData.GetPagePairsForProspectPrinting(); + std::map< sal_Int32, const SwPageFrame * > validStartFrames; + + rPagePairs.clear(); + rValidPagesSet.clear(); + + OUString aPageRange; + // PageContent : + // 0 -> print all pages (default if aPageRange is empty) + // 1 -> print range according to PageRange + // 2 -> print selection + const sal_Int64 nContent = rOptions.getIntValue( "PrintContent", 0 ); + if (nContent == 1) + aPageRange = rOptions.getStringValue( "PageRange" ); + if (aPageRange.isEmpty()) // empty string -> print all + { + // set page range to print to 'all pages' + aPageRange = OUString::number( 1 ) + "-" + OUString::number( nDocPageCount ); + } + StringRangeEnumerator aRange( aPageRange, 1, nDocPageCount, 0 ); + + if ( aRange.size() <= 0) + return; + + const SwPageFrame *pStPage = dynamic_cast<const SwPageFrame*>( rLayout.Lower() ); + for ( sal_Int32 i = 1; pStPage && i < nDocPageCount; ++i ) + pStPage = static_cast<const SwPageFrame*>(pStPage->GetNext()); + if ( !pStPage ) // Then it was that + return; + + // currently for prospect printing all pages are valid to be printed + // thus we add them all to the respective map and set for later use + sal_Int32 nPageNum = 0; + const SwPageFrame *pPageFrame = dynamic_cast<const SwPageFrame*>( rLayout.Lower() ); + while( pPageFrame && nPageNum < nDocPageCount ) + { + ++nPageNum; + rValidPagesSet.insert( nPageNum ); + validStartFrames[ nPageNum ] = pPageFrame; + pPageFrame = static_cast<const SwPageFrame*>(pPageFrame->GetNext()); + + rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage ); + } + OSL_ENSURE( nPageNum == nDocPageCount, "unexpected number of pages" ); + + // properties to take into account when calculating the set of pages + // Note: here bPrintLeftPages and bPrintRightPages refer to the (virtual) resulting pages + // of the prospect! + bool bPrintLeftPages = rOptions.IsPrintLeftPages(); + bool bPrintRightPages = rOptions.IsPrintRightPages(); + bool bPrintProspectRTL = rOptions.getIntValue( "PrintProspectRTL", 0 ) != 0; + + // get pages for prospect printing according to the 'PageRange' + // (duplicates and any order allowed!) + std::vector< sal_Int32 > aPagesToPrint; + StringRangeEnumerator::getRangesFromString( + aPageRange, aPagesToPrint, 1, nDocPageCount, 0 ); + + if (aPagesToPrint.empty()) + return; + + // now fill the vector for calculating the page pairs with the start frames + // from the above obtained vector + std::vector< const SwPageFrame * > aVec; + for (sal_Int32 nPage : aPagesToPrint) + { + const SwPageFrame *pFrame = validStartFrames[ nPage ]; + aVec.push_back( pFrame ); + } + + // just one page is special ... + if ( 1 == aVec.size() ) + aVec.insert( aVec.begin() + 1, nullptr ); // insert a second empty page + else + { + // now extend the number of pages to fit a multiple of 4 + // (4 'normal' pages are needed for a single prospect paper + // with back and front) + while( aVec.size() & 3 ) + aVec.push_back( nullptr ); + } + + // make sure that all pages are in correct order + std::vector< const SwPageFrame * >::size_type nSPg = 0; + std::vector< const SwPageFrame * >::size_type nEPg = aVec.size(); + sal_Int32 nStep = 1; + if ( 0 == (nEPg & 1 )) // there are no uneven ones! + --nEPg; + + if ( !bPrintLeftPages ) + ++nStep; + else if ( !bPrintRightPages ) + { + ++nStep; + ++nSPg; + --nEPg; + } + + // the number of 'virtual' pages to be printed + sal_Int32 nCntPage = (( nEPg - nSPg ) / ( 2 * nStep )) + 1; + + for ( sal_Int32 nPrintCount = 0; nSPg < nEPg && + nPrintCount < nCntPage; ++nPrintCount ) + { + pStPage = aVec[ nSPg ]; + const SwPageFrame* pNxtPage = nEPg < aVec.size() ? aVec[ nEPg ] : nullptr; + + short nRtlOfs = bPrintProspectRTL ? 1 : 0; + if ( 0 == (( nSPg + nRtlOfs) & 1 ) ) // switch for odd number in LTR, even number in RTL + { + const SwPageFrame* pTmp = pStPage; + pStPage = pNxtPage; + pNxtPage = pTmp; + } + + sal_Int32 nFirst = -1, nSecond = -1; + for ( int nC = 0; nC < 2; ++nC ) + { + sal_Int32 nPage = -1; + if ( pStPage ) + nPage = pStPage->GetPhyPageNum(); + if (nC == 0) + nFirst = nPage; + else + nSecond = nPage; + + pStPage = pNxtPage; + } + rPagePairs.emplace_back(nFirst, nSecond ); + + nSPg = nSPg + nStep; + nEPg = nEPg - nStep; + } + OSL_ENSURE( size_t(nCntPage) == rPagePairs.size(), "size mismatch for number of page pairs" ); + + // luckily prospect printing does not make use of post-its so far, + // thus we are done here. +} + +/// @return the reference in the doc for the name +const SwFormatRefMark* SwDoc::GetRefMark( const OUString& rName ) const +{ + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pFormatRef = dynamic_cast<const SwFormatRefMark*>(pItem); + if(!pFormatRef) + continue; + + const SwTextRefMark* pTextRef = pFormatRef->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() && + rName == pFormatRef->GetRefName() ) + return pFormatRef; + } + return nullptr; +} + +/// @return the RefMark per index - for Uno +const SwFormatRefMark* SwDoc::GetRefMark( sal_uInt16 nIndex ) const +{ + const SwFormatRefMark* pRet = nullptr; + + sal_uInt32 nCount = 0; + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pRefMark = dynamic_cast<const SwFormatRefMark*>(pItem); + if( !pRefMark ) + continue; + const SwTextRefMark* pTextRef = pRefMark->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() ) + { + if(nCount == nIndex) + { + pRet = pRefMark; + break; + } + nCount++; + } + } + return pRet; +} + +/// @return the names of all set references in the Doc +//JP 24.06.96: If the array pointer is 0, then just return whether a RefMark is set in the Doc +// OS 25.06.96: From now on we always return the reference count +sal_uInt16 SwDoc::GetRefMarks( std::vector<OUString>* pNames ) const +{ + sal_uInt16 nCount = 0; + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)) + { + auto pRefMark = dynamic_cast<const SwFormatRefMark*>(pItem); + if( !pRefMark ) + continue; + const SwTextRefMark* pTextRef = pRefMark->GetTextRefMark(); + if( pTextRef && &pTextRef->GetTextNode().GetNodes() == &GetNodes() ) + { + if( pNames ) + { + OUString aTmp(pRefMark->GetRefName()); + pNames->insert(pNames->begin() + nCount, aTmp); + } + ++nCount; + } + } + + return nCount; +} + +static bool lcl_SpellAndGrammarAgain( const SwNodePtr& rpNd, void* pArgs ) +{ + SwTextNode *pTextNode = rpNd->GetTextNode(); + bool bOnlyWrong = *static_cast<sal_Bool*>(pArgs); + if( pTextNode ) + { + if( bOnlyWrong ) + { + if( pTextNode->GetWrong() && + pTextNode->GetWrong()->InvalidateWrong() ) + pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); + if( pTextNode->GetGrammarCheck() && + pTextNode->GetGrammarCheck()->InvalidateWrong() ) + pTextNode->SetGrammarCheckDirty( true ); + } + else + { + pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); + if( pTextNode->GetWrong() ) + pTextNode->GetWrong()->SetInvalid( 0, COMPLETE_STRING ); + pTextNode->SetGrammarCheckDirty( true ); + if( pTextNode->GetGrammarCheck() ) + pTextNode->GetGrammarCheck()->SetInvalid( 0, COMPLETE_STRING ); + } + } + return true; +} + +static bool lcl_CheckSmartTagsAgain( const SwNodePtr& rpNd, void* ) +{ + SwTextNode *pTextNode = rpNd->GetTextNode(); + if( pTextNode ) + { + pTextNode->SetSmartTagDirty( true ); + if( pTextNode->GetSmartTags() ) + { + pTextNode->SetSmartTags( nullptr ); + } + } + return true; +} + +/** + * Re-trigger spelling in the idle handler. + * + * @param bInvalid if <true>, the WrongLists in all nodes are invalidated + * and the SpellInvalid flag is set on all pages. + * @param bOnlyWrong controls whether only the areas with wrong words are + * checked or the whole area. + * @param bSmartTags ??? + */ +void SwDoc::SpellItAgainSam( bool bInvalid, bool bOnlyWrong, bool bSmartTags ) +{ + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = GetAllLayouts(); + assert(getIDocumentLayoutAccess().GetCurrentLayout() && "SpellAgain: Where's my RootFrame?"); + if( bInvalid ) + { + for ( auto aLayout : aAllLayouts ) + { + aLayout->AllInvalidateSmartTagsOrSpelling(bSmartTags); + aLayout->SetNeedGrammarCheck(true); + } + if ( bSmartTags ) + GetNodes().ForEach( lcl_CheckSmartTagsAgain, &bOnlyWrong ); + GetNodes().ForEach( lcl_SpellAndGrammarAgain, &bOnlyWrong ); + } + + for ( auto aLayout : aAllLayouts ) + aLayout->SetIdleFlags(); +} + +void SwDoc::InvalidateAutoCompleteFlag() +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( pTmpRoot ) + { + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = GetAllLayouts(); + for( auto aLayout : aAllLayouts ) + aLayout->AllInvalidateAutoCompleteWords(); + for( sal_uLong nNd = 1, nCnt = GetNodes().Count(); nNd < nCnt; ++nNd ) + { + SwTextNode* pTextNode = GetNodes()[ nNd ]->GetTextNode(); + if ( pTextNode ) pTextNode->SetAutoCompleteWordDirty( true ); + } + + for( auto aLayout : aAllLayouts ) + aLayout->SetIdleFlags(); + } +} + +const SwFormatINetFormat* SwDoc::FindINetAttr( const OUString& rName ) const +{ + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + auto pFormatItem = dynamic_cast<const SwFormatINetFormat*>(pItem); + if( !pFormatItem || pFormatItem->GetName() != rName ) + continue; + const SwTextINetFormat* pTextAttr = pFormatItem->GetTextINetFormat(); + if( !pTextAttr ) + continue; + const SwTextNode* pTextNd = pTextAttr->GetpTextNode(); + if( pTextNd && &pTextNd->GetNodes() == &GetNodes() ) + { + return pFormatItem; + } + } + return nullptr; +} + +void SwDoc::Summary( SwDoc* pExtDoc, sal_uInt8 nLevel, sal_uInt8 nPara, bool bImpress ) +{ + const SwOutlineNodes& rOutNds = GetNodes().GetOutLineNds(); + if( pExtDoc && !rOutNds.empty() ) + { + ::StartProgress( STR_STATSTR_SUMMARY, 0, rOutNds.size(), GetDocShell() ); + SwNodeIndex aEndOfDoc( pExtDoc->GetNodes().GetEndOfContent(), -1 ); + for( SwOutlineNodes::size_type i = 0; i < rOutNds.size(); ++i ) + { + ::SetProgressState( static_cast<long>(i), GetDocShell() ); + const sal_uLong nIndex = rOutNds[ i ]->GetIndex(); + + const int nLvl = GetNodes()[ nIndex ]->GetTextNode()->GetAttrOutlineLevel()-1; + if( nLvl > nLevel ) + continue; + long nEndOfs = 1; + sal_uInt8 nWish = nPara; + sal_uLong nNextOutNd = i + 1 < rOutNds.size() ? + rOutNds[ i + 1 ]->GetIndex() : GetNodes().Count(); + bool bKeep = false; + while( ( nWish || bKeep ) && nIndex + nEndOfs < nNextOutNd && + GetNodes()[ nIndex + nEndOfs ]->IsTextNode() ) + { + SwTextNode* pTextNode = GetNodes()[ nIndex+nEndOfs ]->GetTextNode(); + if (pTextNode->GetText().getLength() && nWish) + --nWish; + bKeep = pTextNode->GetSwAttrSet().GetKeep().GetValue(); + ++nEndOfs; + } + + SwNodeRange aRange( *rOutNds[ i ], 0, *rOutNds[ i ], nEndOfs ); + GetNodes().Copy_( aRange, aEndOfDoc ); + } + const SwTextFormatColls *pColl = pExtDoc->GetTextFormatColls(); + for( SwTextFormatColls::size_type i = 0; i < pColl->size(); ++i ) + (*pColl)[ i ]->ResetFormatAttr( RES_PAGEDESC, RES_BREAK ); + SwNodeIndex aIndx( pExtDoc->GetNodes().GetEndOfExtras() ); + ++aEndOfDoc; + while( aIndx < aEndOfDoc ) + { + bool bDelete = false; + SwNode *pNode = &aIndx.GetNode(); + if( pNode->IsTextNode() ) + { + SwTextNode *pNd = pNode->GetTextNode(); + if( pNd->HasSwAttrSet() ) + pNd->ResetAttr( RES_PAGEDESC, RES_BREAK ); + if( bImpress ) + { + SwTextFormatColl* pMyColl = pNd->GetTextColl(); + + const sal_uInt16 nHeadLine = static_cast<sal_uInt16>( + !pMyColl->IsAssignedToListLevelOfOutlineStyle() + ? RES_POOLCOLL_HEADLINE2 + : RES_POOLCOLL_HEADLINE1 ); + pMyColl = pExtDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nHeadLine ); + pNd->ChgFormatColl( pMyColl ); + } + if( !pNd->Len() && + pNd->StartOfSectionIndex()+2 < pNd->EndOfSectionIndex() ) + { + bDelete = true; + pExtDoc->GetNodes().Delete( aIndx ); + } + } + if( !bDelete ) + ++aIndx; + } + ::EndProgress( GetDocShell() ); + } +} + +namespace +{ +void RemoveOrDeleteContents(SwTextNode* pTextNd, IDocumentContentOperations& xOperations) +{ + SwPaM aPam(*pTextNd, 0, *pTextNd, pTextNd->GetText().getLength()); + + // Remove hidden paragraph or delete contents: + // Delete contents if + // 1. removing the paragraph would result in an empty section or + // 2. if the paragraph is the last paragraph in the section and + // there is no paragraph in front of the paragraph: + if ((2 == pTextNd->EndOfSectionIndex() - pTextNd->StartOfSectionIndex()) + || (1 == pTextNd->EndOfSectionIndex() - pTextNd->GetIndex() + && !pTextNd->GetNodes()[pTextNd->GetIndex() - 1]->GetTextNode())) + { + xOperations.DeleteRange(aPam); + } + else + { + aPam.DeleteMark(); + xOperations.DelFullPara(aPam); + } +} +// Returns if the data was actually modified +bool HandleHidingField(SwFormatField& rFormatField, const SwNodes& rNodes, + IDocumentContentOperations& xOperations) +{ + if( !rFormatField.GetTextField() ) + return false; + SwTextNode* pTextNd = rFormatField.GetTextField()->GetpTextNode(); + if( pTextNd + && pTextNd->GetpSwpHints() && pTextNd->IsHiddenByParaField() + && &pTextNd->GetNodes() == &rNodes) + { + RemoveOrDeleteContents(pTextNd, xOperations); + return true; + } + return false; +} +} + +// The greater the returned value, the more weight has this field type on deciding the final +// paragraph state +int SwDoc::FieldCanHideParaWeight(SwFieldIds eFieldId) const +{ + switch (eFieldId) + { + case SwFieldIds::HiddenPara: + return 20; + case SwFieldIds::Database: + return GetDocumentSettingManager().get(DocumentSettingId::EMPTY_DB_FIELD_HIDES_PARA) + ? 10 + : 0; + default: + return 0; + } +} + +bool SwDoc::FieldHidesPara(const SwField& rField) const +{ + switch (rField.GetTyp()->Which()) + { + case SwFieldIds::HiddenPara: + return static_cast<const SwHiddenParaField&>(rField).IsHidden(); + case SwFieldIds::Database: + return FieldCanHideParaWeight(SwFieldIds::Database) + && rField.ExpandField(true, nullptr).isEmpty(); + default: + return false; + } +} + +/// Remove the invisible content from the document e.g. hidden areas, hidden paragraphs +// Returns if the data was actually modified +bool SwDoc::RemoveInvisibleContent() +{ + bool bRet = false; + GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_DELETE_INVISIBLECNTNT, nullptr ); + + { + class FieldTypeGuard : public SwClient + { + public: + explicit FieldTypeGuard(SwFieldType* pType) + : SwClient(pType) + { + } + const SwFieldType* get() const + { + return static_cast<const SwFieldType*>(GetRegisteredIn()); + } + }; + // Removing some nodes for one SwFieldIds::Database type might remove the type from + // document's field types, invalidating iterators. So, we need to create own list of + // matching types prior to processing them. + std::vector<std::unique_ptr<FieldTypeGuard>> aHidingFieldTypes; + for (std::unique_ptr<SwFieldType> const & pType : *getIDocumentFieldsAccess().GetFieldTypes()) + { + if (FieldCanHideParaWeight(pType->Which())) + aHidingFieldTypes.push_back(std::make_unique<FieldTypeGuard>(pType.get())); + } + for (const auto& pTypeGuard : aHidingFieldTypes) + { + if (const SwFieldType* pType = pTypeGuard->get()) + { + std::vector<SwFormatField*> vFields; + pType->GatherFields(vFields); + for(auto pFormatField: vFields) + bRet |= HandleHidingField(*pFormatField, GetNodes(), getIDocumentContentOperations()); + } + } + } + + // Remove any hidden paragraph (hidden text attribute) + for( sal_uLong n = GetNodes().Count(); n; ) + { + SwTextNode* pTextNd = GetNodes()[ --n ]->GetTextNode(); + if ( pTextNd ) + { + bool bRemoved = false; + if ( pTextNd->HasHiddenCharAttribute( true ) ) + { + bRemoved = true; + bRet = true; + + if (2 == pTextNd->EndOfSectionIndex() - pTextNd->StartOfSectionIndex()) + { + SwFrameFormat *const pFormat = pTextNd->StartOfSectionNode()->GetFlyFormat(); + if (nullptr != pFormat) + { + // remove hidden text frame + getIDocumentLayoutAccess().DelLayoutFormat(pFormat); + } + else + { + // default, remove hidden paragraph + RemoveOrDeleteContents(pTextNd, getIDocumentContentOperations()); + } + } + else + { + // default, remove hidden paragraph + RemoveOrDeleteContents(pTextNd, getIDocumentContentOperations()); + } + } + else if ( pTextNd->HasHiddenCharAttribute( false ) ) + { + bRemoved = true; + bRet = true; + SwScriptInfo::DeleteHiddenRanges( *pTextNd ); + } + + // Footnotes/Frames may have been removed, therefore we have + // to reset n: + if ( bRemoved ) + { + // [n] has to be inside [0 .. GetNodes().Count()] range + if (n > GetNodes().Count()) + n = GetNodes().Count(); + } + } + } + + { + // Delete/empty all hidden areas + o3tl::sorted_vector<SwSectionFormat*> aSectFormats; + SwSectionFormats& rSectFormats = GetSections(); + + for( SwSectionFormats::size_type n = rSectFormats.size(); n; ) + { + SwSectionFormat* pSectFormat = rSectFormats[ --n ]; + // don't add sections in Undo/Redo + if( !pSectFormat->IsInNodesArr()) + continue; + SwSection* pSect = pSectFormat->GetSection(); + if( pSect->CalcHiddenFlag() ) + { + SwSection* pParent = pSect, *pTmp; + while( nullptr != (pTmp = pParent->GetParent() )) + { + if( pTmp->IsHiddenFlag() ) + pSect = pTmp; + pParent = pTmp; + } + + aSectFormats.insert( pSect->GetFormat() ); + } + if( !pSect->GetCondition().isEmpty() ) + { + SwSectionData aSectionData( *pSect ); + aSectionData.SetCondition( OUString() ); + aSectionData.SetHidden( false ); + UpdateSection( n, aSectionData ); + } + } + + auto n = aSectFormats.size(); + + if( 0 != n ) + { + while( n ) + { + SwSectionFormat* pSectFormat = aSectFormats[ --n ]; + SwSectionNode* pSectNd = pSectFormat->GetSectionNode(); + if( pSectNd ) + { + bRet = true; + SwPaM aPam( *pSectNd ); + + if( pSectNd->StartOfSectionNode()->StartOfSectionIndex() == + pSectNd->GetIndex() - 1 && + pSectNd->StartOfSectionNode()->EndOfSectionIndex() == + pSectNd->EndOfSectionIndex() + 1 ) + { + // only delete the content + SwContentNode* pCNd = GetNodes().GoNext( + &aPam.GetPoint()->nNode ); + aPam.GetPoint()->nContent.Assign( pCNd, 0 ); + aPam.SetMark(); + aPam.GetPoint()->nNode = *pSectNd->EndOfSectionNode(); + pCNd = SwNodes::GoPrevious( + &aPam.GetPoint()->nNode ); + aPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + + getIDocumentContentOperations().DeleteRange( aPam ); + } + else + { + // delete the whole section + aPam.SetMark(); + aPam.GetPoint()->nNode = *pSectNd->EndOfSectionNode(); + getIDocumentContentOperations().DelFullPara( aPam ); + } + + } + } + } + } + + if( bRet ) + getIDocumentState().SetModified(); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_DELETE_INVISIBLECNTNT, nullptr ); + return bRet; +} + +bool SwDoc::HasInvisibleContent() const +{ + std::vector<SwFormatField*> vFields; + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::HiddenPara)->GatherFields(vFields); + if(vFields.size()) + return true; + + // Search for any hidden paragraph (hidden text attribute) + for( sal_uLong n = GetNodes().Count()-1; n; --n) + { + SwTextNode* pTextNd = GetNodes()[ n ]->GetTextNode(); + if ( pTextNd && + ( pTextNd->HasHiddenCharAttribute( true ) || pTextNd->HasHiddenCharAttribute( false ) ) ) + return true; + } + + for(auto pSectFormat : GetSections()) + { + // don't add sections in Undo/Redo + if( !pSectFormat->IsInNodesArr()) + continue; + SwSection* pSect = pSectFormat->GetSection(); + if( pSect->IsHidden() ) + return true; + } + return false; +} + +bool SwDoc::RestoreInvisibleContent() +{ + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (GetIDocumentUndoRedo().GetLastUndoInfo(nullptr, & nLastUndoId) + && (SwUndoId::UI_DELETE_INVISIBLECNTNT == nLastUndoId)) + { + GetIDocumentUndoRedo().Undo(); + GetIDocumentUndoRedo().ClearRedo(); + return true; + } + return false; +} + +bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) +{ + bool bRet = false; + getIDocumentFieldsAccess().LockExpFields(); + GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE, nullptr ); + + const SwFieldTypes* pMyFieldTypes = getIDocumentFieldsAccess().GetFieldTypes(); + const SwFieldTypes::size_type nCount = pMyFieldTypes->size(); + //go backward, field types are removed + for(SwFieldTypes::size_type nType = nCount; nType > 0; --nType) + { + const SwFieldType *pCurType = (*pMyFieldTypes)[nType - 1].get(); + + if ( SwFieldIds::Postit == pCurType->Which() ) + continue; + + std::vector<SwFormatField*> vFieldFormats; + pCurType->GatherFields(vFieldFormats, false); + for(const auto& rpFieldFormat : vFieldFormats) + { + const SwTextField *pTextField = rpFieldFormat->GetTextField(); + // skip fields that are currently not in the document + // e.g. fields in undo or redo array + + bool bSkip = !pTextField || + !pTextField->GetpTextNode()->GetNodes().IsDocNodes(); + + if (!bSkip) + { + bool bInHeaderFooter = IsInHeaderFooter(SwNodeIndex(*pTextField->GetpTextNode())); + const SwFormatField& rFormatField = pTextField->GetFormatField(); + const SwField* pField = rFormatField.GetField(); + + //#i55595# some fields have to be excluded in headers/footers + SwFieldIds nWhich = pField->GetTyp()->Which(); + if(!bInHeaderFooter || + (nWhich != SwFieldIds::PageNumber && + nWhich != SwFieldIds::Chapter && + nWhich != SwFieldIds::GetExp&& + nWhich != SwFieldIds::SetExp&& + nWhich != SwFieldIds::Input&& + nWhich != SwFieldIds::RefPageGet&& + nWhich != SwFieldIds::RefPageSet)) + { + OUString sText = pField->ExpandField(true, &rLayout); + + // database fields should not convert their command into text + if( SwFieldIds::Database == pCurType->Which() && !static_cast<const SwDBField*>(pField)->IsInitialized()) + sText.clear(); + + SwPaM aInsertPam(*pTextField->GetpTextNode(), pTextField->GetStart()); + aInsertPam.SetMark(); + + // go to the end of the field + const SwTextField *pFieldAtEnd = sw::DocumentFieldsManager::GetTextFieldAtPos(*aInsertPam.End()); + if (pFieldAtEnd && pFieldAtEnd->Which() == RES_TXTATR_INPUTFIELD) + { + SwPosition &rEndPos = *aInsertPam.GetPoint(); + rEndPos.nContent = SwCursorShell::EndOfInputFieldAtPos( *aInsertPam.End() ); + } + else + { + aInsertPam.Move(); + } + + // first insert the text after field to keep the field's attributes, + // then delete the field + if (!sText.isEmpty()) + { + // to keep the position after insert + SwPaM aDelPam( *aInsertPam.GetMark(), *aInsertPam.GetPoint() ); + aDelPam.Move( fnMoveBackward ); + aInsertPam.DeleteMark(); + + getIDocumentContentOperations().InsertString( aInsertPam, sText ); + + aDelPam.Move(); + // finally remove the field + getIDocumentContentOperations().DeleteAndJoin( aDelPam ); + } + else + { + getIDocumentContentOperations().DeleteAndJoin( aInsertPam ); + } + + bRet = true; + } + } + } + } + + if( bRet ) + getIDocumentState().SetModified(); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_REPLACE, nullptr ); + getIDocumentFieldsAccess().UnlockExpFields(); + return bRet; + +} + +bool SwDoc::IsInsTableFormatNum() const +{ + return SW_MOD()->IsInsTableFormatNum(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsInsTableChangeNumFormat() const +{ + return SW_MOD()->IsInsTableChangeNumFormat(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsInsTableAlignNum() const +{ + return SW_MOD()->IsInsTableAlignNum(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +bool SwDoc::IsSplitVerticalByDefault() const +{ + return SW_MOD()->IsSplitVerticalByDefault(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE)); +} + +void SwDoc::SetSplitVerticalByDefault(bool value) +{ + SW_MOD()->SetSplitVerticalByDefault(GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE), value); +} + +/// Set up the InsertDB as Undo table +void SwDoc::AppendUndoForInsertFromDB( const SwPaM& rPam, bool bIsTable ) +{ + if( bIsTable ) + { + const SwTableNode* pTableNd = rPam.GetPoint()->nNode.GetNode().FindTableNode(); + if( pTableNd ) + { + std::unique_ptr<SwUndoCpyTable> pUndo(new SwUndoCpyTable(this)); + pUndo->SetTableSttIdx( pTableNd->GetIndex() ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else if( rPam.HasMark() ) + { + std::unique_ptr<SwUndoCpyDoc> pUndo(new SwUndoCpyDoc( rPam )); + pUndo->SetInsertRange( rPam, false ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } +} + +void SwDoc::ChangeTOX(SwTOXBase & rTOX, const SwTOXBase & rNew) +{ + assert(dynamic_cast<const SwTOXBaseSection*>(&rTOX)); + SwTOXBaseSection& rTOXSect(static_cast<SwTOXBaseSection&>(rTOX)); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTOXChange>(this, rTOXSect, rNew)); + } + + rTOX = rNew; + + // note: do not Update the ToX here - the caller will do it, with a ViewShell! +} + +OUString SwDoc::GetPaMDescr(const SwPaM & rPam) +{ + if (&rPam.GetNode() == &rPam.GetNode(false)) + { + SwTextNode * pTextNode = rPam.GetNode().GetTextNode(); + + if (nullptr != pTextNode) + { + const sal_Int32 nStart = rPam.Start()->nContent.GetIndex(); + const sal_Int32 nEnd = rPam.End()->nContent.GetIndex(); + + return SwResId(STR_START_QUOTE) + + ShortenString(pTextNode->GetText().copy(nStart, nEnd - nStart), + nUndoStringLength, + SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + } + } + else + { + return SwResId(STR_PARAGRAPHS); + } + + return "??"; +} + +bool SwDoc::ContainsHiddenChars() const +{ + for( sal_uLong n = GetNodes().Count(); n; ) + { + SwNode* pNd = GetNodes()[ --n ]; + if ( pNd->IsTextNode() && pNd->GetTextNode()->HasHiddenCharAttribute( false ) ) + return true; + } + + return false; +} + +std::shared_ptr<SwUnoCursor> SwDoc::CreateUnoCursor( const SwPosition& rPos, bool bTableCursor ) +{ + std::shared_ptr<SwUnoCursor> pNew; + if( bTableCursor ) + pNew = std::make_shared<SwUnoTableCursor>(rPos); + else + pNew = std::make_shared<SwUnoCursor>(rPos); + + mvUnoCursorTable.push_back( pNew ); + return pNew; +} + +void SwDoc::ChkCondColls() +{ + for (SwTextFormatColls::size_type n = 0; n < mpTextFormatCollTable->size(); ++n) + { + SwTextFormatColl *pColl = (*mpTextFormatCollTable)[n]; + if (RES_CONDTXTFMTCOLL == pColl->Which()) + pColl->CallSwClientNotify( SwAttrHint() ); + } +} + +uno::Reference< script::vba::XVBAEventProcessor > const & +SwDoc::GetVbaEventProcessor() +{ +#if HAVE_FEATURE_SCRIPTING + if( !mxVbaEvents.is() && mpDocShell && ooo::vba::isAlienWordDoc( *mpDocShell ) ) + { + try + { + uno::Reference< frame::XModel > xModel( mpDocShell->GetModel(), uno::UNO_SET_THROW ); + uno::Sequence< uno::Any > aArgs(1); + aArgs[0] <<= xModel; + mxVbaEvents.set( ooo::vba::createVBAUnoAPIServiceWithArgs( mpDocShell, "com.sun.star.script.vba.VBATextEventProcessor" , aArgs ), uno::UNO_QUERY_THROW ); + } + catch( uno::Exception& ) + { + } + } +#endif + return mxVbaEvents; +} + +void SwDoc::SetMissingDictionaries( bool bIsMissing ) +{ + if (!bIsMissing) + meDictionaryMissing = MissingDictionary::False; + else if (meDictionaryMissing == MissingDictionary::Undefined) + meDictionaryMissing = MissingDictionary::True; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docbasic.cxx b/sw/source/core/doc/docbasic.cxx new file mode 100644 index 000000000..4fb0db964 --- /dev/null +++ b/sw/source/core/doc/docbasic.cxx @@ -0,0 +1,234 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <basic/sbx.hxx> +#include <frmfmt.hxx> +#include <fmtinfmt.hxx> +#include <fmturl.hxx> +#include <frmatr.hxx> +#include <docary.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <swevent.hxx> +#include <frameformats.hxx> +#include <memory> + +using namespace ::com::sun::star::uno; + +static Sequence<Any> *lcl_docbasic_convertArgs( SbxArray& rArgs ) +{ + Sequence<Any> *pRet = nullptr; + + sal_uInt32 nCount = rArgs.Count32(); + if( nCount > 1 ) + { + nCount--; + pRet = new Sequence<Any>( nCount ); + Any *pUnoArgs = pRet->getArray(); + for( sal_uInt32 i=0; i<nCount; i++ ) + { + SbxVariable *pVar = rArgs.Get32( i+1 ); + switch( pVar->GetType() ) + { + case SbxSTRING: + pUnoArgs[i] <<= pVar->GetOUString(); + break; + case SbxCHAR: + pUnoArgs[i] <<= static_cast<sal_Int16>(pVar->GetChar()) ; + break; + case SbxUSHORT: + pUnoArgs[i] <<= static_cast<sal_Int16>(pVar->GetUShort()); + break; + case SbxLONG: + pUnoArgs[i] <<= pVar->GetLong(); + break; + default: + pUnoArgs[i].clear(); + break; + } + } + } + + return pRet; +} + +void SwDoc::ExecMacro( const SvxMacro& rMacro, OUString* pRet, SbxArray* pArgs ) +{ + switch( rMacro.GetScriptType() ) + { + case STARBASIC: + { + SbxBaseRef aRef; + SbxValue* pRetValue = new SbxValue; + aRef = pRetValue; + mpDocShell->CallBasic( rMacro.GetMacName(), + rMacro.GetLibName(), + pArgs, pRet ? pRetValue : nullptr ); + + if( pRet && SbxNULL < pRetValue->GetType() && + SbxVOID != pRetValue->GetType() ) + { + // valid value, so set it + *pRet = pRetValue->GetOUString(); + } + } + break; + case JAVASCRIPT: + // ignore JavaScript calls + break; + case EXTENDED_STYPE: + { + std::unique_ptr<Sequence<Any> > pUnoArgs; + if( pArgs ) + { + // better to rename the local function to lcl_translateBasic2Uno and + // a much shorter routine can be found in sfx2/source/doc/objmisc.cxx + pUnoArgs.reset(lcl_docbasic_convertArgs( *pArgs )); + } + + if (!pUnoArgs) + { + pUnoArgs.reset(new Sequence< Any > (0)); + } + + // TODO - return value is not handled + Any aRet; + Sequence< sal_Int16 > aOutArgsIndex; + Sequence< Any > aOutArgs; + + SAL_INFO("sw", "SwDoc::ExecMacro URL is " << rMacro.GetMacName() ); + + mpDocShell->CallXScript( + rMacro.GetMacName(), *pUnoArgs, aRet, aOutArgsIndex, aOutArgs); + + break; + } + } +} + +sal_uInt16 SwDoc::CallEvent( SvMacroItemId nEvent, const SwCallMouseEvent& rCallEvent, + bool bCheckPtr ) +{ + if( !mpDocShell ) // we can't do that without a DocShell! + return 0; + + sal_uInt16 nRet = 0; + const SvxMacroTableDtor* pTable = nullptr; + switch( rCallEvent.eType ) + { + case EVENT_OBJECT_INETATTR: + if( bCheckPtr ) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + auto pFormatItem = dynamic_cast<const SwFormatINetFormat*>(pItem); + if( pFormatItem && rCallEvent.PTR.pINetAttr == pFormatItem ) + { + bCheckPtr = false; // misuse as a flag + break; + } + } + } + if( !bCheckPtr ) + pTable = rCallEvent.PTR.pINetAttr->GetMacroTable(); + break; + + case EVENT_OBJECT_URLITEM: + case EVENT_OBJECT_IMAGE: + { + const SwFrameFormat* pFormat = rCallEvent.PTR.pFormat; + if( bCheckPtr ) + { + if (GetSpzFrameFormats()->IsAlive(pFormat)) + bCheckPtr = false; // misuse as a flag + else + // this shouldn't be possible now that SwCallMouseEvent + // listens for dying format? + assert(false); + } + if( !bCheckPtr ) + pTable = &pFormat->GetMacro().GetMacroTable(); + } + break; + + case EVENT_OBJECT_IMAGEMAP: + { + const IMapObject* pIMapObj = rCallEvent.PTR.IMAP.pIMapObj; + if( bCheckPtr ) + { + const SwFrameFormat* pFormat = rCallEvent.PTR.IMAP.pFormat; + if (GetSpzFrameFormats()->IsAlive(pFormat)) + { + const ImageMap* pIMap = pFormat->GetURL().GetMap(); + if (pIMap) + { + for( size_t nPos = pIMap->GetIMapObjectCount(); nPos; ) + if( pIMapObj == pIMap->GetIMapObject( --nPos )) + { + bCheckPtr = false; // misuse as a flag + break; + } + } + } + } + if( !bCheckPtr ) + pTable = &pIMapObj->GetMacroTable(); + } + break; + default: + break; + } + + if( pTable ) + { + nRet = 0x1; + if( pTable->IsKeyValid( nEvent ) ) + { + const SvxMacro& rMacro = *pTable->Get( nEvent ); + if( STARBASIC == rMacro.GetScriptType() ) + { + nRet += ERRCODE_NONE == mpDocShell->CallBasic( rMacro.GetMacName(), + rMacro.GetLibName(), nullptr ) ? 1 : 0; + } + else if( EXTENDED_STYPE == rMacro.GetScriptType() ) + { + std::unique_ptr<Sequence<Any> > pUnoArgs(new Sequence<Any>()); + + Any aRet; + Sequence< sal_Int16 > aOutArgsIndex; + Sequence< Any > aOutArgs; + + SAL_INFO("sw", "SwDoc::CallEvent URL is " << rMacro.GetMacName() ); + + nRet += ERRCODE_NONE == mpDocShell->CallXScript( + rMacro.GetMacName(), *pUnoArgs,aRet, aOutArgsIndex, aOutArgs) ? 1 : 0; + } + // JavaScript calls are ignored + } + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docbm.cxx b/sw/source/core/doc/docbm.cxx new file mode 100644 index 000000000..c76e4417c --- /dev/null +++ b/sw/source/core/doc/docbm.cxx @@ -0,0 +1,1862 @@ +/* -*- 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 <memory> +#include <utility> + +#include <MarkManager.hxx> +#include <bookmrk.hxx> +#include <crossrefbookmark.hxx> +#include <crsrsh.hxx> +#include <annotationmark.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentUndoRedo.hxx> +#include <docary.hxx> +#include <xmloff/odffields.hxx> +#include <mvsave.hxx> +#include <ndtxt.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <redline.hxx> +#include <rolbck.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <sal/log.hxx> +#include <UndoBookmark.hxx> +#include <tools/datetimeutils.hxx> +#include <view.hxx> + +#include <libxml/xmlstring.h> +#include <libxml/xmlwriter.h> + +using namespace ::sw::mark; + +std::vector<::sw::mark::MarkBase*>::const_iterator const& +IDocumentMarkAccess::iterator::get() const +{ + return *m_pIter; +} + +IDocumentMarkAccess::iterator::iterator(std::vector<::sw::mark::MarkBase*>::const_iterator const& rIter) + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator(rIter)) +{ +} + +IDocumentMarkAccess::iterator::iterator(iterator const& rOther) + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator(*rOther.m_pIter)) +{ +} + +auto IDocumentMarkAccess::iterator::operator=(iterator const& rOther) -> iterator& +{ + m_pIter.reset(new std::vector<::sw::mark::MarkBase*>::const_iterator(*rOther.m_pIter)); + return *this; +} + +IDocumentMarkAccess::iterator::iterator(iterator && rOther) noexcept + : m_pIter(std::move(rOther.m_pIter)) +{ +} + +auto IDocumentMarkAccess::iterator::operator=(iterator && rOther) noexcept -> iterator& +{ + m_pIter = std::move(rOther.m_pIter); + return *this; +} + +IDocumentMarkAccess::iterator::~iterator() +{ +} + +// ARGH why does it *need* to return const& ? +::sw::mark::IMark* /*const&*/ +IDocumentMarkAccess::iterator::operator*() const +{ + return static_cast<sw::mark::IMark*>(**m_pIter); +} + +auto IDocumentMarkAccess::iterator::operator++() -> iterator& +{ + ++(*m_pIter); + return *this; +} +auto IDocumentMarkAccess::iterator::operator++(int) -> iterator +{ + iterator tmp(*this); + ++(*m_pIter); + return tmp; +} + +bool IDocumentMarkAccess::iterator::operator==(iterator const& rOther) const +{ + return *m_pIter == *rOther.m_pIter; +} + +bool IDocumentMarkAccess::iterator::operator!=(iterator const& rOther) const +{ + return *m_pIter != *rOther.m_pIter; +} + +IDocumentMarkAccess::iterator::iterator() + : m_pIter(new std::vector<::sw::mark::MarkBase*>::const_iterator()) +{ +} + +auto IDocumentMarkAccess::iterator::operator--() -> iterator& +{ + --(*m_pIter); + return *this; +} + +auto IDocumentMarkAccess::iterator::operator--(int) -> iterator +{ + iterator tmp(*this); + --(*m_pIter); + return tmp; +} + +auto IDocumentMarkAccess::iterator::operator+=(difference_type const n) -> iterator& +{ + (*m_pIter) += n; + return *this; +} + +auto IDocumentMarkAccess::iterator::operator+(difference_type const n) const -> iterator +{ + return iterator(*m_pIter + n); +} + +auto IDocumentMarkAccess::iterator::operator-=(difference_type const n) -> iterator& +{ + (*m_pIter) -= n; + return *this; +} + +auto IDocumentMarkAccess::iterator::operator-(difference_type const n) const -> iterator +{ + return iterator(*m_pIter - n); +} + +auto IDocumentMarkAccess::iterator::operator-(iterator const& rOther) const -> difference_type +{ + return *m_pIter - *rOther.m_pIter; +} + +auto IDocumentMarkAccess::iterator::operator[](difference_type const n) const -> value_type +{ + return static_cast<sw::mark::IMark*>((*m_pIter)[n]); +} + +bool IDocumentMarkAccess::iterator::operator<(iterator const& rOther) const +{ + return *m_pIter < *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator>(iterator const& rOther) const +{ + return *m_pIter > *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator<=(iterator const& rOther) const +{ + return *m_pIter <= *rOther.m_pIter; +} +bool IDocumentMarkAccess::iterator::operator>=(iterator const& rOther) const +{ + return *m_pIter >= *rOther.m_pIter; +} + + +namespace +{ + bool lcl_GreaterThan( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return pIdx != nullptr + ? ( rPos.nNode > rNdIdx + || ( rPos.nNode == rNdIdx + && rPos.nContent >= pIdx->GetIndex() ) ) + : rPos.nNode >= rNdIdx; + } + + bool lcl_Lower( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return rPos.nNode < rNdIdx + || ( pIdx != nullptr + && rPos.nNode == rNdIdx + && rPos.nContent < pIdx->GetIndex() ); + } + + bool lcl_MarkOrderingByStart(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + auto const& rFirstStart(pFirst->GetMarkStart()); + auto const& rSecondStart(pSecond->GetMarkStart()); + if (rFirstStart.nNode != rSecondStart.nNode) + { + return rFirstStart.nNode < rSecondStart.nNode; + } + const sal_Int32 nFirstContent = rFirstStart.nContent.GetIndex(); + const sal_Int32 nSecondContent = rSecondStart.nContent.GetIndex(); + if (nFirstContent != 0 || nSecondContent != 0) + { + return nFirstContent < nSecondContent; + } + auto *const pCRFirst (dynamic_cast<::sw::mark::CrossRefBookmark const*>(pFirst)); + auto *const pCRSecond(dynamic_cast<::sw::mark::CrossRefBookmark const*>(pSecond)); + if ((pCRFirst == nullptr) == (pCRSecond == nullptr)) + { + return false; // equal + } + return pCRFirst != nullptr; // cross-ref sorts *before* + } + + bool lcl_MarkOrderingByEnd(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + return pFirst->GetMarkEnd() < pSecond->GetMarkEnd(); + } + + void lcl_InsertMarkSorted(MarkManager::container_t& io_vMarks, + ::sw::mark::MarkBase *const pMark) + { + io_vMarks.insert( + lower_bound( + io_vMarks.begin(), + io_vMarks.end(), + pMark, + &lcl_MarkOrderingByStart), + pMark); + } + + std::unique_ptr<SwPosition> lcl_PositionFromContentNode( + SwContentNode * const pContentNode, + const bool bAtEnd) + { + std::unique_ptr<SwPosition> pResult(new SwPosition(*pContentNode)); + pResult->nContent.Assign(pContentNode, bAtEnd ? pContentNode->Len() : 0); + return pResult; + } + + // return a position at the begin of rEnd, if it is a ContentNode + // else set it to the begin of the Node after rEnd, if there is one + // else set it to the end of the node before rStt + // else set it to the ContentNode of the Pos outside the Range + std::unique_ptr<SwPosition> lcl_FindExpelPosition( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + const SwPosition& rOtherPosition) + { + SwContentNode * pNode = rEnd.GetNode().GetContentNode(); + bool bPosAtEndOfNode = false; + if ( pNode == nullptr) + { + SwNodeIndex aEnd = rEnd; + pNode = rEnd.GetNodes().GoNext( &aEnd ); + bPosAtEndOfNode = false; + } + if ( pNode == nullptr ) + { + SwNodeIndex aStt = rStt; + pNode = SwNodes::GoPrevious(&aStt); + bPosAtEndOfNode = true; + } + if ( pNode != nullptr ) + { + return lcl_PositionFromContentNode( pNode, bPosAtEndOfNode ); + } + + return std::make_unique<SwPosition>(rOtherPosition); + } + + struct CompareIMarkStartsBefore + { + bool operator()(SwPosition const& rPos, + const sw::mark::IMark* pMark) + { + return rPos < pMark->GetMarkStart(); + } + bool operator()(const sw::mark::IMark* pMark, + SwPosition const& rPos) + { + return pMark->GetMarkStart() < rPos; + } + }; + + // Apple llvm-g++ 4.2.1 with _GLIBCXX_DEBUG won't eat boost::bind for this + // Neither will MSVC 2008 with _DEBUG + struct CompareIMarkStartsAfter + { + bool operator()(SwPosition const& rPos, + const sw::mark::IMark* pMark) + { + return pMark->GetMarkStart() > rPos; + } + }; + + + IMark* lcl_getMarkAfter(const MarkManager::container_t& rMarks, const SwPosition& rPos) + { + auto const pMarkAfter = upper_bound( + rMarks.begin(), + rMarks.end(), + rPos, + CompareIMarkStartsAfter()); + if(pMarkAfter == rMarks.end()) + return nullptr; + return *pMarkAfter; + }; + + IMark* lcl_getMarkBefore(const MarkManager::container_t& rMarks, const SwPosition& rPos) + { + // candidates from which to choose the mark before + MarkManager::container_t vCandidates; + // no need to consider marks starting after rPos + auto const pCandidatesEnd = upper_bound( + rMarks.begin(), + rMarks.end(), + rPos, + CompareIMarkStartsAfter()); + vCandidates.reserve(pCandidatesEnd - rMarks.begin()); + // only marks ending before are candidates + remove_copy_if( + rMarks.begin(), + pCandidatesEnd, + back_inserter(vCandidates), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return !(pMark->GetMarkEnd() < rPos); } ); + // no candidate left => we are in front of the first mark or there are none + if(vCandidates.empty()) return nullptr; + // return the highest (last) candidate using mark end ordering + return *max_element(vCandidates.begin(), vCandidates.end(), &lcl_MarkOrderingByEnd); + } + + bool lcl_FixCorrectedMark( + const bool bChangedPos, + const bool bChangedOPos, + MarkBase* io_pMark ) + { + if ( IDocumentMarkAccess::GetType(*io_pMark) == IDocumentMarkAccess::MarkType::ANNOTATIONMARK ) + { + // annotation marks are allowed to span a table cell range. + // but trigger sorting to be save + return true; + } + + if ( ( bChangedPos || bChangedOPos ) + && io_pMark->IsExpanded() + && io_pMark->GetOtherMarkPos().nNode.GetNode().FindTableBoxStartNode() != + io_pMark->GetMarkPos().nNode.GetNode().FindTableBoxStartNode() ) + { + if ( !bChangedOPos ) + { + io_pMark->SetMarkPos( io_pMark->GetOtherMarkPos() ); + } + io_pMark->ClearOtherMarkPos(); + DdeBookmark * const pDdeBkmk = dynamic_cast< DdeBookmark*>(io_pMark); + if ( pDdeBkmk != nullptr + && pDdeBkmk->IsServer() ) + { + pDdeBkmk->SetRefObject(nullptr); + } + return true; + } + return false; + } + + bool lcl_MarkEqualByStart(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + return !lcl_MarkOrderingByStart(pFirst, pSecond) && + !lcl_MarkOrderingByStart(pSecond, pFirst); + } + + MarkManager::container_t::const_iterator lcl_FindMark( + MarkManager::container_t& rMarks, + const ::sw::mark::MarkBase *const pMarkToFind) + { + auto ppCurrentMark = lower_bound( + rMarks.begin(), rMarks.end(), + pMarkToFind, &lcl_MarkOrderingByStart); + // since there are usually not too many marks on the same start + // position, we are not doing a bisect search for the upper bound + // but instead start to iterate from pMarkLow directly + while (ppCurrentMark != rMarks.end() && lcl_MarkEqualByStart(*ppCurrentMark, pMarkToFind)) + { + if(*ppCurrentMark == pMarkToFind) + { + return MarkManager::container_t::const_iterator(std::move(ppCurrentMark)); + } + ++ppCurrentMark; + } + // reached a mark starting on a later start pos or the end of the + // vector => not found + return rMarks.end(); + }; + + MarkManager::container_t::const_iterator lcl_FindMarkAtPos( + MarkManager::container_t& rMarks, + const SwPosition& rPos, + const IDocumentMarkAccess::MarkType eType) + { + for (auto ppCurrentMark = lower_bound( + rMarks.begin(), rMarks.end(), + rPos, + CompareIMarkStartsBefore()); + ppCurrentMark != rMarks.end(); + ++ppCurrentMark) + { + // Once we reach a mark starting after the target pos + // we do not need to continue + if((*ppCurrentMark)->GetMarkStart() > rPos) + break; + if(IDocumentMarkAccess::GetType(**ppCurrentMark) == eType) + { + return MarkManager::container_t::const_iterator(std::move(ppCurrentMark)); + } + } + // reached a mark starting on a later start pos or the end of the + // vector => not found + return rMarks.end(); + }; + + MarkManager::container_t::const_iterator lcl_FindMarkByName( + const OUString& rName, + const MarkManager::container_t::const_iterator& ppMarksBegin, + const MarkManager::container_t::const_iterator& ppMarksEnd) + { + return find_if( + ppMarksBegin, + ppMarksEnd, + [&rName] (::sw::mark::MarkBase const*const pMark) { return pMark->GetName() == rName; } ); + } + + void lcl_DebugMarks(MarkManager::container_t const& rMarks) + { +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("sw.core", rMarks.size() << " Marks"); + for (auto ppMark = rMarks.begin(); + ppMark != rMarks.end(); + ++ppMark) + { + IMark* pMark = *ppMark; + const SwPosition* const pStPos = &pMark->GetMarkStart(); + const SwPosition* const pEndPos = &pMark->GetMarkEnd(); + SAL_INFO("sw.core", + pStPos->nNode.GetIndex() << "," << + pStPos->nContent.GetIndex() << " " << + pEndPos->nNode.GetIndex() << "," << + pEndPos->nContent.GetIndex() << " " << + typeid(*pMark).name() << " " << + pMark->GetName()); + } +#else + (void) rMarks; +#endif + assert(std::is_sorted(rMarks.begin(), rMarks.end(), lcl_MarkOrderingByStart)); + }; +} + +IDocumentMarkAccess::MarkType IDocumentMarkAccess::GetType(const IMark& rBkmk) +{ + const std::type_info* const pMarkTypeInfo = &typeid(rBkmk); + // not using dynamic_cast<> here for performance + if(*pMarkTypeInfo == typeid(UnoMark)) + return MarkType::UNO_BOOKMARK; + else if(*pMarkTypeInfo == typeid(DdeBookmark)) + return MarkType::DDE_BOOKMARK; + else if(*pMarkTypeInfo == typeid(Bookmark)) + return MarkType::BOOKMARK; + else if(*pMarkTypeInfo == typeid(CrossRefHeadingBookmark)) + return MarkType::CROSSREF_HEADING_BOOKMARK; + else if(*pMarkTypeInfo == typeid(CrossRefNumItemBookmark)) + return MarkType::CROSSREF_NUMITEM_BOOKMARK; + else if(*pMarkTypeInfo == typeid(AnnotationMark)) + return MarkType::ANNOTATIONMARK; + else if(*pMarkTypeInfo == typeid(TextFieldmark)) + return MarkType::TEXT_FIELDMARK; + else if(*pMarkTypeInfo == typeid(CheckboxFieldmark)) + return MarkType::CHECKBOX_FIELDMARK; + else if(*pMarkTypeInfo == typeid(DropDownFieldmark)) + return MarkType::DROPDOWN_FIELDMARK; + else if(*pMarkTypeInfo == typeid(DateFieldmark)) + return MarkType::DATE_FIELDMARK; + else if(*pMarkTypeInfo == typeid(NavigatorReminder)) + return MarkType::NAVIGATOR_REMINDER; + else + { + assert(false && "IDocumentMarkAccess::GetType(..)" + " - unknown MarkType. This needs to be fixed!"); + return MarkType::UNO_BOOKMARK; + } +} + +OUString IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix() +{ + return "__RefHeading__"; +} + +bool IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark( const SwPaM& rPaM ) +{ + return rPaM.Start()->nNode.GetNode().IsTextNode() && + rPaM.Start()->nContent.GetIndex() == 0 && + ( !rPaM.HasMark() || + ( rPaM.GetMark()->nNode == rPaM.GetPoint()->nNode && + rPaM.End()->nContent.GetIndex() == rPaM.End()->nNode.GetNode().GetTextNode()->Len() ) ); +} + +void IDocumentMarkAccess::DeleteFieldmarkCommand(::sw::mark::IFieldmark const& rMark) +{ + if (GetType(rMark) != MarkType::TEXT_FIELDMARK) + { + return; // TODO FORMDATE has no command? + } + SwPaM pam(sw::mark::FindFieldSep(rMark), rMark.GetMarkStart()); + ++pam.GetPoint()->nContent; // skip CH_TXT_ATR_FIELDSTART + pam.GetDoc()->getIDocumentContentOperations().DeleteAndJoin(pam); +} + +namespace sw::mark +{ + MarkManager::MarkManager(SwDoc& rDoc) + : m_vAllMarks() + , m_vBookmarks() + , m_vFieldmarks() + , m_vAnnotationMarks() + , m_pDoc(&rDoc) + , m_pLastActiveFieldmark(nullptr) + { } + + ::sw::mark::IMark* MarkManager::makeMark(const SwPaM& rPaM, + const OUString& rName, + const IDocumentMarkAccess::MarkType eType, + sw::mark::InsertMode const eMode, + SwPosition const*const pSepPos) + { +#if OSL_DEBUG_LEVEL > 0 + { + const SwPosition* const pPos1 = rPaM.GetPoint(); + const SwPosition* pPos2 = pPos1; + if(rPaM.HasMark()) + pPos2 = rPaM.GetMark(); + SAL_INFO("sw.core", + rName << " " << + pPos1->nNode.GetIndex() << "," << + pPos1->nContent.GetIndex() << " " << + pPos2->nNode.GetIndex() << "," << + pPos2->nContent.GetIndex()); + } +#endif + if ( (!rPaM.GetPoint()->nNode.GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + // SwXTextRange can be on table node or plain start node (FLY_AT_FLY) + || !rPaM.GetPoint()->nNode.GetNode().IsStartNode())) + || (!rPaM.GetMark()->nNode.GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + || !rPaM.GetMark()->nNode.GetNode().IsStartNode()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - refusing to create mark on non-textnode"); + return nullptr; + } + // There should only be one CrossRefBookmark per Textnode per Type + if ((eType == MarkType::CROSSREF_NUMITEM_BOOKMARK || eType == MarkType::CROSSREF_HEADING_BOOKMARK) + && (lcl_FindMarkAtPos(m_vBookmarks, *rPaM.Start(), eType) != m_vBookmarks.end())) + { // this can happen via UNO API + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - refusing to create duplicate CrossRefBookmark"); + return nullptr; + } + + if ((eType == MarkType::CHECKBOX_FIELDMARK || eType == MarkType::DROPDOWN_FIELDMARK) + && (eMode == InsertMode::New + ? *rPaM.GetPoint() != *rPaM.GetMark() + // CopyText: pam covers CH_TXT_ATR_FORMELEMENT + : (rPaM.GetPoint()->nNode != rPaM.GetMark()->nNode + || rPaM.Start()->nContent.GetIndex() + 1 != rPaM.End()->nContent.GetIndex()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on point fieldmark"); + return nullptr; + } + + if ((eType == MarkType::TEXT_FIELDMARK || eType == MarkType::DATE_FIELDMARK) + && (rPaM.GetPoint()->nNode.GetNode().StartOfSectionNode() != rPaM.GetMark()->nNode.GetNode().StartOfSectionNode() + || (pSepPos && rPaM.GetPoint()->nNode.GetNode().StartOfSectionNode() != pSepPos->nNode.GetNode().StartOfSectionNode()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on fieldmark, different nodes array sections"); + return nullptr; + } + + if ((eType == MarkType::TEXT_FIELDMARK || eType == MarkType::DATE_FIELDMARK) + // can't check for Copy - it asserts - but it's also obviously unnecessary + && eMode == InsertMode::New + && sw::mark::IsFieldmarkOverlap(rPaM)) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on fieldmark, overlaps existing fieldmark or meta-field"); + return nullptr; + } + + // create mark + std::unique_ptr<::sw::mark::MarkBase> pMark; + switch(eType) + { + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + pMark = std::make_unique<TextFieldmark>(rPaM, rName); + break; + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + pMark = std::make_unique<CheckboxFieldmark>(rPaM); + break; + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + pMark = std::make_unique<DropDownFieldmark>(rPaM); + break; + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + pMark = std::make_unique<DateFieldmark>(rPaM); + break; + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + pMark = std::make_unique<NavigatorReminder>(rPaM); + break; + case IDocumentMarkAccess::MarkType::BOOKMARK: + pMark = std::make_unique<Bookmark>(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + pMark = std::make_unique<DdeBookmark>(rPaM); + break; + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + pMark = std::make_unique<CrossRefHeadingBookmark>(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + pMark = std::make_unique<CrossRefNumItemBookmark>(rPaM, vcl::KeyCode(), rName); + break; + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + pMark = std::make_unique<UnoMark>(rPaM); + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + pMark = std::make_unique<AnnotationMark>( rPaM, rName ); + break; + } + assert(pMark && "MarkManager::makeMark(..) - Mark was not created."); + + if(pMark->GetMarkPos() != pMark->GetMarkStart()) + pMark->Swap(); + + // for performance reasons, we trust UnoMarks to have a (generated) unique name + if ( eType != IDocumentMarkAccess::MarkType::UNO_BOOKMARK ) + pMark->SetName( getUniqueMarkName( pMark->GetName() ) ); + + // insert any dummy chars before inserting into sorted vectors + pMark->InitDoc(m_pDoc, eMode, pSepPos); + + // register mark + lcl_InsertMarkSorted(m_vAllMarks, pMark.get()); + switch(eType) + { + case IDocumentMarkAccess::MarkType::BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + lcl_InsertMarkSorted(m_vBookmarks, pMark.get()); + break; + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + lcl_InsertMarkSorted(m_vFieldmarks, pMark.get()); + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + lcl_InsertMarkSorted( m_vAnnotationMarks, pMark.get() ); + break; + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no special array for these + break; + } + SAL_INFO("sw.core", "--- makeType ---"); + SAL_INFO("sw.core", "Marks"); + lcl_DebugMarks(m_vAllMarks); + SAL_INFO("sw.core", "Bookmarks"); + lcl_DebugMarks(m_vBookmarks); + SAL_INFO("sw.core", "Fieldmarks"); + lcl_DebugMarks(m_vFieldmarks); + + return pMark.release(); + } + + ::sw::mark::IFieldmark* MarkManager::makeFieldBookmark( + const SwPaM& rPaM, + const OUString& rName, + const OUString& rType, + SwPosition const*const pSepPos) + { + + // Disable undo, because we handle it using SwUndoInsTextFieldmark + bool bUndoIsEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + sw::mark::IMark* pMark = nullptr; + if(rType == ODF_FORMDATE) + { + pMark = makeMark(rPaM, rName, + IDocumentMarkAccess::MarkType::DATE_FIELDMARK, + sw::mark::InsertMode::New, + pSepPos); + } + else + { + pMark = makeMark(rPaM, rName, + IDocumentMarkAccess::MarkType::TEXT_FIELDMARK, + sw::mark::InsertMode::New, + pSepPos); + } + sw::mark::IFieldmark* pFieldMark = dynamic_cast<sw::mark::IFieldmark*>( pMark ); + if (pFieldMark) + pFieldMark->SetFieldname( rType ); + + if (bUndoIsEnabled) + { + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_pDoc->GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsTextFieldmark>(*pFieldMark)); + } + + return pFieldMark; + } + + ::sw::mark::IFieldmark* MarkManager::makeNoTextFieldBookmark( + const SwPaM& rPaM, + const OUString& rName, + const OUString& rType) + { + // Disable undo, because we handle it using SwUndoInsNoTextFieldmark + bool bUndoIsEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + m_pDoc->GetIDocumentUndoRedo().DoUndo(false); + + bool bEnableSetModified = m_pDoc->getIDocumentState().IsEnableSetModified(); + m_pDoc->getIDocumentState().SetEnableSetModified(false); + + sw::mark::IMark* pMark = nullptr; + if(rType == ODF_FORMCHECKBOX) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK, + sw::mark::InsertMode::New); + } + else if(rType == ODF_FORMDROPDOWN) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK, + sw::mark::InsertMode::New); + } + else if(rType == ODF_FORMDATE) + { + pMark = makeMark( rPaM, rName, + IDocumentMarkAccess::MarkType::DATE_FIELDMARK, + sw::mark::InsertMode::New); + } + + sw::mark::IFieldmark* pFieldMark = dynamic_cast<sw::mark::IFieldmark*>( pMark ); + if (pFieldMark) + pFieldMark->SetFieldname( rType ); + + if (bUndoIsEnabled) + { + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_pDoc->GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsNoTextFieldmark>(*pFieldMark)); + } + + m_pDoc->getIDocumentState().SetEnableSetModified(bEnableSetModified); + m_pDoc->getIDocumentState().SetModified(); + + return pFieldMark; + } + + ::sw::mark::IMark* MarkManager::getMarkForTextNode( + const SwTextNode& rTextNode, + const IDocumentMarkAccess::MarkType eType ) + { + SwPosition aPos(rTextNode); + aPos.nContent.Assign(&const_cast<SwTextNode&>(rTextNode), 0); + auto const ppExistingMark = lcl_FindMarkAtPos(m_vBookmarks, aPos, eType); + if(ppExistingMark != m_vBookmarks.end()) + return *ppExistingMark; + const SwPaM aPaM(aPos); + return makeMark(aPaM, OUString(), eType, sw::mark::InsertMode::New); + } + + sw::mark::IMark* MarkManager::makeAnnotationMark( + const SwPaM& rPaM, + const OUString& rName ) + { + return makeMark(rPaM, rName, IDocumentMarkAccess::MarkType::ANNOTATIONMARK, + sw::mark::InsertMode::New); + } + + void MarkManager::repositionMark( + ::sw::mark::IMark* const io_pMark, + const SwPaM& rPaM) + { + assert(io_pMark->GetMarkPos().GetDoc() == m_pDoc && + "<MarkManager::repositionMark(..)>" + " - Mark is not in my doc."); + MarkBase* const pMarkBase = dynamic_cast< MarkBase* >(io_pMark); + if (!pMarkBase) + return; + + pMarkBase->InvalidateFrames(); + + pMarkBase->SetMarkPos(*(rPaM.GetPoint())); + if(rPaM.HasMark()) + pMarkBase->SetOtherMarkPos(*(rPaM.GetMark())); + else + pMarkBase->ClearOtherMarkPos(); + + if(pMarkBase->GetMarkPos() != pMarkBase->GetMarkStart()) + pMarkBase->Swap(); + + pMarkBase->InvalidateFrames(); + + sortMarks(); + } + + bool MarkManager::renameMark( + ::sw::mark::IMark* io_pMark, + const OUString& rNewName ) + { + assert(io_pMark->GetMarkPos().GetDoc() == m_pDoc && + "<MarkManager::renameMark(..)>" + " - Mark is not in my doc."); + if ( io_pMark->GetName() == rNewName ) + return true; + if (lcl_FindMarkByName(rNewName, m_vAllMarks.begin(), m_vAllMarks.end()) != m_vAllMarks.end()) + return false; + if (::sw::mark::MarkBase* pMarkBase = dynamic_cast< ::sw::mark::MarkBase* >(io_pMark)) + { + const OUString sOldName(pMarkBase->GetName()); + pMarkBase->SetName(rNewName); + + if (dynamic_cast< ::sw::mark::Bookmark* >(io_pMark)) + { + if (m_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + m_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRenameBookmark>(sOldName, rNewName, m_pDoc)); + } + m_pDoc->getIDocumentState().SetModified(); + } + } + return true; + } + + void MarkManager::correctMarksAbsolute( + const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + bool isSortingNeeded = false; + + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + ::sw::mark::MarkBase *const pMark = *ppMark; + // correction of non-existent non-MarkBase instances cannot be done + assert(pMark); + // is on position ?? + bool bChangedPos = false; + if(&pMark->GetMarkPos().nNode.GetNode() == pOldNode) + { + pMark->SetMarkPos(aNewPos); + bChangedPos = true; + isSortingNeeded = true; + } + bool bChangedOPos = false; + if (pMark->IsExpanded() && + &pMark->GetOtherMarkPos().nNode.GetNode() == pOldNode) + { + // shift the OtherMark to aNewPos + pMark->SetOtherMarkPos(aNewPos); + bChangedOPos= true; + isSortingNeeded = true; + } + // illegal selection? collapse the mark and restore sorting later + isSortingNeeded |= lcl_FixCorrectedMark(bChangedPos, bChangedOPos, pMark); + } + + // restore sorting if needed + if(isSortingNeeded) + sortMarks(); + + SAL_INFO("sw.core", "correctMarksAbsolute"); + lcl_DebugMarks(m_vAllMarks); + } + + void MarkManager::correctMarksRelative(const SwNodeIndex& rOldNode, const SwPosition& rNewPos, const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + bool isSortingNeeded = false; + + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + // is on position ?? + bool bChangedPos = false, bChangedOPos = false; + ::sw::mark::MarkBase* const pMark = *ppMark; + // correction of non-existent non-MarkBase instances cannot be done + assert(pMark); + if(&pMark->GetMarkPos().nNode.GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + if (dynamic_cast< ::sw::mark::CrossRefBookmark *>(pMark)) + { + // ensure that cross ref bookmark always starts at 0 + aNewPosRel.nContent = 0; // HACK for WW8 import + isSortingNeeded = true; // and sort them to be safe... + } + aNewPosRel.nContent += pMark->GetMarkPos().nContent.GetIndex(); + pMark->SetMarkPos(aNewPosRel); + bChangedPos = true; + } + if(pMark->IsExpanded() && + &pMark->GetOtherMarkPos().nNode.GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + aNewPosRel.nContent += pMark->GetOtherMarkPos().nContent.GetIndex(); + pMark->SetOtherMarkPos(aNewPosRel); + bChangedOPos = true; + } + // illegal selection? collapse the mark and restore sorting later + isSortingNeeded |= lcl_FixCorrectedMark(bChangedPos, bChangedOPos, pMark); + } + + // restore sorting if needed + if(isSortingNeeded) + sortMarks(); + + SAL_INFO("sw.core", "correctMarksRelative"); + lcl_DebugMarks(m_vAllMarks); + } + + static bool isDeleteMark( + ::sw::mark::MarkBase const*const pMark, + SwNodeIndex const& rStt, + SwNodeIndex const& rEnd, + SwIndex const*const pSttIdx, + SwIndex const*const pEndIdx, + bool & rbIsPosInRange, + bool & rbIsOtherPosInRange) + { + assert(pMark); + // navigator marks should not be moved + // TODO: Check if this might make them invalid + if (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER) + { + return false; + } + + // on position ?? + rbIsPosInRange = lcl_GreaterThan(pMark->GetMarkPos(), rStt, pSttIdx) + && lcl_Lower(pMark->GetMarkPos(), rEnd, pEndIdx); + rbIsOtherPosInRange = pMark->IsExpanded() + && lcl_GreaterThan(pMark->GetOtherMarkPos(), rStt, pSttIdx) + && lcl_Lower(pMark->GetOtherMarkPos(), rEnd, pEndIdx); + // special case: completely in range, touching the end? + if ( pEndIdx != nullptr + && ( ( rbIsOtherPosInRange + && pMark->GetMarkPos().nNode == rEnd + && pMark->GetMarkPos().nContent == *pEndIdx ) + || ( rbIsPosInRange + && pMark->IsExpanded() + && pMark->GetOtherMarkPos().nNode == rEnd + && pMark->GetOtherMarkPos().nContent == *pEndIdx ) ) ) + { + rbIsPosInRange = true; + rbIsOtherPosInRange = true; + } + + if (rbIsPosInRange + && (rbIsOtherPosInRange + || !pMark->IsExpanded())) + { + // completely in range + + bool bDeleteMark = true; + { + switch ( IDocumentMarkAccess::GetType( *pMark ) ) + { + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + // no delete of cross-reference bookmarks, if range is inside one paragraph + bDeleteMark = rStt != rEnd; + break; + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no delete of UNO mark, if it is not expanded and only touches the start of the range + bDeleteMark = rbIsOtherPosInRange + || pMark->IsExpanded() + || pSttIdx == nullptr + || !( pMark->GetMarkPos().nNode == rStt + && pMark->GetMarkPos().nContent == *pSttIdx ); + break; + default: + bDeleteMark = true; + break; + } + } + return bDeleteMark; + } + return false; + } + + bool MarkManager::isBookmarkDeleted(SwPaM const& rPaM) const + { + SwPosition const& rStart(*rPaM.Start()); + SwPosition const& rEnd(*rPaM.End()); + for (auto ppMark = m_vBookmarks.begin(); + ppMark != m_vBookmarks.end(); + ++ppMark) + { + bool bIsPosInRange(false); + bool bIsOtherPosInRange(false); + bool const bDeleteMark = isDeleteMark(*ppMark, + rStart.nNode, rEnd.nNode, &rStart.nContent, &rEnd.nContent, + bIsPosInRange, bIsOtherPosInRange); + if (bDeleteMark + && IDocumentMarkAccess::GetType(**ppMark) == MarkType::BOOKMARK) + { + return true; + } + } + return false; + } + + void MarkManager::deleteMarks( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + std::vector<SaveBookmark>* pSaveBkmk, + const SwIndex* pSttIdx, + const SwIndex* pEndIdx ) + { + std::vector<const_iterator_t> vMarksToDelete; + bool bIsSortingNeeded = false; + + // boolean indicating, if at least one mark has been moved while collecting marks for deletion + bool bMarksMoved = false; + // have marks in the range been skipped instead of deleted + bool bMarksSkipDeletion = false; + + // copy all bookmarks in the move area to a vector storing all position data as offset + // reassignment is performed after the move + for (auto ppMark = m_vAllMarks.begin(); + ppMark != m_vAllMarks.end(); + ++ppMark) + { + ::sw::mark::MarkBase *const pMark = *ppMark; + bool bIsPosInRange(false); + bool bIsOtherPosInRange(false); + bool const bDeleteMark = isDeleteMark(pMark, rStt, rEnd, pSttIdx, pEndIdx, bIsPosInRange, bIsOtherPosInRange); + + if ( bIsPosInRange + && ( bIsOtherPosInRange + || !pMark->IsExpanded() ) ) + { + if ( bDeleteMark ) + { + if ( pSaveBkmk ) + { + pSaveBkmk->push_back( SaveBookmark( *pMark, rStt, pSttIdx ) ); + } + vMarksToDelete.emplace_back(ppMark); + } + else + { + bMarksSkipDeletion = true; + } + } + else if ( bIsPosInRange != bIsOtherPosInRange ) + { + // the bookmark is partially in the range + // move position of that is in the range out of it + + std::unique_ptr< SwPosition > pNewPos; + { + if ( pEndIdx != nullptr ) + { + pNewPos = std::make_unique< SwPosition >( rEnd, *pEndIdx ); + } + else + { + pNewPos = + lcl_FindExpelPosition( rStt, rEnd, bIsPosInRange ? pMark->GetOtherMarkPos() : pMark->GetMarkPos() ); + } + } + + bool bMoveMark = true; + { + switch ( IDocumentMarkAccess::GetType( *pMark ) ) + { + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + // no move of cross-reference bookmarks, if move occurs inside a certain node + bMoveMark = pMark->GetMarkPos().nNode != pNewPos->nNode; + break; + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + // no move of annotation marks, if method is called to collect deleted marks + bMoveMark = pSaveBkmk == nullptr; + break; + default: + bMoveMark = true; + break; + } + } + if ( bMoveMark ) + { + if ( bIsPosInRange ) + pMark->SetMarkPos(*pNewPos); + else + pMark->SetOtherMarkPos(*pNewPos); + bMarksMoved = true; + + // illegal selection? collapse the mark and restore sorting later + bIsSortingNeeded |= lcl_FixCorrectedMark( bIsPosInRange, bIsOtherPosInRange, pMark ); + } + } + } + + { + // fdo#61016 delay the deletion of the fieldmark characters + // to prevent that from deleting the marks on that position + // which would invalidate the iterators in vMarksToDelete + std::vector< std::unique_ptr<ILazyDeleter> > vDelay; + vDelay.reserve(vMarksToDelete.size()); + + // If needed, sort mark containers containing subsets of the marks + // in order to assure sorting. The sorting is critical for the + // deletion of a mark as it is searched in these container for + // deletion. + if ( !vMarksToDelete.empty() && bMarksMoved ) + { + sortSubsetMarks(); + } + // we just remembered the iterators to delete, so we do not need to search + // for the shared_ptr<> (the entry in m_vAllMarks) again + // reverse iteration, since erasing an entry invalidates iterators + // behind it (the iterators in vMarksToDelete are sorted) + for ( std::vector< const_iterator_t >::reverse_iterator pppMark = vMarksToDelete.rbegin(); + pppMark != vMarksToDelete.rend(); + ++pppMark ) + { + vDelay.push_back(deleteMark(*pppMark)); + } + } // scope to kill vDelay + + // also need to sort if both marks were moved and not-deleted because + // the not-deleted marks could be in wrong order vs. the moved ones + if (bIsSortingNeeded || (bMarksMoved && bMarksSkipDeletion)) + { + sortMarks(); + } + + SAL_INFO("sw.core", "deleteMarks"); + lcl_DebugMarks(m_vAllMarks); + } + + namespace { + + struct LazyFieldmarkDeleter : public IDocumentMarkAccess::ILazyDeleter + { + std::unique_ptr<Fieldmark> m_pFieldmark; + SwDoc * m_pDoc; + LazyFieldmarkDeleter(Fieldmark* pMark, SwDoc *const pDoc) + : m_pFieldmark(pMark), m_pDoc(pDoc) + { + assert(m_pFieldmark); + } + virtual ~LazyFieldmarkDeleter() override + { + // note: because of the call chain from SwUndoDelete, the field + // command *cannot* be deleted here as it would create a separate + // SwUndoDelete that's interleaved with the SwHistory of the outer + // one - only delete the CH_TXT_ATR_FIELD*! + m_pFieldmark->ReleaseDoc(m_pDoc); + } + }; + + } + + std::unique_ptr<IDocumentMarkAccess::ILazyDeleter> + MarkManager::deleteMark(const const_iterator_t& ppMark) + { + std::unique_ptr<ILazyDeleter> ret; + if (ppMark.get() == m_vAllMarks.end()) + return ret; + IMark* pMark = *ppMark; + + switch(IDocumentMarkAccess::GetType(*pMark)) + { + case IDocumentMarkAccess::MarkType::BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: + case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: + { + auto const ppBookmark = lcl_FindMark(m_vBookmarks, *ppMark.get()); + if ( ppBookmark != m_vBookmarks.end() ) + { + m_vBookmarks.erase(ppBookmark); + } + else + { + assert(false && + "<MarkManager::deleteMark(..)> - Bookmark not found in Bookmark container."); + } + } + break; + + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + { + auto const ppFieldmark = lcl_FindMark(m_vFieldmarks, *ppMark.get()); + if ( ppFieldmark != m_vFieldmarks.end() ) + { + if(m_pLastActiveFieldmark == *ppFieldmark) + ClearFieldActivation(); + + m_vFieldmarks.erase(ppFieldmark); + ret.reset(new LazyFieldmarkDeleter(dynamic_cast<Fieldmark*>(pMark), m_pDoc)); + } + else + { + assert(false && + "<MarkManager::deleteMark(..)> - Fieldmark not found in Fieldmark container."); + } + } + break; + + case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: + { + auto const ppAnnotationMark = lcl_FindMark(m_vAnnotationMarks, *ppMark.get()); + assert(ppAnnotationMark != m_vAnnotationMarks.end() && + "<MarkManager::deleteMark(..)> - Annotation Mark not found in Annotation Mark container."); + m_vAnnotationMarks.erase(ppAnnotationMark); + } + break; + + case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: + case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: + case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: + // no special marks container + break; + } + DdeBookmark* const pDdeBookmark = dynamic_cast<DdeBookmark*>(pMark); + if (pDdeBookmark) + pDdeBookmark->DeregisterFromDoc(m_pDoc); + //Effective STL Item 27, get a non-const iterator aI at the same + //position as const iterator ppMark was + auto aI = m_vAllMarks.begin(); + std::advance(aI, std::distance<container_t::const_iterator>(aI, ppMark.get())); + + m_vAllMarks.erase(aI); + // If we don't have a lazy deleter + if (!ret) + // delete after we remove from the list, because the destructor can + // recursively call into this method. + delete pMark; + return ret; + } + + void MarkManager::deleteMark(const IMark* const pMark) + { + assert(pMark->GetMarkPos().GetDoc() == m_pDoc && + "<MarkManager::deleteMark(..)>" + " - Mark is not in my doc."); + // finds the last Mark that is starting before pMark + // (pMarkLow < pMark) + auto [it, endIt] = equal_range( + m_vAllMarks.begin(), + m_vAllMarks.end(), + pMark->GetMarkStart(), + CompareIMarkStartsBefore()); + for ( ; it != endIt; ++it) + if (*it == pMark) + { + deleteMark(iterator(it)); + break; + } + } + + void MarkManager::clearAllMarks() + { + ClearFieldActivation(); + m_vFieldmarks.clear(); + m_vBookmarks.clear(); + m_vAnnotationMarks.clear(); + for (const auto & p : m_vAllMarks) + delete p; + m_vAllMarks.clear(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findMark(const OUString& rName) const + { + auto const ret = lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()); + return IDocumentMarkAccess::iterator(ret); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findBookmark(const OUString& rName) const + { + auto const ret = lcl_FindMarkByName(rName, m_vBookmarks.begin(), m_vBookmarks.end()); + return IDocumentMarkAccess::iterator(ret); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAllMarksBegin() const + { return m_vAllMarks.begin(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAllMarksEnd() const + { return m_vAllMarks.end(); } + + sal_Int32 MarkManager::getAllMarksCount() const + { return m_vAllMarks.size(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getBookmarksBegin() const + { return m_vBookmarks.begin(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getBookmarksEnd() const + { return m_vBookmarks.end(); } + + sal_Int32 MarkManager::getBookmarksCount() const + { return m_vBookmarks.size(); } + + // finds the first that is starting after + IDocumentMarkAccess::const_iterator_t MarkManager::findFirstBookmarkStartsAfter(const SwPosition& rPos) const + { + return std::upper_bound( + m_vBookmarks.begin(), + m_vBookmarks.end(), + rPos, + CompareIMarkStartsAfter()); + } + + IFieldmark* MarkManager::getFieldmarkAt(const SwPosition& rPos) const + { + auto const pFieldmark = find_if( + m_vFieldmarks.begin(), + m_vFieldmarks.end(), + [&rPos] (::sw::mark::MarkBase const*const pMark) { + return pMark->GetMarkStart() == rPos + // end position includes the CH_TXT_ATR_FIELDEND + || (pMark->GetMarkEnd().nContent.GetIndex() == rPos.nContent.GetIndex() + 1 + && pMark->GetMarkEnd().nNode == rPos.nNode); + } ); + return (pFieldmark == m_vFieldmarks.end()) + ? nullptr + : dynamic_cast<IFieldmark*>(*pFieldmark); + } + + IFieldmark* MarkManager::getFieldmarkFor(const SwPosition& rPos) const + { + auto itFieldmark = find_if( + m_vFieldmarks.begin(), + m_vFieldmarks.end(), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return pMark->IsCoveringPosition(rPos); } ); + if (itFieldmark == m_vFieldmarks.end()) + return nullptr; + auto pFieldmark(*itFieldmark); + for ( ; itFieldmark != m_vFieldmarks.end() + && (**itFieldmark).IsCoveringPosition(rPos); ++itFieldmark) + { // find the innermost fieldmark + if (pFieldmark->GetMarkStart() < (**itFieldmark).GetMarkStart() + || (**itFieldmark).GetMarkEnd() < pFieldmark->GetMarkEnd()) + { + pFieldmark = *itFieldmark; + } + } + return dynamic_cast<IFieldmark*>(pFieldmark); + } + + void MarkManager::deleteFieldmarkAt(const SwPosition& rPos) + { + auto const pFieldmark = dynamic_cast<Fieldmark*>(getFieldmarkAt(rPos)); + if (!pFieldmark) + return; + + deleteMark(lcl_FindMark(m_vAllMarks, pFieldmark)); + } + + ::sw::mark::IFieldmark* MarkManager::changeFormFieldmarkType(::sw::mark::IFieldmark* pFieldmark, const OUString& rNewType) + { + bool bActualChange = false; + if(rNewType == ODF_FORMDROPDOWN) + { + if (!dynamic_cast<::sw::mark::DropDownFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::CheckboxFieldmark*>(pFieldmark)) // only allowed converting between checkbox <-> dropdown + return nullptr; + } + else if(rNewType == ODF_FORMCHECKBOX) + { + if (!dynamic_cast<::sw::mark::CheckboxFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::DropDownFieldmark*>(pFieldmark)) // only allowed converting between checkbox <-> dropdown + return nullptr; + } + else if(rNewType == ODF_FORMDATE) + { + if (!dynamic_cast<::sw::mark::DateFieldmark*>(pFieldmark)) + bActualChange = true; + if (!dynamic_cast<::sw::mark::TextFieldmark*>(pFieldmark)) // only allowed converting between date field <-> text field + return nullptr; + } + + if (!bActualChange) + return nullptr; + + // Store attributes needed to create the new fieldmark + OUString sName = pFieldmark->GetName(); + SwPaM aPaM(pFieldmark->GetMarkPos()); + + // Remove the old fieldmark and create a new one with the new type + if(aPaM.GetPoint()->nContent > 0 && (rNewType == ODF_FORMDROPDOWN || rNewType == ODF_FORMCHECKBOX)) + { + --aPaM.GetPoint()->nContent; + SwPosition aNewPos (aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent); + deleteFieldmarkAt(aNewPos); + return makeNoTextFieldBookmark(aPaM, sName, rNewType); + } + else if(rNewType == ODF_FORMDATE) + { + SwPosition aPos (aPaM.GetPoint()->nNode, aPaM.GetPoint()->nContent); + SwPaM aNewPaM(pFieldmark->GetMarkStart(), pFieldmark->GetMarkEnd()); + deleteFieldmarkAt(aPos); + // HACK: hard-code the separator position here at the start because + // writerfilter put it in the wrong place (at the end) on attach() + SwPosition const sepPos(*aNewPaM.Start()); + return makeFieldBookmark(aNewPaM, sName, rNewType, &sepPos); + } + return nullptr; + } + + void MarkManager::NotifyCursorUpdate(const SwCursorShell& rCursorShell) + { + SwView* pSwView = dynamic_cast<SwView *>(rCursorShell.GetSfxViewShell()); + if(!pSwView) + return; + + SwEditWin& rEditWin = pSwView->GetEditWin(); + SwPosition aPos(*rCursorShell.GetCursor()->GetPoint()); + IFieldmark* pFieldBM = getFieldmarkFor(aPos); + FieldmarkWithDropDownButton* pNewActiveFieldmark = nullptr; + if ((!pFieldBM || (pFieldBM->GetFieldname() != ODF_FORMDROPDOWN && pFieldBM->GetFieldname() != ODF_FORMDATE)) + && aPos.nContent.GetIndex() > 0 ) + { + --aPos.nContent; + pFieldBM = getFieldmarkFor(aPos); + } + + if ( pFieldBM && (pFieldBM->GetFieldname() == ODF_FORMDROPDOWN || + pFieldBM->GetFieldname() == ODF_FORMDATE)) + { + if (m_pLastActiveFieldmark != pFieldBM) + { + FieldmarkWithDropDownButton& rFormField = dynamic_cast<FieldmarkWithDropDownButton&>(*pFieldBM); + pNewActiveFieldmark = &rFormField; + } + else + { + pNewActiveFieldmark = m_pLastActiveFieldmark; + } + } + + if(pNewActiveFieldmark != m_pLastActiveFieldmark) + { + ClearFieldActivation(); + m_pLastActiveFieldmark = pNewActiveFieldmark; + if(pNewActiveFieldmark) + pNewActiveFieldmark->ShowButton(&rEditWin); + } + } + + void MarkManager::ClearFieldActivation() + { + if(m_pLastActiveFieldmark) + m_pLastActiveFieldmark->RemoveButton(); + + m_pLastActiveFieldmark = nullptr; + } + + IFieldmark* MarkManager::getDropDownFor(const SwPosition& rPos) const + { + IFieldmark *pMark = getFieldmarkAt(rPos); + if (!pMark || pMark->GetFieldname() != ODF_FORMDROPDOWN) + return nullptr; + return pMark; + } + + std::vector<IFieldmark*> MarkManager::getDropDownsFor(const SwPaM &rPaM) const + { + std::vector<IFieldmark*> aRet; + + for (auto aI = m_vFieldmarks.begin(), + aEnd = m_vFieldmarks.end(); aI != aEnd; ++aI) + { + ::sw::mark::IMark* pI = *aI; + const SwPosition &rStart = pI->GetMarkPos(); + if (!rPaM.ContainsPosition(rStart)) + continue; + + IFieldmark *pMark = dynamic_cast<IFieldmark*>(pI); + if (!pMark || pMark->GetFieldname() != ODF_FORMDROPDOWN) + continue; + + aRet.push_back(pMark); + } + + return aRet; + } + + IFieldmark* MarkManager::getFieldmarkAfter(const SwPosition& rPos) const + { return dynamic_cast<IFieldmark*>(lcl_getMarkAfter(m_vFieldmarks, rPos)); } + + IFieldmark* MarkManager::getFieldmarkBefore(const SwPosition& rPos) const + { return dynamic_cast<IFieldmark*>(lcl_getMarkBefore(m_vFieldmarks, rPos)); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAnnotationMarksBegin() const + { + return m_vAnnotationMarks.begin(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::getAnnotationMarksEnd() const + { + return m_vAnnotationMarks.end(); + } + + sal_Int32 MarkManager::getAnnotationMarksCount() const + { + return m_vAnnotationMarks.size(); + } + + IDocumentMarkAccess::const_iterator_t MarkManager::findAnnotationMark( const OUString& rName ) const + { + auto const ret = lcl_FindMarkByName( rName, m_vAnnotationMarks.begin(), m_vAnnotationMarks.end() ); + return IDocumentMarkAccess::iterator(ret); + } + + IMark* MarkManager::getAnnotationMarkFor(const SwPosition& rPos) const + { + auto const pAnnotationMark = find_if( + m_vAnnotationMarks.begin(), + m_vAnnotationMarks.end(), + [&rPos] (const ::sw::mark::MarkBase *const pMark) { return pMark->IsCoveringPosition(rPos); } ); + if (pAnnotationMark == m_vAnnotationMarks.end()) + return nullptr; + return *pAnnotationMark; + } + + // finds the first that is starting after + IDocumentMarkAccess::const_iterator_t MarkManager::findFirstAnnotationStartsAfter(const SwPosition& rPos) const + { + return std::upper_bound( + m_vAnnotationMarks.begin(), + m_vAnnotationMarks.end(), + rPos, + CompareIMarkStartsAfter()); + } + + OUString MarkManager::getUniqueMarkName(const OUString& rName) const + { + OSL_ENSURE(rName.getLength(), + "<MarkManager::getUniqueMarkName(..)> - a name should be proposed"); + if( m_pDoc->IsInMailMerge()) + { + OUString newName = rName + "MailMergeMark" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( m_vAllMarks.size() + 1 ); + return newName; + } + + if (lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()) == m_vAllMarks.end()) + { + return rName; + } + OUString sTmp; + + // try the name "<rName>XXX" (where XXX is a number starting from 1) unless there is + // an unused name. Due to performance-reasons (especially in mailmerge-scenarios) there + // is a map m_aMarkBasenameMapUniqueOffset which holds the next possible offset (XXX) for + // rName (so there is no need to test for nCnt-values smaller than the offset). + sal_Int32 nCnt = 1; + MarkBasenameMapUniqueOffset_t::const_iterator aIter = m_aMarkBasenameMapUniqueOffset.find(rName); + if(aIter != m_aMarkBasenameMapUniqueOffset.end()) nCnt = aIter->second; + while(nCnt < SAL_MAX_INT32) + { + sTmp = rName + OUString::number(nCnt); + nCnt++; + if (lcl_FindMarkByName(sTmp, m_vAllMarks.begin(), m_vAllMarks.end()) == m_vAllMarks.end()) + { + break; + } + } + m_aMarkBasenameMapUniqueOffset[rName] = nCnt; + + return sTmp; + } + + void MarkManager::assureSortedMarkContainers() const + { + const_cast< MarkManager* >(this)->sortMarks(); + } + + void MarkManager::sortSubsetMarks() + { + sort(m_vBookmarks.begin(), m_vBookmarks.end(), &lcl_MarkOrderingByStart); + sort(m_vFieldmarks.begin(), m_vFieldmarks.end(), &lcl_MarkOrderingByStart); + sort(m_vAnnotationMarks.begin(), m_vAnnotationMarks.end(), &lcl_MarkOrderingByStart); + } + + void MarkManager::sortMarks() + { + sort(m_vAllMarks.begin(), m_vAllMarks.end(), &lcl_MarkOrderingByStart); + sortSubsetMarks(); + } + +void MarkManager::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + struct + { + const char* pName; + const container_t* pContainer; + } aContainers[] = + { + // UNO marks are only part of all marks. + {"allmarks", &m_vAllMarks}, + {"bookmarks", &m_vBookmarks}, + {"fieldmarks", &m_vFieldmarks}, + {"annotationmarks", &m_vAnnotationMarks} + }; + + xmlTextWriterStartElement(pWriter, BAD_CAST("MarkManager")); + for (const auto & rContainer : aContainers) + { + if (!rContainer.pContainer->empty()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST(rContainer.pName)); + for (auto it = rContainer.pContainer->begin(); it != rContainer.pContainer->end(); ++it) + (*it)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + } + xmlTextWriterEndElement(pWriter); +} + +} // namespace ::sw::mark + +namespace +{ + bool lcl_Greater( const SwPosition& rPos, const SwNodeIndex& rNdIdx, const SwIndex* pIdx ) + { + return rPos.nNode > rNdIdx || ( pIdx && rPos.nNode == rNdIdx && rPos.nContent > pIdx->GetIndex() ); + } +} + +// IDocumentMarkAccess for SwDoc +IDocumentMarkAccess* SwDoc::getIDocumentMarkAccess() + { return static_cast< IDocumentMarkAccess* >(mpMarkManager.get()); } + +const IDocumentMarkAccess* SwDoc::getIDocumentMarkAccess() const + { return static_cast< IDocumentMarkAccess* >(mpMarkManager.get()); } + +SaveBookmark::SaveBookmark( + const IMark& rBkmk, + const SwNodeIndex & rMvPos, + const SwIndex* pIdx) + : m_aName(rBkmk.GetName()) + , m_aShortName() + , m_aCode() + , m_eOrigBkmType(IDocumentMarkAccess::GetType(rBkmk)) +{ + const IBookmark* const pBookmark = dynamic_cast< const IBookmark* >(&rBkmk); + if(pBookmark) + { + m_aShortName = pBookmark->GetShortName(); + m_aCode = pBookmark->GetKeyCode(); + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pBookmark)); + if (pMetadatable) + { + m_pMetadataUndo = pMetadatable->CreateUndo(); + } + } + m_nNode1 = rBkmk.GetMarkPos().nNode.GetIndex(); + m_nContent1 = rBkmk.GetMarkPos().nContent.GetIndex(); + + m_nNode1 -= rMvPos.GetIndex(); + if(pIdx && !m_nNode1) + m_nContent1 -= pIdx->GetIndex(); + + if(rBkmk.IsExpanded()) + { + m_nNode2 = rBkmk.GetOtherMarkPos().nNode.GetIndex(); + m_nContent2 = rBkmk.GetOtherMarkPos().nContent.GetIndex(); + + m_nNode2 -= rMvPos.GetIndex(); + if(pIdx && !m_nNode2) + m_nContent2 -= pIdx->GetIndex(); + } + else + { + m_nNode2 = ULONG_MAX; + m_nContent2 = -1; + } +} + +void SaveBookmark::SetInDoc( + SwDoc* pDoc, + const SwNodeIndex& rNewPos, + const SwIndex* pIdx) +{ + SwPaM aPam(rNewPos.GetNode()); + if(pIdx) + aPam.GetPoint()->nContent = *pIdx; + + if(ULONG_MAX != m_nNode2) + { + aPam.SetMark(); + + aPam.GetMark()->nNode += m_nNode2; + if(pIdx && !m_nNode2) + aPam.GetMark()->nContent += m_nContent2; + else + aPam.GetMark()->nContent.Assign(aPam.GetContentNode(false), m_nContent2); + } + + aPam.GetPoint()->nNode += m_nNode1; + + if(pIdx && !m_nNode1) + aPam.GetPoint()->nContent += m_nContent1; + else + aPam.GetPoint()->nContent.Assign(aPam.GetContentNode(), m_nContent1); + + if(!aPam.HasMark() + || CheckNodesRange(aPam.GetPoint()->nNode, aPam.GetMark()->nNode, true)) + { + ::sw::mark::IBookmark* const pBookmark = dynamic_cast<::sw::mark::IBookmark*>( + pDoc->getIDocumentMarkAccess()->makeMark(aPam, m_aName, + m_eOrigBkmType, sw::mark::InsertMode::New)); + if(pBookmark) + { + pBookmark->SetKeyCode(m_aCode); + pBookmark->SetShortName(m_aShortName); + if (m_pMetadataUndo) + { + ::sfx2::Metadatable * const pMeta( + dynamic_cast< ::sfx2::Metadatable* >(pBookmark)); + assert(pMeta && "metadata undo, but not metadatable?"); + if (pMeta) + { + pMeta->RestoreMetadata(m_pMetadataUndo); + } + } + } + } +} + +// DelBookmarks + +void DelBookmarks( + const SwNodeIndex& rStt, + const SwNodeIndex& rEnd, + std::vector<SaveBookmark> * pSaveBkmk, + const SwIndex* pSttIdx, + const SwIndex* pEndIdx) +{ + // illegal range ?? + if(rStt.GetIndex() > rEnd.GetIndex() + || (rStt == rEnd && (!pSttIdx || !pEndIdx || pSttIdx->GetIndex() >= pEndIdx->GetIndex()))) + return; + SwDoc* const pDoc = rStt.GetNode().GetDoc(); + + pDoc->getIDocumentMarkAccess()->deleteMarks(rStt, rEnd, pSaveBkmk, pSttIdx, pEndIdx); + + // Copy all Redlines which are in the move area into an array + // which holds all position information as offset. + // Assignment happens after moving. + SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + // Is at position? + SwPosition *const pRStt = pRedl->Start(); + SwPosition *const pREnd = pRedl->End(); + + if( lcl_Greater( *pRStt, rStt, pSttIdx ) && lcl_Lower( *pRStt, rEnd, pEndIdx )) + { + pRStt->nNode = rEnd; + if( pEndIdx ) + pRStt->nContent = *pEndIdx; + else + { + bool bStt = true; + SwContentNode* pCNd = pRStt->nNode.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &pRStt->nNode ); + if (!pCNd) + { + bStt = false; + pRStt->nNode = rStt; + if( nullptr == ( pCNd = SwNodes::GoPrevious( &pRStt->nNode )) ) + { + pRStt->nNode = pREnd->nNode; + pCNd = pRStt->nNode.GetNode().GetContentNode(); + } + } + pRStt->nContent.Assign( pCNd, bStt ? 0 : pCNd->Len() ); + } + } + if( lcl_Greater( *pREnd, rStt, pSttIdx ) && lcl_Lower( *pREnd, rEnd, pEndIdx )) + { + pREnd->nNode = rStt; + if( pSttIdx ) + pREnd->nContent = *pSttIdx; + else + { + bool bStt = false; + SwContentNode* pCNd = pREnd->nNode.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = SwNodes::GoPrevious( &pREnd->nNode ); + if( !pCNd ) + { + bStt = true; + pREnd->nNode = rEnd; + if( nullptr == ( pCNd = pDoc->GetNodes().GoNext( &pREnd->nNode )) ) + { + pREnd->nNode = pRStt->nNode; + pCNd = pREnd->nNode.GetNode().GetContentNode(); + } + } + pREnd->nContent.Assign( pCNd, bStt ? 0 : pCNd->Len() ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docchart.cxx b/sw/source/core/doc/docchart.cxx new file mode 100644 index 000000000..4acfb5119 --- /dev/null +++ b/sw/source/core/doc/docchart.cxx @@ -0,0 +1,179 @@ +/* -*- 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 <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <ndindex.hxx> +#include <swtable.hxx> +#include <viewsh.hxx> +#include <ndole.hxx> +#include <swtblfmt.hxx> +#include <tblsel.hxx> +#include <frameformats.hxx> +#include <unochart.hxx> + +void SwTable::UpdateCharts() const +{ + GetFrameFormat()->GetDoc()->UpdateCharts( GetFrameFormat()->GetName() ); +} + +bool SwTable::IsTableComplexForChart( const OUString& rSelection ) const +{ + const SwTableBox* pSttBox, *pEndBox; + if( 2 < rSelection.getLength() ) + { + const sal_Int32 nSeparator {rSelection.indexOf( ':' )}; + OSL_ENSURE( -1 != nSeparator, "no valid selection" ); + + // Remove brackets at the beginning and from the end + const sal_Int32 nOffset {'<' == rSelection[0] ? 1 : 0}; + const sal_Int32 nLength {'>' == rSelection[ rSelection.getLength()-1 ] + ? rSelection.getLength()-1 : rSelection.getLength()}; + + pSttBox = GetTableBox(rSelection.copy( nOffset, nSeparator - nOffset )); + pEndBox = GetTableBox(rSelection.copy( nSeparator+1, nLength - (nSeparator+1) )); + } + else + { + const SwTableLines* pLns = &GetTabLines(); + pSttBox = (*pLns)[ 0 ]->GetTabBoxes().front(); + while( !pSttBox->GetSttNd() ) + // Until the Content Box! + pSttBox = pSttBox->GetTabLines().front()->GetTabBoxes().front(); + + const SwTableBoxes* pBoxes = &pLns->back()->GetTabBoxes(); + pEndBox = pBoxes->back(); + while( !pEndBox->GetSttNd() ) + { + // Until the Content Box! + pLns = &pEndBox->GetTabLines(); + pBoxes = &pLns->back()->GetTabBoxes(); + pEndBox = pBoxes->back(); + } + } + + return !pSttBox || !pEndBox || !::ChkChartSel( *pSttBox->GetSttNd(), + *pEndBox->GetSttNd() ); +} + +void SwDoc::DoUpdateAllCharts() +{ + SwViewShell* pVSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pVSh ) + { + const SwFrameFormats& rTableFormats = *GetTableFrameFormats(); + for( size_t n = 0; n < rTableFormats.size(); ++n ) + { + const SwFrameFormat* pFormat = rTableFormats[ n ]; + if( SwTable* pTmpTable = SwTable::FindTable( pFormat ) ) + if( const SwTableNode* pTableNd = pTmpTable->GetTableNode() ) + if( pTableNd->GetNodes().IsDocNodes() ) + { + UpdateCharts_( *pTmpTable, *pVSh ); + } + } + } +} + +void SwDoc::UpdateCharts_( const SwTable& rTable, SwViewShell const & rVSh ) const +{ + OUString aName( rTable.GetFrameFormat()->GetName() ); + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwOLENode *pONd = aIdx.GetNode().GetOLENode(); + if( pONd && + aName == pONd->GetChartTableName() && + pONd->getLayoutFrame( rVSh.GetLayout() ) ) + { + SwChartDataProvider *pPCD = getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + pPCD->InvalidateTable( &rTable ); + // following this the framework will now take care of repainting + // the chart or it's replacement image... + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } +} + +void SwDoc::UpdateCharts( const OUString &rName ) const +{ + SwTable* pTmpTable = SwTable::FindTable( FindTableFormatByName( rName ) ); + if( pTmpTable ) + { + SwViewShell const * pVSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + + if( pVSh ) + UpdateCharts_( *pTmpTable, *pVSh ); + } +} + +void SwDoc::SetTableName( SwFrameFormat& rTableFormat, const OUString &rNewName ) +{ + const OUString aOldName( rTableFormat.GetName() ); + + bool bNameFound = rNewName.isEmpty(); + if( !bNameFound ) + { + const SwFrameFormats& rTable = *GetTableFrameFormats(); + for( size_t i = rTable.size(); i; ) + { + const SwFrameFormat* pFormat = rTable[ --i ]; + if( !pFormat->IsDefault() && + pFormat->GetName() == rNewName && IsUsed( *pFormat ) ) + { + bNameFound = true; + break; + } + } + } + + if( !bNameFound ) + rTableFormat.SetName( rNewName, true ); + else + rTableFormat.SetName( GetUniqueTableName(), true ); + + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwOLENode *pNd = aIdx.GetNode().GetOLENode(); + if( pNd && aOldName == pNd->GetChartTableName() ) + { + pNd->SetChartTableName( rNewName ); + + SwTable* pTable = SwTable::FindTable( &rTableFormat ); + SwChartDataProvider *pPCD = getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + pPCD->InvalidateTable( pTable ); + // following this the framework will now take care of repainting + // the chart or it's replacement image... + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + getIDocumentState().SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doccomp.cxx b/sw/source/core/doc/doccomp.cxx new file mode 100644 index 000000000..1fe455f50 --- /dev/null +++ b/sw/source/core/doc/doccomp.cxx @@ -0,0 +1,2698 @@ +/* -*- 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 <sal/config.h> + +#include <rtl/ustrbuf.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <rtl/character.hxx> +#include <swmodule.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <redline.hxx> +#include <UndoRedline.hxx> +#include <section.hxx> +#include <tox.hxx> +#include <docsh.hxx> +#include <fmtcntnt.hxx> +#include <modcfg.hxx> +#include <frameformats.hxx> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <cstddef> +#include <memory> +#include <vector> + +using namespace ::com::sun::star; + +using std::vector; + +namespace { + +class SwCompareLine +{ + const SwNode& m_rNode; +public: + explicit SwCompareLine( const SwNode& rNd ) : m_rNode( rNd ) {} + + sal_uLong GetHashValue() const; + bool Compare( const SwCompareLine& rLine ) const; + + static sal_uLong GetTextNodeHashValue( const SwTextNode& rNd, sal_uLong nVal ); + static bool CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd ); + static bool CompareTextNd( const SwTextNode& rDstNd, + const SwTextNode& rSrcNd ); + + bool ChangesInLine( const SwCompareLine& rLine, + std::unique_ptr<SwPaM>& rpInsRing, std::unique_ptr<SwPaM>& rpDelRing ) const; + + const SwNode& GetNode() const { return m_rNode; } + + const SwNode& GetEndNode() const; + + // for debugging + OUString GetText() const; +}; + + +class CompareData +{ +protected: + SwDoc& m_rDoc; +private: + std::unique_ptr<size_t[]> m_pIndex; + std::unique_ptr<bool[]> m_pChangedFlag; + + std::unique_ptr<SwPaM> m_pInsertRing, m_pDelRing; + + static sal_uLong PrevIdx( const SwNode* pNd ); + static sal_uLong NextIdx( const SwNode* pNd ); + + vector< SwCompareLine* > m_aLines; + bool m_bRecordDiff; + + // Truncate beginning and end and add all others to the LinesArray + void CheckRanges( CompareData& ); + + virtual const SwNode& GetEndOfContent() = 0; + +public: + CompareData(SwDoc& rD, bool bRecordDiff) + : m_rDoc( rD ) + , m_bRecordDiff(bRecordDiff) + { + } + virtual ~CompareData(); + + // Are there differences? + bool HasDiffs( const CompareData& rData ) const; + + // Triggers the comparison and creation of two documents + void CompareLines( CompareData& rData ); + // Display the differences - calls the methods ShowInsert and ShowDelete. + // These are passed the start and end line number. + // Displaying the actually content is to be handled by the subclass! + sal_uLong ShowDiffs( const CompareData& rData ); + + void ShowInsert( sal_uLong nStt, sal_uLong nEnd ); + void ShowDelete( const CompareData& rData, sal_uLong nStt, + sal_uLong nEnd, sal_uLong nInsPos ); + void CheckForChangesInLine( const CompareData& rData, + sal_uLong nStt, sal_uLong nEnd, + sal_uLong nThisStt, sal_uLong nThisEnd ); + + // Set non-ambiguous index for a line. Same lines have the same index, even in the other CompareData! + void SetIndex( size_t nLine, size_t nIndex ); + size_t GetIndex( size_t nLine ) const + { return nLine < m_aLines.size() ? m_pIndex[ nLine ] : 0; } + + // Set/get of a line has changed + void SetChanged( size_t nLine, bool bFlag = true ); + bool GetChanged( size_t nLine ) const + { + return (m_pChangedFlag && nLine < m_aLines.size()) + && m_pChangedFlag[ nLine ]; + } + + size_t GetLineCount() const { return m_aLines.size(); } + const SwCompareLine* GetLine( size_t nLine ) const + { return m_aLines[ nLine ]; } + void InsertLine( SwCompareLine* pLine ) + { m_aLines.push_back( pLine ); } + + void SetRedlinesToDoc( bool bUseDocInfo ); +}; + +class CompareMainText : public CompareData +{ +public: + CompareMainText(SwDoc &rD, bool bRecordDiff) + : CompareData(rD, bRecordDiff) + { + } + + virtual const SwNode& GetEndOfContent() override + { + return m_rDoc.GetNodes().GetEndOfContent(); + } +}; + +class CompareFrameFormatText : public CompareData +{ + const SwNodeIndex &m_rIndex; +public: + CompareFrameFormatText(SwDoc &rD, const SwNodeIndex &rIndex) + : CompareData(rD, true/*bRecordDiff*/) + , m_rIndex(rIndex) + { + } + + virtual const SwNode& GetEndOfContent() override + { + return *m_rIndex.GetNode().EndOfSectionNode(); + } +}; + +class Hash +{ + struct HashData + { + sal_uLong nNext, nHash; + const SwCompareLine* pLine; + + HashData() + : nNext( 0 ), nHash( 0 ), pLine(nullptr) {} + }; + + std::unique_ptr<sal_uLong[]> m_pHashArr; + std::unique_ptr<HashData[]> m_pDataArr; + sal_uLong m_nCount, m_nPrime; + +public: + explicit Hash( sal_uLong nSize ); + + void CalcHashValue( CompareData& rData ); + + sal_uLong GetCount() const { return m_nCount; } +}; + +class Compare +{ +public: + class MovedData + { + std::unique_ptr<sal_uLong[]> m_pIndex; + std::unique_ptr<sal_uLong[]> m_pLineNum; + sal_uLong m_nCount; + + public: + MovedData( CompareData& rData, const char* pDiscard ); + + sal_uLong GetIndex( sal_uLong n ) const { return m_pIndex[ n ]; } + sal_uLong GetLineNum( sal_uLong n ) const { return m_pLineNum[ n ]; } + sal_uLong GetCount() const { return m_nCount; } + }; + +private: + /// Look for the moved lines + class CompareSequence + { + CompareData &m_rData1, &m_rData2; + const MovedData &m_rMoved1, &m_rMoved2; + std::unique_ptr<long[]> m_pMemory; + long *m_pFDiag, *m_pBDiag; + + void Compare( sal_uLong nStt1, sal_uLong nEnd1, sal_uLong nStt2, sal_uLong nEnd2 ); + sal_uLong CheckDiag( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost ); + public: + CompareSequence( CompareData& rD1, CompareData& rD2, + const MovedData& rMD1, const MovedData& rMD2 ); + }; + + static void CountDifference( const CompareData& rData, sal_uLong* pCounts ); + static void SetDiscard( const CompareData& rData, + char* pDiscard, const sal_uLong* pCounts ); + static void CheckDiscard( sal_uLong nLen, char* pDiscard ); + static void ShiftBoundaries( CompareData& rData1, CompareData& rData2 ); + +public: + Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 ); +}; + +class ArrayComparator +{ +public: + virtual bool Compare( int nIdx1, int nIdx2 ) const = 0; + virtual int GetLen1() const = 0; + virtual int GetLen2() const = 0; + virtual ~ArrayComparator() {} +}; + +/// Consider two lines equal if similar enough (e.g. look like different +/// versions of the same paragraph) +class LineArrayComparator : public ArrayComparator +{ +private: + int m_nLen1, m_nLen2; + const CompareData &m_rData1, &m_rData2; + int m_nFirst1, m_nFirst2; + +public: + LineArrayComparator( const CompareData &rD1, const CompareData &rD2, + int nStt1, int nEnd1, int nStt2, int nEnd2 ); + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_nLen1; } + virtual int GetLen2() const override { return m_nLen2; } +}; + +class WordArrayComparator : public ArrayComparator +{ +private: + const SwTextNode *m_pTextNode1, *m_pTextNode2; + std::unique_ptr<int[]> m_pPos1, m_pPos2; + int m_nCount1, m_nCount2; // number of words + + static void CalcPositions( int *pPos, const SwTextNode *pTextNd, int &nCnt ); + +public: + WordArrayComparator( const SwTextNode *pNode1, const SwTextNode *pNode2 ); + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_nCount1; } + virtual int GetLen2() const override { return m_nCount2; } + int GetCharSequence( const int *pWordLcs1, const int *pWordLcs2, + int *pSubseq1, int *pSubseq2, int nLcsLen ); +}; + +class CharArrayComparator : public ArrayComparator +{ +private: + const SwTextNode *m_pTextNode1, *m_pTextNode2; + +public: + CharArrayComparator( const SwTextNode *pNode1, const SwTextNode *pNode2 ) + : m_pTextNode1( pNode1 ), m_pTextNode2( pNode2 ) + { + } + + virtual bool Compare( int nIdx1, int nIdx2 ) const override; + virtual int GetLen1() const override { return m_pTextNode1->GetText().getLength(); } + virtual int GetLen2() const override { return m_pTextNode2->GetText().getLength(); } +}; + +/// Options set in Tools->Options->Writer->Comparison +struct CmpOptionsContainer +{ + SwCompareMode eCmpMode; + int nIgnoreLen; + bool bUseRsid; +}; + +} + +static CmpOptionsContainer CmpOptions; + +namespace { + +class CommonSubseq +{ +private: + std::unique_ptr<int[]> m_pData; + +protected: + ArrayComparator &m_rComparator; + + CommonSubseq( ArrayComparator &rComparator, int nMaxSize ) + : m_rComparator( rComparator ) + { + m_pData.reset( new int[ nMaxSize ] ); + } + + int FindLCS( int *pLcs1, int *pLcs2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ); + +public: + static int IgnoreIsolatedPieces( int *pLcs1, int *pLcs2, int nLen1, int nLen2, + int nLcsLen, int nPieceLen ); +}; + +/// Use Hirschberg's algorithm to find LCS in linear space +class LgstCommonSubseq: public CommonSubseq +{ +private: + static const int CUTOFF = 1<<20; // Stop recursion at this value + + std::unique_ptr<int[]> m_pL1, m_pL2; + std::unique_ptr<int[]> m_pBuff1, m_pBuff2; + + void FindL( int *pL, int nStt1, int nEnd1, int nStt2, int nEnd2 ); + int HirschbergLCS( int *pLcs1, int *pLcs2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ); + +public: + explicit LgstCommonSubseq( ArrayComparator &rComparator ); + + int Find( int *pSubseq1, int *pSubseq2 ); +}; + +/// Find a common subsequence in linear time +class FastCommonSubseq: private CommonSubseq +{ +private: + static const int CUTOFF = 2056; + + int FindFastCS( int *pSeq1, int *pSeq2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ); + +public: + explicit FastCommonSubseq( ArrayComparator &rComparator ) + : CommonSubseq( rComparator, CUTOFF ) + { + } + + int Find( int *pSubseq1, int *pSubseq2 ) + { + return FindFastCS( pSubseq1, pSubseq2, 0, m_rComparator.GetLen1(), + 0, m_rComparator.GetLen2() ); + } +}; + +} + +CompareData::~CompareData() +{ + if( m_pDelRing ) + { + while( m_pDelRing->GetNext() != m_pDelRing.get() ) + delete m_pDelRing->GetNext(); + m_pDelRing.reset(); + } + if( m_pInsertRing ) + { + while( m_pInsertRing->GetNext() != m_pInsertRing.get() ) + delete m_pInsertRing->GetNext(); + m_pInsertRing.reset(); + } +} + +void CompareData::SetIndex( size_t nLine, size_t nIndex ) +{ + if( !m_pIndex ) + { + m_pIndex.reset( new size_t[ m_aLines.size() ] ); + memset( m_pIndex.get(), 0, m_aLines.size() * sizeof( size_t ) ); + } + if( nLine < m_aLines.size() ) + m_pIndex[ nLine ] = nIndex; +} + +void CompareData::SetChanged( size_t nLine, bool bFlag ) +{ + if( !m_pChangedFlag ) + { + m_pChangedFlag.reset( new bool[ m_aLines.size() +1 ] ); + memset( m_pChangedFlag.get(), 0, (m_aLines.size() +1) * sizeof( bool ) ); + } + if( nLine < m_aLines.size() ) + m_pChangedFlag[ nLine ] = bFlag; +} + +void CompareData::CompareLines( CompareData& rData ) +{ + CheckRanges( rData ); + + sal_uLong nDifferent; + { + Hash aH( GetLineCount() + rData.GetLineCount() + 1 ); + aH.CalcHashValue( *this ); + aH.CalcHashValue( rData ); + nDifferent = aH.GetCount(); + } + { + Compare aComp( nDifferent, *this, rData ); + } +} + +sal_uLong CompareData::ShowDiffs( const CompareData& rData ) +{ + sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount(); + sal_uLong nStt1 = 0, nStt2 = 0; + sal_uLong nCnt = 0; + + while( nStt1 < nLen1 || nStt2 < nLen2 ) + { + if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) ) + { + // Find a region of different lines between two pairs of identical + // lines. + sal_uLong nSav1 = nStt1, nSav2 = nStt2; + while( nStt1 < nLen1 && rData.GetChanged( nStt1 )) ++nStt1; + while( nStt2 < nLen2 && GetChanged( nStt2 )) ++nStt2; + + if (m_bRecordDiff) + { + // Check if there are changed lines (only slightly different) and + // compare them in detail. + CheckForChangesInLine( rData, nSav1, nStt1, nSav2, nStt2 ); + } + + ++nCnt; + } + ++nStt1; + ++nStt2; + } + return nCnt; +} + +bool CompareData::HasDiffs( const CompareData& rData ) const +{ + bool bRet = false; + sal_uLong nLen1 = rData.GetLineCount(), nLen2 = GetLineCount(); + sal_uLong nStt1 = 0, nStt2 = 0; + + while( nStt1 < nLen1 || nStt2 < nLen2 ) + { + if( rData.GetChanged( nStt1 ) || GetChanged( nStt2 ) ) + { + bRet = true; + break; + } + ++nStt1; + ++nStt2; + } + return bRet; +} + +Hash::Hash( sal_uLong nSize ) + : m_nCount(1) +{ + + static const sal_uLong primes[] = + { + 509, + 1021, + 2039, + 4093, + 8191, + 16381, + 32749, + 65521, + 131071, + 262139, + 524287, + 1048573, + 2097143, + 4194301, + 8388593, + 16777213, + 33554393, + 67108859, /* Preposterously large . . . */ + 134217689, + 268435399, + 536870909, + 1073741789, + 2147483647, + 0 + }; + int i; + + m_pDataArr.reset( new HashData[ nSize ] ); + m_pDataArr[0].nNext = 0; + m_pDataArr[0].nHash = 0; + m_pDataArr[0].pLine = nullptr; + m_nPrime = primes[0]; + + for( i = 0; primes[i] < nSize / 3; i++) + if( !primes[i] ) + { + m_pHashArr = nullptr; + return; + } + m_nPrime = primes[ i ]; + m_pHashArr.reset( new sal_uLong[ m_nPrime ] ); + memset( m_pHashArr.get(), 0, m_nPrime * sizeof( sal_uLong ) ); +} + +void Hash::CalcHashValue( CompareData& rData ) +{ + if( m_pHashArr ) + { + for( size_t n = 0; n < rData.GetLineCount(); ++n ) + { + const SwCompareLine* pLine = rData.GetLine( n ); + OSL_ENSURE( pLine, "where is the line?" ); + sal_uLong nH = pLine->GetHashValue(); + + sal_uLong* pFound = &m_pHashArr[ nH % m_nPrime ]; + size_t i; + for( i = *pFound; ; i = m_pDataArr[i].nNext ) + if( !i ) + { + i = m_nCount++; + m_pDataArr[i].nNext = *pFound; + m_pDataArr[i].nHash = nH; + m_pDataArr[i].pLine = pLine; + *pFound = i; + break; + } + else if( m_pDataArr[i].nHash == nH && + m_pDataArr[i].pLine->Compare( *pLine )) + break; + + rData.SetIndex( n, i ); + } + } +} + +Compare::Compare( sal_uLong nDiff, CompareData& rData1, CompareData& rData2 ) +{ + std::unique_ptr<MovedData> pMD1, pMD2; + // Look for the differing lines + { + std::unique_ptr<char[]> pDiscard1( new char[ rData1.GetLineCount() ] ); + std::unique_ptr<char[]> pDiscard2( new char[ rData2.GetLineCount() ] ); + + std::unique_ptr<sal_uLong[]> pCount1(new sal_uLong[ nDiff ]); + std::unique_ptr<sal_uLong[]> pCount2(new sal_uLong[ nDiff ]); + memset( pCount1.get(), 0, nDiff * sizeof( sal_uLong )); + memset( pCount2.get(), 0, nDiff * sizeof( sal_uLong )); + + // find indices in CompareData which have been assigned multiple times + CountDifference( rData1, pCount1.get() ); + CountDifference( rData2, pCount2.get() ); + + // All which occur only once now have either been inserted or deleted. + // All which are also contained in the other one have been moved. + SetDiscard( rData1, pDiscard1.get(), pCount2.get() ); + SetDiscard( rData2, pDiscard2.get(), pCount1.get() ); + + CheckDiscard( rData1.GetLineCount(), pDiscard1.get() ); + CheckDiscard( rData2.GetLineCount(), pDiscard2.get() ); + + pMD1.reset(new MovedData( rData1, pDiscard1.get() )); + pMD2.reset(new MovedData( rData2, pDiscard2.get() )); + } + + { + CompareSequence aTmp( rData1, rData2, *pMD1, *pMD2 ); + } + + ShiftBoundaries( rData1, rData2 ); +} + +void Compare::CountDifference( const CompareData& rData, sal_uLong* pCounts ) +{ + sal_uLong nLen = rData.GetLineCount(); + for( sal_uLong n = 0; n < nLen; ++n ) + { + sal_uLong nIdx = rData.GetIndex( n ); + ++pCounts[ nIdx ]; + } +} + +void Compare::SetDiscard( const CompareData& rData, + char* pDiscard, const sal_uLong* pCounts ) +{ + const sal_uLong nLen = rData.GetLineCount(); + + // calculate Max with respect to the line count + sal_uLong nMax = 5; + + for( sal_uLong n = nLen / 64; ( n = n >> 2 ) > 0; ) + nMax <<= 1; + + for( sal_uLong n = 0; n < nLen; ++n ) + { + sal_uLong nIdx = rData.GetIndex( n ); + if( nIdx ) + { + nIdx = pCounts[ nIdx ]; + pDiscard[ n ] = !nIdx ? 1 : nIdx > nMax ? 2 : 0; + } + else + pDiscard[ n ] = 0; + } +} + +void Compare::CheckDiscard( sal_uLong nLen, char* pDiscard ) +{ + for( sal_uLong n = 0; n < nLen; ++n ) + { + if( 2 == pDiscard[ n ] ) + pDiscard[n] = 0; + else if( pDiscard[ n ] ) + { + sal_uLong j; + sal_uLong length; + sal_uLong provisional = 0; + + /* Find end of this run of discardable lines. + Count how many are provisionally discardable. */ + for (j = n; j < nLen; j++) + { + if( !pDiscard[j] ) + break; + if( 2 == pDiscard[j] ) + ++provisional; + } + + /* Cancel provisional discards at end, and shrink the run. */ + while( j > n && 2 == pDiscard[j - 1] ) + { + pDiscard[ --j ] = 0; + --provisional; + } + + /* Now we have the length of a run of discardable lines + whose first and last are not provisional. */ + length = j - n; + + /* If 1/4 of the lines in the run are provisional, + cancel discarding of all provisional lines in the run. */ + if (provisional * 4 > length) + { + while (j > n) + if (pDiscard[--j] == 2) + pDiscard[j] = 0; + } + else + { + sal_uLong consec; + sal_uLong minimum = 1; + sal_uLong tem = length / 4; + + /* MINIMUM is approximate square root of LENGTH/4. + A subrun of two or more provisionals can stand + when LENGTH is at least 16. + A subrun of 4 or more can stand when LENGTH >= 64. */ + while ((tem = tem >> 2) > 0) + minimum *= 2; + minimum++; + + /* Cancel any subrun of MINIMUM or more provisionals + within the larger run. */ + for (j = 0, consec = 0; j < length; j++) + if (pDiscard[n + j] != 2) + consec = 0; + else if (minimum == ++consec) + /* Back up to start of subrun, to cancel it all. */ + j -= consec; + else if (minimum < consec) + pDiscard[n + j] = 0; + + /* Scan from beginning of run + until we find 3 or more nonprovisionals in a row + or until the first nonprovisional at least 8 lines in. + Until that point, cancel any provisionals. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && pDiscard[n + j] == 1) + break; + if (pDiscard[n + j] == 2) + { + consec = 0; + pDiscard[n + j] = 0; + } + else if (pDiscard[n + j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + + /* I advances to the last line of the run. */ + n += length - 1; + + /* Same thing, from end. */ + for (j = 0, consec = 0; j < length; j++) + { + if (j >= 8 && pDiscard[n - j] == 1) + break; + if (pDiscard[n - j] == 2) + { + consec = 0; + pDiscard[n - j] = 0; + } + else if (pDiscard[n - j] == 0) + consec = 0; + else + consec++; + if (consec == 3) + break; + } + } + } + } +} + +Compare::MovedData::MovedData( CompareData& rData, const char* pDiscard ) + : m_nCount( 0 ) +{ + sal_uLong nLen = rData.GetLineCount(); + sal_uLong n; + + for( n = 0; n < nLen; ++n ) + if( pDiscard[ n ] ) + rData.SetChanged( n ); + else + ++m_nCount; + + if( m_nCount ) + { + m_pIndex.reset( new sal_uLong[ m_nCount ] ); + m_pLineNum.reset( new sal_uLong[ m_nCount ] ); + + for( n = 0, m_nCount = 0; n < nLen; ++n ) + if( !pDiscard[ n ] ) + { + m_pIndex[ m_nCount ] = rData.GetIndex( n ); + m_pLineNum[ m_nCount++ ] = n; + } + } +} + +/// Find the differing lines +Compare::CompareSequence::CompareSequence( + CompareData& rD1, CompareData& rD2, + const MovedData& rMD1, const MovedData& rMD2 ) + : m_rData1( rD1 ), m_rData2( rD2 ), m_rMoved1( rMD1 ), m_rMoved2( rMD2 ) +{ + sal_uLong nSize = rMD1.GetCount() + rMD2.GetCount() + 3; + m_pMemory.reset( new long[ nSize * 2 ] ); + m_pFDiag = m_pMemory.get() + ( rMD2.GetCount() + 1 ); + m_pBDiag = m_pMemory.get() + ( nSize + rMD2.GetCount() + 1 ); + + Compare( 0, rMD1.GetCount(), 0, rMD2.GetCount() ); +} + +void Compare::CompareSequence::Compare( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2 ) +{ + /* Slide down the bottom initial diagonal. */ + while( nStt1 < nEnd1 && nStt2 < nEnd2 && + m_rMoved1.GetIndex( nStt1 ) == m_rMoved2.GetIndex( nStt2 )) + { + ++nStt1; + ++nStt2; + } + + /* Slide up the top initial diagonal. */ + while( nEnd1 > nStt1 && nEnd2 > nStt2 && + m_rMoved1.GetIndex( nEnd1 - 1 ) == m_rMoved2.GetIndex( nEnd2 - 1 )) + { + --nEnd1; + --nEnd2; + } + + /* Handle simple cases. */ + if( nStt1 == nEnd1 ) + while( nStt2 < nEnd2 ) + m_rData2.SetChanged( m_rMoved2.GetLineNum( nStt2++ )); + + else if (nStt2 == nEnd2) + while (nStt1 < nEnd1) + m_rData1.SetChanged( m_rMoved1.GetLineNum( nStt1++ )); + + else + { + sal_uLong c, d, b; + + /* Find a point of correspondence in the middle of the files. */ + + d = CheckDiag( nStt1, nEnd1, nStt2, nEnd2, &c ); + b = m_pBDiag[ d ]; + + if( 1 != c ) + { + /* Use that point to split this problem into two subproblems. */ + Compare( nStt1, b, nStt2, b - d ); + /* This used to use f instead of b, + but that is incorrect! + It is not necessarily the case that diagonal d + has a snake from b to f. */ + Compare( b, nEnd1, b - d, nEnd2 ); + } + } +} + +sal_uLong Compare::CompareSequence::CheckDiag( sal_uLong nStt1, sal_uLong nEnd1, + sal_uLong nStt2, sal_uLong nEnd2, sal_uLong* pCost ) +{ + const long dmin = nStt1 - nEnd2; /* Minimum valid diagonal. */ + const long dmax = nEnd1 - nStt2; /* Maximum valid diagonal. */ + const long fmid = nStt1 - nStt2; /* Center diagonal of top-down search. */ + const long bmid = nEnd1 - nEnd2; /* Center diagonal of bottom-up search. */ + + long fmin = fmid, fmax = fmid; /* Limits of top-down search. */ + long bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */ + + long c; /* Cost. */ + long odd = (fmid - bmid) & 1; /* True if southeast corner is on an odd + diagonal with respect to the northwest. */ + + m_pFDiag[fmid] = nStt1; + m_pBDiag[bmid] = nEnd1; + + for (c = 1;; ++c) + { + long d; /* Active diagonal. */ + + /* Extend the top-down search by an edit step in each diagonal. */ + if (fmin > dmin) + m_pFDiag[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + m_pFDiag[++fmax + 1] = -1; + else + --fmax; + for (d = fmax; d >= fmin; d -= 2) + { + long x, y, tlo = m_pFDiag[d - 1], thi = m_pFDiag[d + 1]; + + if (tlo >= thi) + x = tlo + 1; + else + x = thi; + y = x - d; + while( o3tl::make_unsigned(x) < nEnd1 && o3tl::make_unsigned(y) < nEnd2 && + m_rMoved1.GetIndex( x ) == m_rMoved2.GetIndex( y )) + { + ++x; + ++y; + } + m_pFDiag[d] = x; + if( odd && bmin <= d && d <= bmax && m_pBDiag[d] <= m_pFDiag[d] ) + { + *pCost = 2 * c - 1; + return d; + } + } + + /* Similar extend the bottom-up search. */ + if (bmin > dmin) + m_pBDiag[--bmin - 1] = INT_MAX; + else + ++bmin; + if (bmax < dmax) + m_pBDiag[++bmax + 1] = INT_MAX; + else + --bmax; + for (d = bmax; d >= bmin; d -= 2) + { + long x, y, tlo = m_pBDiag[d - 1], thi = m_pBDiag[d + 1]; + + if (tlo < thi) + x = tlo; + else + x = thi - 1; + y = x - d; + while( o3tl::make_unsigned(x) > nStt1 && o3tl::make_unsigned(y) > nStt2 && + m_rMoved1.GetIndex( x - 1 ) == m_rMoved2.GetIndex( y - 1 )) + { + --x; + --y; + } + m_pBDiag[d] = x; + if (!odd && fmin <= d && d <= fmax && m_pBDiag[d] <= m_pFDiag[d]) + { + *pCost = 2 * c; + return d; + } + } + } +} + +namespace +{ + void lcl_ShiftBoundariesOneway( CompareData* const pData, CompareData const * const pOtherData) + { + sal_uLong i = 0; + sal_uLong j = 0; + sal_uLong i_end = pData->GetLineCount(); + sal_uLong preceding = ULONG_MAX; + sal_uLong other_preceding = ULONG_MAX; + + while (true) + { + sal_uLong start, other_start; + + /* Scan forwards to find beginning of another run of changes. + Also keep track of the corresponding point in the other file. */ + + while( i < i_end && !pData->GetChanged( i ) ) + { + while( pOtherData->GetChanged( j++ )) + /* Non-corresponding lines in the other file + will count as the preceding batch of changes. */ + other_preceding = j; + i++; + } + + if (i == i_end) + break; + + start = i; + other_start = j; + + while (true) + { + /* Now find the end of this run of changes. */ + + while( pData->GetChanged( ++i )) + ; + + /* If the first changed line matches the following unchanged one, + and this run does not follow right after a previous run, + and there are no lines deleted from the other file here, + then classify the first changed line as unchanged + and the following line as changed in its place. */ + + /* You might ask, how could this run follow right after another? + Only because the previous run was shifted here. */ + + if( i != i_end && + pData->GetIndex( start ) == pData->GetIndex( i ) && + !pOtherData->GetChanged( j ) && + !( start == preceding || other_start == other_preceding )) + { + pData->SetChanged( start++, false ); + pData->SetChanged( i ); + /* Since one line-that-matches is now before this run + instead of after, we must advance in the other file + to keep in sync. */ + ++j; + } + else + break; + } + + preceding = i; + other_preceding = j; + } + } +} + +void Compare::ShiftBoundaries( CompareData& rData1, CompareData& rData2 ) +{ + lcl_ShiftBoundariesOneway(&rData1, &rData2); + lcl_ShiftBoundariesOneway(&rData2, &rData1); +} + +sal_uLong SwCompareLine::GetHashValue() const +{ + sal_uLong nRet = 0; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Text: + nRet = GetTextNodeHashValue( *m_rNode.GetTextNode(), nRet ); + break; + + case SwNodeType::Table: + { + const SwNode* pEndNd = m_rNode.EndOfSectionNode(); + SwNodeIndex aIdx( m_rNode ); + while( &aIdx.GetNode() != pEndNd ) + { + if( aIdx.GetNode().IsTextNode() ) + nRet = GetTextNodeHashValue( *aIdx.GetNode().GetTextNode(), nRet ); + ++aIdx; + } + } + break; + + case SwNodeType::Section: + { + OUString sStr( GetText() ); + for( sal_Int32 n = 0; n < sStr.getLength(); ++n ) + nRet = (nRet << 1) + sStr[ n ]; + } + break; + + case SwNodeType::Grf: + case SwNodeType::Ole: + // Fixed ID? Should never occur ... + break; + default: break; + } + return nRet; +} + +const SwNode& SwCompareLine::GetEndNode() const +{ + const SwNode* pNd = &m_rNode; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Table: + pNd = m_rNode.EndOfSectionNode(); + break; + + case SwNodeType::Section: + { + const SwSectionNode& rSNd = static_cast<const SwSectionNode&>(m_rNode); + const SwSection& rSect = rSNd.GetSection(); + if( SectionType::Content != rSect.GetType() || rSect.IsProtect() ) + pNd = m_rNode.EndOfSectionNode(); + } + break; + default: break; + } + return *pNd; +} + +bool SwCompareLine::Compare( const SwCompareLine& rLine ) const +{ + return CompareNode( m_rNode, rLine.m_rNode ); +} + +namespace +{ + OUString SimpleTableToText(const SwNode &rNode) + { + OUStringBuffer sRet; + const SwNode* pEndNd = rNode.EndOfSectionNode(); + SwNodeIndex aIdx( rNode ); + while (&aIdx.GetNode() != pEndNd) + { + if (aIdx.GetNode().IsTextNode()) + { + if (sRet.getLength()) + { + sRet.append( '\n' ); + } + sRet.append( aIdx.GetNode().GetTextNode()->GetExpandText(nullptr) ); + } + ++aIdx; + } + return sRet.makeStringAndClear(); + } +} + +bool SwCompareLine::CompareNode( const SwNode& rDstNd, const SwNode& rSrcNd ) +{ + if( rSrcNd.GetNodeType() != rDstNd.GetNodeType() ) + return false; + + bool bRet = false; + + switch( rDstNd.GetNodeType() ) + { + case SwNodeType::Text: + bRet = CompareTextNd( *rDstNd.GetTextNode(), *rSrcNd.GetTextNode() ) + && ( !CmpOptions.bUseRsid || rDstNd.GetTextNode()->CompareParRsid( *rSrcNd.GetTextNode() ) ); + break; + + case SwNodeType::Table: + { + const SwTableNode& rTSrcNd = static_cast<const SwTableNode&>(rSrcNd); + const SwTableNode& rTDstNd = static_cast<const SwTableNode&>(rDstNd); + + bRet = ( rTSrcNd.EndOfSectionIndex() - rTSrcNd.GetIndex() ) == + ( rTDstNd.EndOfSectionIndex() - rTDstNd.GetIndex() ); + + // --> #i107826#: compare actual table content + if (bRet) + { + bRet = (SimpleTableToText(rSrcNd) == SimpleTableToText(rDstNd)); + } + } + break; + + case SwNodeType::Section: + { + const SwSectionNode& rSSrcNd = static_cast<const SwSectionNode&>(rSrcNd), + & rSDstNd = static_cast<const SwSectionNode&>(rDstNd); + const SwSection& rSrcSect = rSSrcNd.GetSection(), + & rDstSect = rSDstNd.GetSection(); + SectionType eSrcSectType = rSrcSect.GetType(), + eDstSectType = rDstSect.GetType(); + switch( eSrcSectType ) + { + case SectionType::Content: + bRet = SectionType::Content == eDstSectType && + rSrcSect.IsProtect() == rDstSect.IsProtect(); + if( bRet && rSrcSect.IsProtect() ) + { + // the only have they both the same size + bRet = ( rSSrcNd.EndOfSectionIndex() - rSSrcNd.GetIndex() ) == + ( rSDstNd.EndOfSectionIndex() - rSDstNd.GetIndex() ); + } + break; + + case SectionType::ToxHeader: + case SectionType::ToxContent: + if( SectionType::ToxHeader == eDstSectType || + SectionType::ToxContent == eDstSectType ) + { + // the same type of TOX? + const SwTOXBase* pSrcTOX = rSrcSect.GetTOXBase(); + const SwTOXBase* pDstTOX = rDstSect.GetTOXBase(); + bRet = pSrcTOX && pDstTOX + && pSrcTOX->GetType() == pDstTOX->GetType() + && pSrcTOX->GetTitle() == pDstTOX->GetTitle() + && pSrcTOX->GetTypeName() == pDstTOX->GetTypeName() + ; + } + break; + + case SectionType::DdeLink: + case SectionType::FileLink: + bRet = eSrcSectType == eDstSectType && + rSrcSect.GetLinkFileName() == + rDstSect.GetLinkFileName(); + break; + } + } + break; + + case SwNodeType::End: + bRet = rSrcNd.StartOfSectionNode()->GetNodeType() == + rDstNd.StartOfSectionNode()->GetNodeType(); + + // --> #i107826#: compare actual table content + if (bRet && rSrcNd.StartOfSectionNode()->GetNodeType() == SwNodeType::Table) + { + bRet = CompareNode( + *rSrcNd.StartOfSectionNode(), *rDstNd.StartOfSectionNode()); + } + + break; + + default: break; + } + return bRet; +} + +OUString SwCompareLine::GetText() const +{ + OUString sRet; + switch( m_rNode.GetNodeType() ) + { + case SwNodeType::Text: + sRet = m_rNode.GetTextNode()->GetExpandText(nullptr); + break; + + case SwNodeType::Table: + { + sRet = "Tabelle: " + SimpleTableToText(m_rNode); + } + break; + + case SwNodeType::Section: + { + sRet = "Section - Node:"; + + const SwSectionNode& rSNd = static_cast<const SwSectionNode&>(m_rNode); + const SwSection& rSect = rSNd.GetSection(); + switch( rSect.GetType() ) + { + case SectionType::Content: + if( rSect.IsProtect() ) + sRet += OUString::number( + rSNd.EndOfSectionIndex() - rSNd.GetIndex() ); + break; + + case SectionType::ToxHeader: + case SectionType::ToxContent: + { + const SwTOXBase* pTOX = rSect.GetTOXBase(); + if( pTOX ) + sRet += pTOX->GetTitle() + pTOX->GetTypeName() + + OUString::number(pTOX->GetType()); + } + break; + + case SectionType::DdeLink: + case SectionType::FileLink: + sRet += rSect.GetLinkFileName(); + break; + } + } + break; + + case SwNodeType::Grf: + sRet = "Grafik - Node:"; + break; + case SwNodeType::Ole: + sRet = "OLE - Node:"; + break; + default: break; + } + return sRet; +} + +sal_uLong SwCompareLine::GetTextNodeHashValue( const SwTextNode& rNd, sal_uLong nVal ) +{ + OUString sStr( rNd.GetExpandText(nullptr) ); + for( sal_Int32 n = 0; n < sStr.getLength(); ++n ) + nVal = (nVal << 1 ) + sStr[ n ]; + return nVal; +} + +bool SwCompareLine::CompareTextNd( const SwTextNode& rDstNd, + const SwTextNode& rSrcNd ) +{ + bool bRet = false; + // Very simple at first + if( rDstNd.GetText() == rSrcNd.GetText() ) + { + // The text is the same, but are the "special attributes" (0xFF) also the same? + bRet = true; + } + return bRet; +} + +bool SwCompareLine::ChangesInLine( const SwCompareLine& rLine, + std::unique_ptr<SwPaM>& rpInsRing, std::unique_ptr<SwPaM>& rpDelRing ) const +{ + bool bRet = false; + + // Only compare textnodes + if( SwNodeType::Text == m_rNode.GetNodeType() && + SwNodeType::Text == rLine.GetNode().GetNodeType() ) + { + SwTextNode& rDstNd = *const_cast<SwTextNode*>(m_rNode.GetTextNode()); + const SwTextNode& rSrcNd = *rLine.GetNode().GetTextNode(); + SwDoc* pDstDoc = rDstNd.GetDoc(); + + int nLcsLen = 0; + + int nDstLen = rDstNd.GetText().getLength(); + int nSrcLen = rSrcNd.GetText().getLength(); + + int nMinLen = std::min( nDstLen , nSrcLen ); + int nAvgLen = ( nDstLen + nSrcLen )/2; + + std::vector<int> aLcsDst( nMinLen + 1 ); + std::vector<int> aLcsSrc( nMinLen + 1 ); + + if( CmpOptions.eCmpMode == SwCompareMode::ByWord ) + { + std::vector<int> aTmpLcsDst( nMinLen + 1 ); + std::vector<int> aTmpLcsSrc( nMinLen + 1 ); + + WordArrayComparator aCmp( &rDstNd, &rSrcNd ); + + LgstCommonSubseq aSeq( aCmp ); + + nLcsLen = aSeq.Find( aTmpLcsDst.data(), aTmpLcsSrc.data() ); + + if( CmpOptions.nIgnoreLen ) + { + nLcsLen = CommonSubseq::IgnoreIsolatedPieces( aTmpLcsDst.data(), aTmpLcsSrc.data(), + aCmp.GetLen1(), aCmp.GetLen2(), + nLcsLen, CmpOptions.nIgnoreLen ); + } + + nLcsLen = aCmp.GetCharSequence( aTmpLcsDst.data(), aTmpLcsSrc.data(), + aLcsDst.data(), aLcsSrc.data(), nLcsLen ); + } + else + { + CharArrayComparator aCmp( &rDstNd, &rSrcNd ); + LgstCommonSubseq aSeq( aCmp ); + + nLcsLen = aSeq.Find( aLcsDst.data(), aLcsSrc.data() ); + + if( CmpOptions.nIgnoreLen ) + { + nLcsLen = CommonSubseq::IgnoreIsolatedPieces( aLcsDst.data(), aLcsSrc.data(), nDstLen, + nSrcLen, nLcsLen, + CmpOptions.nIgnoreLen ); + } + } + + // find the sum of the squares of the continuous substrings + int nSqSum = 0; + int nCnt = 1; + for( int i = 0; i < nLcsLen; i++ ) + { + if( i != nLcsLen - 1 && aLcsDst[i] + 1 == aLcsDst[i + 1] + && aLcsSrc[i] + 1 == aLcsSrc[i + 1] ) + { + nCnt++; + } + else + { + nSqSum += nCnt*nCnt; + nCnt = 1; + } + } + + // Don't compare if there aren't enough similarities + if ( nAvgLen >= 8 && nSqSum*32 < nAvgLen*nAvgLen ) + { + return false; + } + + // Show the differences + int nSkip = 0; + for( int i = 0; i <= nLcsLen; i++ ) + { + int nDstFrom = i ? (aLcsDst[i - 1] + 1) : 0; + int nDstTo = ( i == nLcsLen ) ? nDstLen : aLcsDst[i]; + int nSrcFrom = i ? (aLcsSrc[i - 1] + 1) : 0; + int nSrcTo = ( i == nLcsLen ) ? nSrcLen : aLcsSrc[i]; + + SwPaM aPam( rDstNd, nDstTo + nSkip ); + + if ( nDstFrom < nDstTo ) + { + SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpInsRing.get() ); + if( !rpInsRing ) + rpInsRing.reset(pTmp); + pTmp->SetMark(); + pTmp->GetMark()->nContent = nDstFrom + nSkip; + } + + if ( nSrcFrom < nSrcTo ) + { + bool bUndo = pDstDoc->GetIDocumentUndoRedo().DoesUndo(); + pDstDoc->GetIDocumentUndoRedo().DoUndo( false ); + SwPaM aCpyPam( rSrcNd, nSrcFrom ); + aCpyPam.SetMark(); + aCpyPam.GetPoint()->nContent = nSrcTo; + aCpyPam.GetDoc()->getIDocumentContentOperations().CopyRange( aCpyPam, *aPam.GetPoint(), + SwCopyFlags::CheckPosInFly); + pDstDoc->GetIDocumentUndoRedo().DoUndo( bUndo ); + + SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpDelRing.get() ); + if( !rpDelRing ) + rpDelRing.reset(pTmp); + + pTmp->SetMark(); + pTmp->GetMark()->nContent = nDstTo + nSkip; + nSkip += nSrcTo - nSrcFrom; + + if( rpInsRing ) + { + SwPaM* pCorr = rpInsRing->GetPrev(); + if( *pCorr->GetPoint() == *pTmp->GetPoint() ) + *pCorr->GetPoint() = *pTmp->GetMark(); + } + } + } + + bRet = true; + } + + return bRet; +} + +sal_uLong CompareData::NextIdx( const SwNode* pNd ) +{ + if( pNd->IsStartNode() ) + { + if( pNd->IsTableNode() ) + pNd = pNd->EndOfSectionNode(); + else + { + const SwSectionNode* pSNd = pNd->GetSectionNode(); + if( pSNd && + ( SectionType::Content != pSNd->GetSection().GetType() || + pSNd->GetSection().IsProtect() ) ) + pNd = pNd->EndOfSectionNode(); + } + } + return pNd->GetIndex() + 1; +} + +sal_uLong CompareData::PrevIdx( const SwNode* pNd ) +{ + if( pNd->IsEndNode() ) + { + if( pNd->StartOfSectionNode()->IsTableNode() ) + pNd = pNd->StartOfSectionNode(); + else + { + const SwSectionNode* pSNd = pNd->StartOfSectionNode()->GetSectionNode(); + if( pSNd && + ( SectionType::Content != pSNd->GetSection().GetType() || + pSNd->GetSection().IsProtect() ) ) + pNd = pNd->StartOfSectionNode(); + } + } + return pNd->GetIndex() - 1; +} + +void CompareData::CheckRanges( CompareData& rData ) +{ + const SwNodes& rSrcNds = rData.m_rDoc.GetNodes(); + const SwNodes& rDstNds = m_rDoc.GetNodes(); + + const SwNode& rSrcEndNd = rData.GetEndOfContent(); + const SwNode& rDstEndNd = GetEndOfContent(); + + sal_uLong nSrcSttIdx = NextIdx( rSrcEndNd.StartOfSectionNode() ); + sal_uLong nSrcEndIdx = rSrcEndNd.GetIndex(); + + sal_uLong nDstSttIdx = NextIdx( rDstEndNd.StartOfSectionNode() ); + sal_uLong nDstEndIdx = rDstEndNd.GetIndex(); + + while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx ) + { + const SwNode* pSrcNd = rSrcNds[ nSrcSttIdx ]; + const SwNode* pDstNd = rDstNds[ nDstSttIdx ]; + if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd )) + break; + + nSrcSttIdx = NextIdx( pSrcNd ); + nDstSttIdx = NextIdx( pDstNd ); + } + + nSrcEndIdx = PrevIdx( &rSrcEndNd ); + nDstEndIdx = PrevIdx( &rDstEndNd ); + while( nSrcSttIdx < nSrcEndIdx && nDstSttIdx < nDstEndIdx ) + { + const SwNode* pSrcNd = rSrcNds[ nSrcEndIdx ]; + const SwNode* pDstNd = rDstNds[ nDstEndIdx ]; + if( !SwCompareLine::CompareNode( *pSrcNd, *pDstNd )) + break; + + nSrcEndIdx = PrevIdx( pSrcNd ); + nDstEndIdx = PrevIdx( pDstNd ); + } + + while( nSrcSttIdx <= nSrcEndIdx ) + { + const SwNode* pNd = rSrcNds[ nSrcSttIdx ]; + rData.InsertLine( new SwCompareLine( *pNd ) ); + nSrcSttIdx = NextIdx( pNd ); + } + + while( nDstSttIdx <= nDstEndIdx ) + { + const SwNode* pNd = rDstNds[ nDstSttIdx ]; + InsertLine( new SwCompareLine( *pNd ) ); + nDstSttIdx = NextIdx( pNd ); + } +} + +void CompareData::ShowInsert( sal_uLong nStt, sal_uLong nEnd ) +{ + SwPaM* pTmp = new SwPaM( GetLine( nStt )->GetNode(), 0, + GetLine( nEnd-1 )->GetEndNode(), 0, + m_pInsertRing.get() ); + if( !m_pInsertRing ) + m_pInsertRing.reset( pTmp ); + + // #i65201#: These SwPaMs are calculated smaller than needed, see comment below +} + +void CompareData::ShowDelete( + const CompareData& rData, + sal_uLong nStt, + sal_uLong nEnd, + sal_uLong nInsPos ) +{ + SwNodeRange aRg( + rData.GetLine( nStt )->GetNode(), 0, + rData.GetLine( nEnd-1 )->GetEndNode(), 1 ); + + sal_uInt16 nOffset = 0; + const SwCompareLine* pLine = nullptr; + if( nInsPos >= 1 ) + { + if( GetLineCount() == nInsPos ) + { + pLine = GetLine( nInsPos-1 ); + nOffset = 1; + } + else + pLine = GetLine( nInsPos ); + } + + const SwNode* pLineNd; + if( pLine ) + { + if( nOffset ) + pLineNd = &pLine->GetEndNode(); + else + pLineNd = &pLine->GetNode(); + } + else + { + pLineNd = &GetEndOfContent(); + nOffset = 0; + } + + SwNodeIndex aInsPos( *pLineNd, nOffset ); + SwNodeIndex aSavePos( aInsPos, -1 ); + + rData.m_rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); + m_rDoc.getIDocumentState().SetModified(); + ++aSavePos; + + // #i65201#: These SwPaMs are calculated when the (old) delete-redlines are hidden, + // they will be inserted when the delete-redlines are shown again. + // To avoid unwanted insertions of delete-redlines into these new redlines, what happens + // especially at the end of the document, I reduce the SwPaM by one node. + // Before the new redlines are inserted, they have to expand again. + SwPaM* pTmp = new SwPaM( aSavePos.GetNode(), aInsPos.GetNode(), 0, -1, m_pDelRing.get() ); + if( !m_pDelRing ) + m_pDelRing.reset(pTmp); + + if( m_pInsertRing ) + { + SwPaM* pCorr = m_pInsertRing->GetPrev(); + if( *pCorr->GetPoint() == *pTmp->GetPoint() ) + { + SwNodeIndex aTmpPos( pTmp->GetMark()->nNode, -1 ); + *pCorr->GetPoint() = SwPosition( aTmpPos ); + } + } +} + +void CompareData::CheckForChangesInLine( const CompareData& rData, + sal_uLong nStt, sal_uLong nEnd, + sal_uLong nThisStt, sal_uLong nThisEnd ) +{ + LineArrayComparator aCmp( *this, rData, nThisStt, nThisEnd, + nStt, nEnd ); + + int nMinLen = std::min( aCmp.GetLen1(), aCmp.GetLen2() ); + std::unique_ptr<int[]> pLcsDst(new int[ nMinLen ]); + std::unique_ptr<int[]> pLcsSrc(new int[ nMinLen ]); + + FastCommonSubseq subseq( aCmp ); + int nLcsLen = subseq.Find( pLcsDst.get(), pLcsSrc.get() ); + for (int i = 0; i <= nLcsLen; i++) + { + // Beginning of inserted lines (inclusive) + int nDstFrom = i ? pLcsDst[i - 1] + 1 : 0; + // End of inserted lines (exclusive) + int nDstTo = ( i == nLcsLen ) ? aCmp.GetLen1() : pLcsDst[i]; + // Beginning of deleted lines (inclusive) + int nSrcFrom = i ? pLcsSrc[i - 1] + 1 : 0; + // End of deleted lines (exclusive) + int nSrcTo = ( i == nLcsLen ) ? aCmp.GetLen2() : pLcsSrc[i]; + + if( i ) + { + const SwCompareLine* pDstLn = GetLine( nThisStt + nDstFrom - 1 ); + const SwCompareLine* pSrcLn = rData.GetLine( nStt + nSrcFrom - 1 ); + + // Show differences in detail for lines that + // were matched as only slightly different + if( !pDstLn->ChangesInLine( *pSrcLn, m_pInsertRing, m_pDelRing ) ) + { + ShowInsert( nThisStt + nDstFrom - 1, nThisStt + nDstFrom ); + ShowDelete( rData, nStt + nSrcFrom - 1, nStt + nSrcFrom, + nThisStt + nDstFrom ); + } + } + + // Lines missing from source are inserted + if( nDstFrom != nDstTo ) + { + ShowInsert( nThisStt + nDstFrom, nThisStt + nDstTo ); + } + + // Lines missing from destination are deleted + if( nSrcFrom != nSrcTo ) + { + ShowDelete( rData, nStt + nSrcFrom, nStt + nSrcTo, nThisStt + nDstTo ); + } + } +} + +void CompareData::SetRedlinesToDoc( bool bUseDocInfo ) +{ + SwPaM* pTmp = m_pDelRing.get(); + + // get the Author / TimeStamp from the "other" document info + std::size_t nAuthor = m_rDoc.getIDocumentRedlineAccess().GetRedlineAuthor(); + DateTime aTimeStamp( DateTime::SYSTEM ); + SwDocShell *pDocShell(m_rDoc.GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + + if( bUseDocInfo && xDocProps.is() ) { + OUString aTmp( 1 == xDocProps->getEditingCycles() + ? xDocProps->getAuthor() + : xDocProps->getModifiedBy() ); + util::DateTime uDT( 1 == xDocProps->getEditingCycles() + ? xDocProps->getCreationDate() + : xDocProps->getModificationDate() ); + + if( !aTmp.isEmpty() ) + { + nAuthor = m_rDoc.getIDocumentRedlineAccess().InsertRedlineAuthor( aTmp ); + aTimeStamp = DateTime(uDT); + } + } + } + + if( pTmp ) + { + SwRedlineData aRedlnData( RedlineType::Delete, nAuthor, aTimeStamp, + OUString(), nullptr ); + do { + // #i65201#: Expand again, see comment above. + if( pTmp->GetPoint()->nContent == 0 ) + { + ++pTmp->GetPoint()->nNode; + pTmp->GetPoint()->nContent.Assign( pTmp->GetContentNode(), 0 ); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->nNode.GetNode()) + { + --pTmp->GetPoint()->nNode; + SwContentNode *const pContentNode( pTmp->GetContentNode() ); + pTmp->GetPoint()->nContent.Assign( pContentNode, + pContentNode ? pContentNode->Len() : 0 ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->nContent == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->nNode, -1); + if (prev.GetNode().IsTextNode()) + { + *pTmp->GetMark() = SwPosition( + *prev.GetNode().GetTextNode(), + prev.GetNode().GetTextNode()->Len()); + } + } + } + + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pTmp, false, RedlineType::Any ); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoCompDoc>( *pTmp, false )); + } + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( aRedlnData, *pTmp ), true ); + + } while( m_pDelRing.get() != ( pTmp = pTmp->GetNext()) ); + } + + pTmp = m_pInsertRing.get(); + if( pTmp ) + { + do { + if( pTmp->GetPoint()->nContent == 0 ) + { + ++pTmp->GetPoint()->nNode; + pTmp->GetPoint()->nContent.Assign( pTmp->GetContentNode(), 0 ); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->nNode.GetNode()) + { + --pTmp->GetPoint()->nNode; + SwContentNode *const pContentNode( pTmp->GetContentNode() ); + pTmp->GetPoint()->nContent.Assign( pContentNode, + pContentNode ? pContentNode->Len() : 0 ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->nContent == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->nNode, -1); + if (prev.GetNode().IsTextNode()) + { + *pTmp->GetMark() = SwPosition( + *prev.GetNode().GetTextNode(), + prev.GetNode().GetTextNode()->Len()); + } + } + } + } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); + SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, + OUString(), nullptr ); + + // combine consecutive + if( pTmp->GetNext() != m_pInsertRing.get() ) + { + do { + SwPosition& rSttEnd = *pTmp->End(), + & rEndStt = *pTmp->GetNext()->Start(); + const SwContentNode* pCNd; + if( rSttEnd == rEndStt || + (!rEndStt.nContent.GetIndex() && + rEndStt.nNode.GetIndex() - 1 == rSttEnd.nNode.GetIndex() && + nullptr != ( pCNd = rSttEnd.nNode.GetNode().GetContentNode() ) && + rSttEnd.nContent.GetIndex() == pCNd->Len())) + { + if( pTmp->GetNext() == m_pInsertRing.get() ) + { + // are consecutive, so combine + rEndStt = *pTmp->Start(); + delete pTmp; + pTmp = m_pInsertRing.get(); + } + else + { + // are consecutive, so combine + rSttEnd = *pTmp->GetNext()->End(); + delete pTmp->GetNext(); + } + } + else + pTmp = pTmp->GetNext(); + } while( m_pInsertRing.get() != pTmp ); + } + + do { + if (IDocumentRedlineAccess::AppendResult::APPENDED == + m_rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(aRedlnData, *pTmp), true) && + m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoCompDoc>( *pTmp, true )); + } + } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); + } +} + +typedef std::shared_ptr<CompareData> CompareDataPtr; +typedef std::pair<CompareDataPtr, CompareDataPtr> CompareDataPtrPair; +typedef std::vector<CompareDataPtrPair> Comparators; + +namespace +{ + Comparators buildComparators(SwDoc &rSrcDoc, SwDoc &rDestDoc) + { + Comparators aComparisons; + //compare main text + aComparisons.emplace_back(std::make_shared<CompareMainText>(rSrcDoc, true), + std::make_shared<CompareMainText>(rDestDoc, true)); + + //if we have the same number of frames then try to compare within them + const SwFrameFormats *pSrcFrameFormats = rSrcDoc.GetSpzFrameFormats(); + const SwFrameFormats *pDestFrameFormats = rDestDoc.GetSpzFrameFormats(); + if (pSrcFrameFormats->size() == pDestFrameFormats->size()) + { + for (size_t i = 0; i < pSrcFrameFormats->size(); ++i) + { + const SwFrameFormat& rSrcFormat = *(*pSrcFrameFormats)[i]; + const SwFrameFormat& rDestFormat = *(*pDestFrameFormats)[i]; + const SwNodeIndex* pSrcIdx = rSrcFormat.GetContent().GetContentIdx(); + const SwNodeIndex* pDestIdx = rDestFormat.GetContent().GetContentIdx(); + if (!pSrcIdx && !pDestIdx) + continue; + if (!pSrcIdx || !pDestIdx) + break; + const SwNode* pSrcNode = pSrcIdx->GetNode().EndOfSectionNode(); + const SwNode* pDestNode = pDestIdx->GetNode().EndOfSectionNode(); + if (!pSrcNode && !pDestNode) + continue; + if (!pSrcNode || !pDestNode) + break; + if (pSrcIdx->GetNodes()[pSrcIdx->GetIndex() + 1]->IsNoTextNode() + || pDestIdx->GetNodes()[pDestIdx->GetIndex() + 1]->IsNoTextNode()) + { + continue; // tdf#125660 don't redline GrfNode/OLENode + } + aComparisons.emplace_back(std::make_shared<CompareFrameFormatText>(rSrcDoc, *pSrcIdx), + std::make_shared<CompareFrameFormatText>(rDestDoc, *pDestIdx)); + } + } + return aComparisons; + } +} + +// Returns (the difference count?) if something is different +long SwDoc::CompareDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + long nRet = 0; + + // Get comparison options + CmpOptions.eCmpMode = SW_MOD()->GetCompareMode(); + if( CmpOptions.eCmpMode == SwCompareMode::Auto ) + { + if( getRsidRoot() == rDoc.getRsidRoot() ) + { + CmpOptions.eCmpMode = SwCompareMode::ByChar; + CmpOptions.bUseRsid = true; + CmpOptions.nIgnoreLen = 2; + } + else + { + CmpOptions.eCmpMode = SwCompareMode::ByWord; + CmpOptions.bUseRsid = false; + CmpOptions.nIgnoreLen = 3; + } + } + else + { + CmpOptions.bUseRsid = getRsidRoot() == rDoc.getRsidRoot() && SW_MOD()->IsUseRsid(); + CmpOptions.nIgnoreLen = SW_MOD()->IsIgnorePieces() ? SW_MOD()->GetPieceLen() : 0; + } + + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + bool bDocWasModified = getIDocumentState().IsModified(); + SwDoc& rSrcDoc = const_cast<SwDoc&>(rDoc); + bool bSrcModified = rSrcDoc.getIDocumentState().IsModified(); + + RedlineFlags eSrcRedlMode = rSrcDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowInsert ); + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowInsert); + + Comparators aComparisons(buildComparators(rSrcDoc, *this)); + + for (auto& a : aComparisons) + { + CompareData& rD0 = *a.first; + CompareData& rD1 = *a.second; + rD1.CompareLines( rD0 ); + nRet |= rD1.ShowDiffs( rD0 ); + } + + if( nRet ) + { + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | + RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + for (auto& a : aComparisons) + { + CompareData& rD1 = *a.second; + rD1.SetRedlinesToDoc( !bDocWasModified ); + } + getIDocumentState().SetModified(); + } + + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode ); + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + if( !bSrcModified ) + rSrcDoc.getIDocumentState().ResetModified(); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return nRet; +} + +namespace +{ + struct SaveMergeRedline + { + const SwRangeRedline* pSrcRedl; + SwRangeRedline* pDestRedl; + SaveMergeRedline( const SwNode& rDstNd, const SwRangeRedline& rSrcRedl); + sal_uInt16 InsertRedline(SwPaM* pLastDestRedline); + }; +} + +SaveMergeRedline::SaveMergeRedline( const SwNode& rDstNd, + const SwRangeRedline& rSrcRedl) + : pSrcRedl( &rSrcRedl ) +{ + SwPosition aPos( rDstNd ); + + const SwPosition* pStt = rSrcRedl.Start(); + if( rDstNd.IsContentNode() ) + aPos.nContent.Assign( const_cast<SwContentNode*>(static_cast<const SwContentNode*>(&rDstNd)), pStt->nContent.GetIndex() ); + pDestRedl = new SwRangeRedline( rSrcRedl.GetRedlineData(), aPos ); + + if( RedlineType::Delete == pDestRedl->GetType() ) + { + // mark the area as deleted + const SwPosition* pEnd = pStt == rSrcRedl.GetPoint() + ? rSrcRedl.GetMark() + : rSrcRedl.GetPoint(); + + pDestRedl->SetMark(); + pDestRedl->GetPoint()->nNode += pEnd->nNode.GetIndex() - + pStt->nNode.GetIndex(); + pDestRedl->GetPoint()->nContent.Assign( pDestRedl->GetContentNode(), + pEnd->nContent.GetIndex() ); + } +} + +sal_uInt16 SaveMergeRedline::InsertRedline(SwPaM* pLastDestRedline) +{ + sal_uInt16 nIns = 0; + SwDoc* pDoc = pDestRedl->GetDoc(); + + if( RedlineType::Insert == pDestRedl->GetType() ) + { + // the part was inserted so copy it from the SourceDoc + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodeIndex aSaveNd( pDestRedl->GetPoint()->nNode, -1 ); + const sal_Int32 nSaveCnt = pDestRedl->GetPoint()->nContent.GetIndex(); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + pSrcRedl->GetDoc()->getIDocumentContentOperations().CopyRange( + *const_cast<SwPaM*>(static_cast<const SwPaM*>(pSrcRedl)), + *pDestRedl->GetPoint(), SwCopyFlags::CheckPosInFly); + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + pDestRedl->SetMark(); + ++aSaveNd; + pDestRedl->GetMark()->nNode = aSaveNd; + pDestRedl->GetMark()->nContent.Assign( aSaveNd.GetNode().GetContentNode(), + nSaveCnt ); + + if( pLastDestRedline && *pLastDestRedline->GetPoint() == *pDestRedl->GetPoint() ) + *pLastDestRedline->GetPoint() = *pDestRedl->GetMark(); + } + else + { + //JP 21.09.98: Bug 55909 + // If there already is a deleted or inserted one at the same position, we have to split it! + SwPosition* pDStt = pDestRedl->GetMark(), + * pDEnd = pDestRedl->GetPoint(); + SwRedlineTable::size_type n = 0; + + // find the first redline for StartPos + if( !pDoc->getIDocumentRedlineAccess().GetRedline( *pDStt, &n ) && n ) + --n; + + const SwRedlineTable& rRedlineTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( ; n < rRedlineTable.size(); ++n ) + { + SwRangeRedline* pRedl = rRedlineTable[ n ]; + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRStt == pRedl->GetPoint() ? pRedl->GetMark() + : pRedl->GetPoint(); + if( RedlineType::Delete == pRedl->GetType() || + RedlineType::Insert == pRedl->GetType() ) + { + SwComparePosition eCmpPos = ComparePosition( *pDStt, *pDEnd, *pRStt, *pREnd ); + switch( eCmpPos ) + { + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: + break; + + case SwComparePosition::Inside: + case SwComparePosition::Equal: + delete pDestRedl; + pDestRedl = nullptr; + [[fallthrough]]; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: + n = rRedlineTable.size(); + break; + + case SwComparePosition::Outside: + assert(pDestRedl && "is this actually impossible"); + if (pDestRedl) + { + SwRangeRedline* pCpyRedl = new SwRangeRedline( + pDestRedl->GetRedlineData(), *pDStt ); + pCpyRedl->SetMark(); + *pCpyRedl->GetPoint() = *pRStt; + + std::unique_ptr<SwUndoCompDoc> pUndo; + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pCpyRedl )); + + // now modify doc: append redline, undo (and count) + pDoc->getIDocumentRedlineAccess().AppendRedline( pCpyRedl, true ); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + ++nIns; + + *pDStt = *pREnd; + + // we should start over now + n = SwRedlineTable::npos; + } + break; + + case SwComparePosition::OverlapBefore: + *pDEnd = *pRStt; + break; + + case SwComparePosition::OverlapBehind: + *pDStt = *pREnd; + break; + } + } + else if( *pDEnd <= *pRStt ) + break; + } + + } + + if( pDestRedl ) + { + std::unique_ptr<SwUndoCompDoc> pUndo; + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pDestRedl )); + + // now modify doc: append redline, undo (and count) + IDocumentRedlineAccess::AppendResult const result( + pDoc->getIDocumentRedlineAccess().AppendRedline(pDestRedl, true)); + if( pUndo ) + { + pDoc->GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + ++nIns; + + // if AppendRedline has deleted our redline, we may not keep a + // reference to it + if (IDocumentRedlineAccess::AppendResult::APPENDED != result) + pDestRedl = nullptr; + } + return nIns; +} + +/// Merge two documents +long SwDoc::MergeDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + long nRet = 0; + + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + SwDoc& rSrcDoc = const_cast<SwDoc&>(rDoc); + bool bSrcModified = rSrcDoc.getIDocumentState().IsModified(); + + RedlineFlags eSrcRedlMode = rSrcDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete ); + getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::ShowDelete ); + + CompareMainText aD0(rSrcDoc, false); + CompareMainText aD1(*this, false); + aD1.CompareLines( aD0 ); + if( !aD1.HasDiffs( aD0 ) ) + { + // we want to get all redlines from the SourceDoc + + // look for all insert redlines from the SourceDoc and determine their position in the DestDoc + std::vector<SaveMergeRedline> vRedlines; + const SwRedlineTable& rSrcRedlTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable(); + sal_uLong nEndOfExtra = rSrcDoc.GetNodes().GetEndOfExtras().GetIndex(); + sal_uLong nMyEndOfExtra = GetNodes().GetEndOfExtras().GetIndex(); + for(const SwRangeRedline* pRedl : rSrcRedlTable) + { + sal_uLong nNd = pRedl->GetPoint()->nNode.GetIndex(); + RedlineType eType = pRedl->GetType(); + if( nEndOfExtra < nNd && + ( RedlineType::Insert == eType || RedlineType::Delete == eType )) + { + const SwNode* pDstNd = GetNodes()[ + nMyEndOfExtra + nNd - nEndOfExtra ]; + + // Found the position. + // Then we also have to insert the redline to the line in the DestDoc. + vRedlines.emplace_back(*pDstNd, *pRedl); + } + } + + if( !vRedlines.empty() ) + { + // Carry over all into DestDoc + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | + RedlineFlags::ShowInsert | + RedlineFlags::ShowDelete); + + SwPaM* pLastDestRedline(nullptr); + for(SaveMergeRedline& rRedline: vRedlines) + { + nRet += rRedline.InsertRedline(pLastDestRedline); + pLastDestRedline = rRedline.pDestRedl; + } + } + } + + rSrcDoc.getIDocumentRedlineAccess().SetRedlineFlags( eSrcRedlMode ); + if( !bSrcModified ) + rSrcDoc.getIDocumentState().ResetModified(); + + getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::ShowInsert | RedlineFlags::ShowDelete); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + return nRet; +} + +LineArrayComparator::LineArrayComparator( const CompareData &rD1, + const CompareData &rD2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) + : m_rData1( rD1 ), m_rData2( rD2 ), m_nFirst1( nStt1 ), m_nFirst2( nStt2 ) +{ + m_nLen1 = nEnd1 - nStt1; + m_nLen2 = nEnd2 - nStt2; +} + +bool LineArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + if( nIdx1 < 0 || nIdx2 < 0 || nIdx1 >= m_nLen1 || nIdx2 >= m_nLen2 ) + { + OSL_ENSURE( false, "Index out of range!" ); + return false; + } + + const SwTextNode *pTextNd1 = m_rData1.GetLine( m_nFirst1 + nIdx1 )->GetNode().GetTextNode(); + const SwTextNode *pTextNd2 = m_rData2.GetLine( m_nFirst2 + nIdx2 )->GetNode().GetTextNode(); + + if( !pTextNd1 || !pTextNd2 + || ( CmpOptions.bUseRsid && !pTextNd1->CompareParRsid( *pTextNd2 ) ) ) + { + return false; + } + + const sal_Int32 nPar1Len = pTextNd1->Len(); + const sal_Int32 nPar2Len = pTextNd2->Len(); + + if( std::min( nPar1Len, nPar2Len ) * 3 < std::max( nPar1Len, nPar2Len ) ) + { + return false; + } + + sal_Int32 nBorderLen = ( nPar1Len + nPar2Len )/16; + + if( nBorderLen < 3 ) + { + nBorderLen = std::min<sal_Int32>( 3, std::min( nPar1Len, nPar2Len ) ); + } + + std::set<unsigned> aHashes; + unsigned nHash = 0; + unsigned nMul = 251; + unsigned nPow = 1; + sal_Int32 i; + + for( i = 0; i < nBorderLen - 1; i++ ) + { + nPow *= nMul; + } + for( i = 0; i < nBorderLen; i++ ) + { + nHash = nHash*nMul + pTextNd1->GetText()[i]; + } + aHashes.insert( nHash ); + for( ; i < nPar1Len; i++ ) + { + nHash = nHash - nPow*pTextNd1->GetText()[ i - nBorderLen ]; + nHash = nHash*nMul + pTextNd1->GetText()[ i ]; + + aHashes.insert( nHash ); + } + + nHash = 0; + for( i = 0; i < nBorderLen; i++ ) + { + nHash = nHash*nMul + pTextNd2->GetText()[ i ]; + } + + if( aHashes.find( nHash ) != aHashes.end() ) + { + return true; + } + + for( ; i < nPar2Len; i++ ) + { + nHash = nHash - nPow*pTextNd2->GetText()[ i - nBorderLen ]; + nHash = nHash*nMul + pTextNd2->GetText()[ i ]; + if( aHashes.find( nHash ) != aHashes.end() ) + { + return true; + } + } + return false; +} + +bool CharArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + if( nIdx1 < 0 || nIdx2 < 0 || nIdx1 >= GetLen1() || nIdx2 >= GetLen2() ) + { + OSL_ENSURE( false, "Index out of range!" ); + return false; + } + + return ( !CmpOptions.bUseRsid + || m_pTextNode1->CompareRsid( *m_pTextNode2, nIdx1 + 1, nIdx2 + 1 ) ) + && m_pTextNode1->GetText()[ nIdx1 ] == m_pTextNode2->GetText()[ nIdx2 ]; +} + +WordArrayComparator::WordArrayComparator( const SwTextNode *pNode1, + const SwTextNode *pNode2 ) + : m_pTextNode1( pNode1 ), m_pTextNode2( pNode2 ) +{ + m_pPos1.reset( new int[ m_pTextNode1->GetText().getLength() + 1 ] ); + m_pPos2.reset( new int[ m_pTextNode2->GetText().getLength() + 1 ] ); + + CalcPositions( m_pPos1.get(), m_pTextNode1, m_nCount1 ); + CalcPositions( m_pPos2.get(), m_pTextNode2, m_nCount2 ); +} + +bool WordArrayComparator::Compare( int nIdx1, int nIdx2 ) const +{ + int nLen = m_pPos1[ nIdx1 + 1 ] - m_pPos1[ nIdx1 ]; + if( nLen != m_pPos2[ nIdx2 + 1 ] - m_pPos2[ nIdx2 ] ) + { + return false; + } + for( int i = 0; i < nLen; i++) + { + if( m_pTextNode1->GetText()[ m_pPos1[ nIdx1 ] + i ] + != m_pTextNode2->GetText()[ m_pPos2[ nIdx2 ] + i ] + || ( CmpOptions.bUseRsid && !m_pTextNode1->CompareRsid( *m_pTextNode2, + m_pPos1[ nIdx1 ] + i, m_pPos2[ nIdx2 ] + i ) ) ) + { + return false; + } + } + return true; +} + +int WordArrayComparator::GetCharSequence( const int *pWordLcs1, + const int *pWordLcs2, int *pSubseq1, int *pSubseq2, int nLcsLen ) +{ + int nLen = 0; + for( int i = 0; i < nLcsLen; i++ ) + { + // Check for hash collisions + if( m_pPos1[ pWordLcs1[i] + 1 ] - m_pPos1[ pWordLcs1[i] ] + != m_pPos2[ pWordLcs2[i] + 1 ] - m_pPos2[ pWordLcs2[i] ] ) + { + continue; + } + for( int j = 0; j < m_pPos1[pWordLcs1[i]+1] - m_pPos1[pWordLcs1[i]]; j++) + { + pSubseq1[ nLen ] = m_pPos1[ pWordLcs1[i] ] + j; + pSubseq2[ nLen ] = m_pPos2[ pWordLcs2[i] ] + j; + + if( m_pTextNode1->GetText()[ m_pPos1[ pWordLcs1[i] ] + j ] + != m_pTextNode2->GetText()[ m_pPos2[ pWordLcs2[i] ] + j ] ) + { + nLen -= j; + break; + } + + nLen++; + } + } + return nLen; +} + +void WordArrayComparator::CalcPositions( int *pPos, const SwTextNode *pTextNd, + int &nCnt ) +{ + nCnt = -1; + for (int i = 0; i <= pTextNd->GetText().getLength(); ++i) + { + if (i == 0 || i == pTextNd->GetText().getLength() + || !rtl::isAsciiAlphanumeric( pTextNd->GetText()[ i - 1 ]) + || !rtl::isAsciiAlphanumeric( pTextNd->GetText()[ i ])) + { // Begin new word + nCnt++; + pPos[ nCnt ] = i; + } + } +} + +int CommonSubseq::FindLCS( int *pLcs1, int *pLcs2, int nStt1, int nEnd1, + int nStt2, int nEnd2 ) +{ + int nLen1 = nEnd1 ? nEnd1 - nStt1 : m_rComparator.GetLen1(); + int nLen2 = nEnd2 ? nEnd2 - nStt2 : m_rComparator.GetLen2(); + + assert( nLen1 >= 0 ); + assert( nLen2 >= 0 ); + + std::unique_ptr<int*[]> pLcs( new int*[ nLen1 + 1 ] ); + pLcs[ 0 ] = m_pData.get(); + + for( int i = 1; i < nLen1 + 1; i++ ) + pLcs[ i ] = pLcs[ i - 1 ] + nLen2 + 1; + + for( int i = 0; i <= nLen1; i++ ) + pLcs[i][0] = 0; + + for( int j = 0; j <= nLen2; j++ ) + pLcs[0][j] = 0; + + // Find lcs + for( int i = 1; i <= nLen1; i++ ) + { + for( int j = 1; j <= nLen2; j++ ) + { + if( m_rComparator.Compare( nStt1 + i - 1, nStt2 + j - 1 ) ) + pLcs[i][j] = pLcs[i - 1][j - 1] + 1; + else + pLcs[i][j] = std::max( pLcs[i][j - 1], pLcs[i - 1][j] ); + } + } + + int nLcsLen = pLcs[ nLen1 ][ nLen2 ]; + + // Recover the lcs in the two sequences + if( pLcs1 && pLcs2 ) + { + int nIdx1 = nLen1; + int nIdx2 = nLen2; + int nIdx = nLcsLen - 1; + + while( nIdx1 > 0 && nIdx2 > 0 ) + { + if( pLcs[ nIdx1 ][ nIdx2 ] == pLcs[ nIdx1 - 1 ][ nIdx2 ] ) + nIdx1--; + else if( pLcs[ nIdx1 ][ nIdx2 ] == pLcs[ nIdx1 ][ nIdx2 - 1 ] ) + nIdx2--; + else + { + nIdx1--; + nIdx2--; + pLcs1[ nIdx ] = nIdx1 + nStt1; + pLcs2[ nIdx ] = nIdx2 + nStt2; + nIdx--; + } + } + } + + return nLcsLen; +} + +int CommonSubseq::IgnoreIsolatedPieces( int *pLcs1, int *pLcs2, int nLen1, + int nLen2, int nLcsLen, int nPieceLen ) +{ + if( !nLcsLen ) + { + return 0; + } + + int nNext = 0; + + // Don't ignore text at the beginning of the paragraphs + if( pLcs1[ 0 ] == 0 && pLcs2[ 0 ] == 0 ) + { + while( nNext < nLcsLen - 1 && pLcs1[ nNext ] + 1 == pLcs1[ nNext + 1 ] + && pLcs2[ nNext ] + 1 == pLcs2[ nNext + 1 ] ) + { + nNext++; + } + nNext++; + } + + int nCnt = 1; + + for( int i = nNext; i < nLcsLen; i++ ) + { + if( i != nLcsLen - 1 && pLcs1[ i ] + 1 == pLcs1[ i + 1 ] + && pLcs2[ i ] + 1 == pLcs2[ i + 1 ] ) + { + nCnt++; + } + else + { + if( nCnt > nPieceLen + // Don't ignore text at the end of the paragraphs + || ( i == nLcsLen - 1 + && pLcs1[i] == nLen1 - 1 && pLcs2[i] == nLen2 - 1 )) + { + for( int j = i + 1 - nCnt; j <= i; j++ ) + { + pLcs2[ nNext ] = pLcs2[ j ]; + pLcs1[ nNext ] = pLcs1[ j ]; + nNext++; + } + } + nCnt = 1; + } + } + + return nNext; +} + +LgstCommonSubseq::LgstCommonSubseq( ArrayComparator &rComparator ) + : CommonSubseq( rComparator, CUTOFF ) +{ + m_pBuff1.reset( new int[ rComparator.GetLen2() + 1 ] ); + m_pBuff2.reset( new int[ rComparator.GetLen2() + 1 ] ); + + m_pL1.reset( new int[ rComparator.GetLen2() + 1 ] ); + m_pL2.reset( new int[ rComparator.GetLen2() + 1 ] ); +} + +void LgstCommonSubseq::FindL( int *pL, int nStt1, int nEnd1, + int nStt2, int nEnd2 ) +{ + int nLen1 = nEnd1 ? nEnd1 - nStt1 : m_rComparator.GetLen1(); + int nLen2 = nEnd2 ? nEnd2 - nStt2 : m_rComparator.GetLen2(); + + int *currL = m_pBuff1.get(); + int *prevL = m_pBuff2.get(); + + // Avoid memory corruption + if( nLen2 > m_rComparator.GetLen2() ) + { + assert( false ); + return; + } + + memset( m_pBuff1.get(), 0, sizeof( m_pBuff1[0] ) * ( nLen2 + 1 ) ); + memset( m_pBuff2.get(), 0, sizeof( m_pBuff2[0] ) * ( nLen2 + 1 ) ); + + // Find lcs + for( int i = 1; i <= nLen1; i++ ) + { + for( int j = 1; j <= nLen2; j++ ) + { + if( m_rComparator.Compare( nStt1 + i - 1, nStt2 + j - 1 ) ) + currL[j] = prevL[j - 1] + 1; + else + currL[j] = std::max( currL[j - 1], prevL[j] ); + } + int *tmp = currL; + currL = prevL; + prevL = tmp; + } + memcpy( pL, prevL, ( nLen2 + 1 ) * sizeof( *prevL ) ); +} + +int LgstCommonSubseq::HirschbergLCS( int *pLcs1, int *pLcs2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) +{ + static int nLen1; + static int nLen2; + nLen1 = nEnd1 - nStt1; + nLen2 = nEnd2 - nStt2; + + if( ( nLen1 + 1 ) * ( nLen2 + 1 ) <= CUTOFF ) + { + if( !nLen1 || !nLen2 ) + { + return 0; + } + return FindLCS(pLcs1, pLcs2, nStt1, nEnd1, nStt2, nEnd2); + } + + int nMid = nLen1/2; + + FindL( m_pL1.get(), nStt1, nStt1 + nMid, nStt2, nEnd2 ); + FindL( m_pL2.get(), nStt1 + nMid, nEnd1, nStt2, nEnd2 ); + + int nMaxPos = 0; + static int nMaxVal; + nMaxVal = -1; + + static int i; + for( i = 0; i <= nLen2; i++ ) + { + if( m_pL1[i] + ( m_pL2[nLen2] - m_pL2[i] ) > nMaxVal ) + { + nMaxPos = i; + nMaxVal = m_pL1[i]+( m_pL2[nLen2] - m_pL2[i] ); + } + } + + int nRet = HirschbergLCS( pLcs1, pLcs2, nStt1, nStt1 + nMid, + nStt2, nStt2 + nMaxPos ); + nRet += HirschbergLCS( pLcs1 + nRet, pLcs2 + nRet, nStt1 + nMid, nEnd1, + nStt2 + nMaxPos, nEnd2 ); + + return nRet; +} + +int LgstCommonSubseq::Find( int *pSubseq1, int *pSubseq2 ) +{ + int nStt = 0; + int nCutEnd = 0; + int nEnd1 = m_rComparator.GetLen1(); + int nEnd2 = m_rComparator.GetLen2(); + + // Check for corresponding lines in the beginning of the sequences + while( nStt < nEnd1 && nStt < nEnd2 && m_rComparator.Compare( nStt, nStt ) ) + { + pSubseq1[ nStt ] = nStt; + pSubseq2[ nStt ] = nStt; + nStt++; + } + + pSubseq1 += nStt; + pSubseq2 += nStt; + + // Check for corresponding lines in the end of the sequences + while( nStt < nEnd1 && nStt < nEnd2 + && m_rComparator.Compare( nEnd1 - 1, nEnd2 - 1 ) ) + { + nCutEnd++; + nEnd1--; + nEnd2--; + } + + int nLen = HirschbergLCS( pSubseq1, pSubseq2, nStt, nEnd1, nStt, nEnd2 ); + + for( int i = 0; i < nCutEnd; i++ ) + { + pSubseq1[ nLen + i ] = nEnd1 + i; + pSubseq2[ nLen + i ] = nEnd2 + i; + } + + return nStt + nLen + nCutEnd; +} + +int FastCommonSubseq::FindFastCS( int *pSeq1, int *pSeq2, int nStt1, + int nEnd1, int nStt2, int nEnd2 ) +{ + int nCutBeg = 0; + int nCutEnd = 0; + + // Check for corresponding lines in the beginning of the sequences + while( nStt1 < nEnd1 && nStt2 < nEnd2 && m_rComparator.Compare( nStt1, nStt2 ) ) + { + pSeq1[ nCutBeg ] = nStt1++; + pSeq2[ nCutBeg ] = nStt2++; + nCutBeg++; + } + + pSeq1 += nCutBeg; + pSeq2 += nCutBeg; + + // Check for corresponding lines in the end of the sequences + while( nStt1 < nEnd1 && nStt2 < nEnd2 + && m_rComparator.Compare( nEnd1 - 1, nEnd2 - 1 ) ) + { + nCutEnd++; + nEnd1--; + nEnd2--; + } + + int nLen1 = nEnd1 - nStt1; + int nLen2 = nEnd2 - nStt2; + + // Return if a sequence is empty + if( nLen1 <= 0 || nLen2 <= 0 ) + { + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ i ] = nEnd1 + i; + pSeq2[ i ] = nEnd2 + i; + } + return nCutBeg + nCutEnd; + } + + // Cut to LCS for small values + if( nLen1 < 3 || nLen2 < 3 || ( nLen1 + 1 ) * ( nLen2 + 1 ) <= CUTOFF ) + { + int nLcsLen = FindLCS( pSeq1, pSeq2, nStt1, nEnd1, nStt2, nEnd2); + + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ nLcsLen + i ] = nEnd1 + i; + pSeq2[ nLcsLen + i ] = nEnd2 + i; + } + return nCutBeg + nLcsLen + nCutEnd; + } + + int nMid1 = nLen1/2; + int nMid2 = nLen2/2; + + int nRad; + int nPos1 = -1, nPos2 = -1; + + // Find a point of correspondence in the middle of the sequences + for( nRad = 0; nRad*nRad < std::min( nMid1, nMid2 ); nRad++ ) + { + // Search to the left and to the right of the middle of the first sequence + for( int i = nMid1 - nRad; i <= nMid1 + nRad; i++ ) + { + if( m_rComparator.Compare( nStt1 + i, nStt2 + nMid2 - nRad ) ) + { + nPos1 = nStt1 + i; + nPos2 = nStt2 + nMid2 - nRad; + break; + } + if( m_rComparator.Compare( nStt1 + i, nStt2 + nMid2 + nRad ) ) + { + nPos1 = nStt1 + i; + nPos2 = nStt2 + nMid2 - nRad; + break; + } + } + // Search to the left and to the right of the middle of the second sequence + for( int i = nMid2 - nRad; i <= nMid2 + nRad; i++ ) + { + if( m_rComparator.Compare( nStt2 + nMid2 - nRad, nStt2 + i ) ) + { + nPos2 = nStt2 + i; + nPos1 = nStt1 + nMid1 - nRad; + break; + } + if( m_rComparator.Compare( nStt2 + nMid2 - nRad, nStt2 + i ) ) + { + nPos2 = nStt2 + i; + nPos1 = nStt1 + nMid1 - nRad; + break; + } + } + } + + // return if no point of correspondence found + if( nPos1 == -1 ) + { + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ i ] = nEnd1 + i; + pSeq2[ i ] = nEnd2 + i; + } + return nCutBeg + nCutEnd; + } + + // Run the same on the sequences to the left of the correspondence point + int nLen = FindFastCS( pSeq1, pSeq2, nStt1, nPos1, nStt2, nPos2 ); + + pSeq1[ nLen ] = nPos1; + pSeq2[ nLen ] = nPos2; + + // Run the same on the sequences to the right of the correspondence point + nLen += FindFastCS( pSeq1 + nLen + 1, pSeq2 + nLen + 1, + nPos1 + 1, nEnd1, nPos2 + 1, nEnd2 ) + 1; + + for( int i = 0; i < nCutEnd; i++ ) + { + pSeq1[ nLen + i ] = nEnd1 + i; + pSeq2[ nLen + i ] = nEnd2 + i; + } + + return nLen + nCutBeg + nCutEnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doccorr.cxx b/sw/source/core/doc/doccorr.cxx new file mode 100644 index 000000000..41f7b673d --- /dev/null +++ b/sw/source/core/doc/doccorr.cxx @@ -0,0 +1,364 @@ +/* -*- 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 <IDocumentLayoutAccess.hxx> +#include <node.hxx> +#include <editsh.hxx> +#include <viscrs.hxx> +#include <redline.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <unocrsr.hxx> + +namespace +{ + /// find the relevant section in which the SwUnoCursor may wander. + /// returns NULL if no restrictions apply + const SwStartNode* lcl_FindUnoCursorSection( const SwNode& rNode ) + { + const SwStartNode* pStartNode = rNode.StartOfSectionNode(); + while( ( pStartNode != nullptr ) && + ( pStartNode->StartOfSectionNode() != pStartNode ) && + ( pStartNode->GetStartNodeType() == SwNormalStartNode ) ) + pStartNode = pStartNode->StartOfSectionNode(); + + return pStartNode; + } + + bool lcl_PosCorrAbs(SwPosition & rPos, + const SwPosition& rStart, + const SwPosition& rEnd, + const SwPosition& rNewPos) + { + if ((rStart <= rPos) && (rPos <= rEnd)) + { + rPos = rNewPos; + return true; + } + return false; + }; + + bool lcl_PaMCorrAbs(SwPaM & rPam, + const SwPosition& rStart, + const SwPosition& rEnd, + const SwPosition& rNewPos) + { + bool bRet = false; + bRet |= lcl_PosCorrAbs(rPam.GetBound(), rStart, rEnd, rNewPos); + bRet |= lcl_PosCorrAbs(rPam.GetBound(false), rStart, rEnd, rNewPos); + return bRet; + }; + + void lcl_PaMCorrRel1(SwPaM * pPam, + SwNode const * const pOldNode, + const SwPosition& rNewPos, + const sal_Int32 nCntIdx) + { + for(int nb = 0; nb < 2; ++nb) + if(&(pPam->GetBound(bool(nb)).nNode.GetNode()) == pOldNode) + { + pPam->GetBound(bool(nb)).nNode = rNewPos.nNode; + pPam->GetBound(bool(nb)).nContent.Assign( + const_cast<SwIndexReg*>(rNewPos.nContent.GetIdxReg()), + nCntIdx + pPam->GetBound(bool(nb)).nContent.GetIndex()); + } + } +} + +void PaMCorrAbs( const SwPaM& rRange, + const SwPosition& rNewPos ) +{ + SwPosition const aStart( *rRange.Start() ); + SwPosition const aEnd( *rRange.End() ); + SwPosition const aNewPos( rNewPos ); + SwDoc *const pDoc = aStart.nNode.GetNode().GetDoc(); + SwCursorShell *const pShell = pDoc->GetEditShell(); + + if( pShell ) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + if(dynamic_cast<const SwCursorShell *>(&rShell) == nullptr) + continue; + const SwCursorShell* pCursorShell = static_cast<const SwCursorShell*>(&rShell); + SwPaM *_pStackCursor = pCursorShell->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_PaMCorrAbs( *_pStackCursor, aStart, aEnd, aNewPos ); + if( !_pStackCursor ) + break; + _pStackCursor = _pStackCursor->GetNext(); + if( _pStackCursor == pCursorShell->GetStackCursor() ) + break; + } + + for(SwPaM& rPaM : const_cast<SwShellCursor*>(pCursorShell->GetCursor_())->GetRingContainer()) + { + lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + + if( pCursorShell->IsTableMode() ) + lcl_PaMCorrAbs( const_cast<SwPaM &>(*pCursorShell->GetTableCrs()), aStart, aEnd, aNewPos ); + } + } + + pDoc->cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + + bool bChange = false; // has the UNO cursor been corrected? + + // determine whether the UNO cursor will leave it's designated + // section + bool const bLeaveSection = + pUnoCursor->IsRemainInSection() && + ( lcl_FindUnoCursorSection( aNewPos.nNode.GetNode() ) != + lcl_FindUnoCursorSection( + pUnoCursor->GetPoint()->nNode.GetNode() ) ); + + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + bChange |= lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + + SwUnoTableCursor *const pUnoTableCursor = + dynamic_cast<SwUnoTableCursor *>(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : pUnoTableCursor->GetSelRing().GetRingContainer()) + { + bChange |= + lcl_PaMCorrAbs( rPaM, aStart, aEnd, aNewPos ); + } + } + + // if a UNO cursor leaves its designated section, we must inform + // (and invalidate) said cursor + if (bChange && bLeaveSection) + { + // the UNO cursor has left its section. We need to notify it! + sw::UnoCursorHint aHint; + pUnoCursor->m_aNotifier.Broadcast(aHint); + } + } +} + +void SwDoc::CorrAbs(const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset, + bool bMoveCursor) +{ + SwContentNode *const pContentNode( rOldNode.GetNode().GetContentNode() ); + SwPaM const aPam(rOldNode, 0, + rOldNode, pContentNode ? pContentNode->Len() : 0); + SwPosition aNewPos(rNewPos); + aNewPos.nContent += nOffset; + + getIDocumentMarkAccess()->correctMarksAbsolute(rOldNode, rNewPos, nOffset); + // fix redlines + { + SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type n = 0; n < rTable.size(); ) + { + // is on position ?? + SwRangeRedline *const pRedline( rTable[ n ] ); + bool const bChanged = + lcl_PaMCorrAbs(*pRedline, *aPam.Start(), *aPam.End(), aNewPos); + // clean up empty redlines: docredln.cxx asserts these as invalid + if (bChanged && (*pRedline->GetPoint() == *pRedline->GetMark()) + && (pRedline->GetContentIdx() == nullptr)) + { + rTable.DeleteAndDestroy(n); + } + else + { + ++n; + } + } + + // To-Do - need to add here 'SwExtraRedlineTable' also ? + } + + if(bMoveCursor) + { + ::PaMCorrAbs(aPam, aNewPos); + } +} + +void SwDoc::CorrAbs( + const SwPaM& rRange, + const SwPosition& rNewPos, + bool bMoveCursor ) +{ + SwPosition aStart(*rRange.Start()); + SwPosition aEnd(*rRange.End()); + + DelBookmarks( aStart.nNode, aEnd.nNode, nullptr, &aStart.nContent, &aEnd.nContent ); + + if(bMoveCursor) + ::PaMCorrAbs(rRange, rNewPos); +} + +void SwDoc::CorrAbs( + const SwNodeIndex& rStartNode, + const SwNodeIndex& rEndNode, + const SwPosition& rNewPos, + bool bMoveCursor ) +{ + DelBookmarks( rStartNode, rEndNode ); + + if(bMoveCursor) + { + SwContentNode *const pContentNode( rEndNode.GetNode().GetContentNode() ); + SwPaM const aPam(rStartNode, 0, + rEndNode, pContentNode ? pContentNode->Len() : 0); + ::PaMCorrAbs(aPam, rNewPos); + } +} + +void PaMCorrRel( const SwNodeIndex &rOldNode, + const SwPosition &rNewPos, + const sal_Int32 nOffset ) +{ + const SwNode* pOldNode = &rOldNode.GetNode(); + SwPosition aNewPos( rNewPos ); + const SwDoc* pDoc = pOldNode->GetDoc(); + + const sal_Int32 nCntIdx = rNewPos.nContent.GetIndex() + nOffset; + + SwCursorShell const* pShell = pDoc->GetEditShell(); + if( pShell ) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + if(dynamic_cast<const SwCursorShell *>(&rShell) == nullptr) + continue; + SwCursorShell* pCursorShell = const_cast<SwCursorShell*>(static_cast<const SwCursorShell*>(&rShell)); + SwPaM *_pStackCursor = pCursorShell->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_PaMCorrRel1( _pStackCursor, pOldNode, aNewPos, nCntIdx ); + if( !_pStackCursor ) + break; + _pStackCursor = _pStackCursor->GetNext(); + if( _pStackCursor == pCursorShell->GetStackCursor() ) + break; + } + + SwPaM* pStartPaM = pCursorShell->GetCursor_(); + for(SwPaM& rPaM : pStartPaM->GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx); + } + + if( pCursorShell->IsTableMode() ) + lcl_PaMCorrRel1( pCursorShell->GetTableCrs(), pOldNode, aNewPos, nCntIdx ); + } + } + + pDoc->cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : pDoc->mvUnoCursorTable) + { + auto pUnoCursor(pWeakUnoCursor.lock()); + if(!pUnoCursor) + continue; + for(SwPaM& rPaM : pUnoCursor->GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx ); + } + + SwUnoTableCursor* pUnoTableCursor = + dynamic_cast<SwUnoTableCursor*>(pUnoCursor.get()); + if( pUnoTableCursor ) + { + for(SwPaM& rPaM : pUnoTableCursor->GetSelRing().GetRingContainer()) + { + lcl_PaMCorrRel1( &rPaM, pOldNode, aNewPos, nCntIdx ); + } + } + } +} + +void SwDoc::CorrRel(const SwNodeIndex& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset, + bool bMoveCursor) +{ + getIDocumentMarkAccess()->correctMarksRelative(rOldNode, rNewPos, nOffset); + + { // fix the Redlines + SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable(); + SwPosition aNewPos(rNewPos); + for(SwRangeRedline* p : rTable) + { + // lies on the position ?? + lcl_PaMCorrRel1( p, &rOldNode.GetNode(), aNewPos, aNewPos.nContent.GetIndex() + nOffset ); + } + + // To-Do - need to add here 'SwExtraRedlineTable' also ? + } + + if(bMoveCursor) + ::PaMCorrRel(rOldNode, rNewPos, nOffset); +} + +SwEditShell const * SwDoc::GetEditShell() const +{ + SwViewShell const *pCurrentView = getIDocumentLayoutAccess().GetCurrentViewShell(); + // Layout and OLE shells should be available + if( pCurrentView ) + { + for(const SwViewShell& rCurrentSh : pCurrentView->GetRingContainer()) + { + // look for an EditShell (if it exists) + if( dynamic_cast<const SwEditShell *>(&rCurrentSh) != nullptr ) + { + return static_cast<const SwEditShell*>(&rCurrentSh); + } + } + } + return nullptr; +} + +SwEditShell* SwDoc::GetEditShell() +{ + return const_cast<SwEditShell*>( const_cast<SwDoc const *>( this )->GetEditShell() ); +} + +::sw::IShellCursorSupplier * SwDoc::GetIShellCursorSupplier() +{ + return GetEditShell(); +} + +//bool foo() +//{ +// bool b1 = true ? true : false; +// bool b2 = (true ? true : false) ? true : false; +// bool b3 = true ? (true ? true : false) : false; +// bool b4 = true ? true : (true ? true : false); +// return false; +//} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docdesc.cxx b/sw/source/core/doc/docdesc.cxx new file mode 100644 index 000000000..51dc930f0 --- /dev/null +++ b/sw/source/core/doc/docdesc.cxx @@ -0,0 +1,922 @@ +/* -*- 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 <cmdid.h> +#include <init.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/paperinf.hxx> +#include <editeng/frmdiritem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <tools/globname.hxx> +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <fmtfsize.hxx> +#include <fmthdft.hxx> +#include <fmtcntnt.hxx> +#include <ftninfo.hxx> +#include <fesh.hxx> +#include <ndole.hxx> +#include <mdiexp.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> +#include <poolfmt.hxx> +#include <docsh.hxx> +#include <ftnidx.hxx> +#include <fmtftn.hxx> +#include <txtftn.hxx> +#include <fldbas.hxx> +#include <GetMetricVal.hxx> +#include <strings.hrc> +#include <hints.hxx> +#include <SwUndoPageDesc.hxx> +#include <pagedeschint.hxx> +#include <tgrditem.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/syslocale.hxx> +#include <svx/swframetypes.hxx> +#include <svx/svxids.hrc> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +using namespace com::sun::star; + +static void lcl_DefaultPageFormat( sal_uInt16 nPoolFormatId, + SwFrameFormat &rFormat1, + SwFrameFormat &rFormat2, + SwFrameFormat &rFormat3, + SwFrameFormat &rFormat4) +{ + // --> #i41075# Printer on demand + // This function does not require a printer anymore. + // The default page size is obtained from the application + //locale + + SwFormatFrameSize aFrameSize( SwFrameSize::Fixed ); + const Size aPhysSize = SvxPaperInfo::GetDefaultPaperSize(); + aFrameSize.SetSize( aPhysSize ); + + // Prepare for default margins. + // Margins have a default minimum size. + // If the printer forces a larger margins, that's ok too. + // The HTML page desc had A4 as page size always. + // This has been changed to take the page size from the printer. + // Unfortunately, the margins of the HTML page desc are smaller than + // the margins used here in general, so one extra case is required. + // In the long term, this needs to be changed to always keep the + // margins from the page desc. + sal_Int32 nMinTop, nMinBottom, nMinLeft, nMinRight; + if( RES_POOLPAGE_HTML == nPoolFormatId ) + { + nMinRight = nMinTop = nMinBottom = GetMetricVal( CM_1 ); + nMinLeft = nMinRight * 2; + } + else if (!utl::ConfigManager::IsFuzzing() && MeasurementSystem::Metric == SvtSysLocale().GetLocaleData().getMeasurementSystemEnum() ) + { + nMinTop = nMinBottom = nMinLeft = nMinRight = 1134; // 2 centimeters + } + else + { + nMinTop = nMinBottom = 1440; // as in MS Word: 1 Inch + nMinLeft = nMinRight = 1800; // 1,25 Inch + } + + // set margins + SvxLRSpaceItem aLR( RES_LR_SPACE ); + SvxULSpaceItem aUL( RES_UL_SPACE ); + + aUL.SetUpper( static_cast<sal_uInt16>(nMinTop) ); + aUL.SetLower( static_cast<sal_uInt16>(nMinBottom) ); + aLR.SetRight( nMinRight ); + aLR.SetLeft( nMinLeft ); + + rFormat1.SetFormatAttr( aFrameSize ); + rFormat1.SetFormatAttr( aLR ); + rFormat1.SetFormatAttr( aUL ); + + rFormat2.SetFormatAttr( aFrameSize ); + rFormat2.SetFormatAttr( aLR ); + rFormat2.SetFormatAttr( aUL ); + + rFormat3.SetFormatAttr( aFrameSize ); + rFormat3.SetFormatAttr( aLR ); + rFormat3.SetFormatAttr( aUL ); + + rFormat4.SetFormatAttr( aFrameSize ); + rFormat4.SetFormatAttr( aLR ); + rFormat4.SetFormatAttr( aUL ); +} + +static void lcl_DescSetAttr( const SwFrameFormat &rSource, SwFrameFormat &rDest, + const bool bPage = true ) +{ + // We should actually use ItemSet's Intersect here, but that doesn't work + // correctly if we have different WhichRanges. + + // Take over the attributes which are of interest. + sal_uInt16 const aIdArr[] = { + RES_FRM_SIZE, RES_UL_SPACE, // [83..86 + RES_BACKGROUND, RES_SHADOW, // [99..101 + RES_COL, RES_COL, // [103 + RES_TEXTGRID, RES_TEXTGRID, // [109 + RES_FRAMEDIR, RES_FRAMEDIR, // [114 + RES_HEADER_FOOTER_EAT_SPACING, RES_HEADER_FOOTER_EAT_SPACING, // [115 + RES_UNKNOWNATR_CONTAINER, RES_UNKNOWNATR_CONTAINER, // [143 + + // take over DrawingLayer FillStyles + XATTR_FILL_FIRST, XATTR_FILL_LAST, // [1014 + + 0}; + + const SfxPoolItem* pItem; + for( sal_uInt16 n = 0; aIdArr[ n ]; n += 2 ) + { + for( sal_uInt16 nId = aIdArr[ n ]; nId <= aIdArr[ n+1]; ++nId ) + { + // #i45539# + // bPage == true: + // All in aIdArr except from RES_HEADER_FOOTER_EAT_SPACING + // bPage == false: + // All in aIdArr except from RES_COL and RES_PAPER_BIN: + bool bExecuteId(true); + + if(bPage) + { + // When Page + switch(nId) + { + // All in aIdArr except from RES_HEADER_FOOTER_EAT_SPACING + case RES_HEADER_FOOTER_EAT_SPACING: + // take out SvxBrushItem; it's the result of the fallback + // at SwFormat::GetItemState and not really in state SfxItemState::SET + case RES_BACKGROUND: + bExecuteId = false; + break; + default: + break; + } + } + else + { + // When not Page + switch(nId) + { + // When not Page: All in aIdArr except from RES_COL and RES_PAPER_BIN: + case RES_COL: + case RES_PAPER_BIN: + bExecuteId = false; + break; + default: + break; + } + } + + if(bExecuteId) + { + if (SfxItemState::SET == rSource.GetItemState(nId, false, &pItem)) + { + rDest.SetFormatAttr(*pItem); + } + else + { + rDest.ResetFormatAttr(nId); + } + } + } + } + + // Transmit pool and help IDs too + rDest.SetPoolFormatId( rSource.GetPoolFormatId() ); + rDest.SetPoolHelpId( rSource.GetPoolHelpId() ); + rDest.SetPoolHlpFileId( rSource.GetPoolHlpFileId() ); +} + +namespace +{ + SwFrameFormat& getFrameFormat(SwPageDesc &rDesc, bool bLeft, bool bFirst) + { + if (bFirst) + { + if (bLeft) + return rDesc.GetFirstLeft(); + return rDesc.GetFirstMaster(); + } + return rDesc.GetLeft(); + } + + const SwFrameFormat& getConstFrameFormat(const SwPageDesc &rDesc, bool bLeft, bool bFirst) + { + return getFrameFormat(const_cast<SwPageDesc&>(rDesc), bLeft, bFirst); + } +} + +void SwDoc::CopyMasterHeader(const SwPageDesc &rChged, const SwFormatHeader &rHead, SwPageDesc &rDesc, bool bLeft, bool bFirst) +{ + assert(bLeft || bFirst); + SwFrameFormat& rDescFrameFormat = getFrameFormat(rDesc, bLeft, bFirst); + if (bFirst && bLeft) + { + // special case: always shared with something + rDescFrameFormat.SetFormatAttr( rChged.IsFirstShared() + ? rDesc.GetLeft().GetHeader() + : rDesc.GetFirstMaster().GetHeader()); + } + else if ((bFirst ? rChged.IsFirstShared() : rChged.IsHeaderShared()) + || !rHead.IsActive()) + { + // Left or first shares the header with the Master. + rDescFrameFormat.SetFormatAttr( rDesc.GetMaster().GetHeader() ); + } + else if ( rHead.IsActive() ) + { // Left or first gets its own header if the Format doesn't already have one. + // If it already has one and it points to the same Section as the + // Right one, it needs to get an own Header. + // The content is evidently copied. + const SwFormatHeader &rFormatHead = rDescFrameFormat.GetHeader(); + if ( !rFormatHead.IsActive() ) + { + SwFormatHeader aHead( getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::HEADERL, nullptr ) ); + rDescFrameFormat.SetFormatAttr( aHead ); + // take over additional attributes (margins, borders ...) + ::lcl_DescSetAttr( *rHead.GetHeaderFormat(), *aHead.GetHeaderFormat(), false); + } + else + { + const SwFrameFormat *pRight = rHead.GetHeaderFormat(); + const SwFormatContent &aRCnt = pRight->GetContent(); + const SwFormatContent &aCnt = rFormatHead.GetHeaderFormat()->GetContent(); + + if (!aCnt.GetContentIdx()) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetHeader() ); + } + else if ((*aRCnt.GetContentIdx() == *aCnt.GetContentIdx()) || + // The ContentIdx is _always_ different when called from + // SwDocStyleSheet::SetItemSet, because it deep-copies the + // PageDesc. So check if it was previously shared. + (bFirst ? rDesc.IsFirstShared() : rDesc.IsHeaderShared())) + { + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), + bFirst ? "First header" : "Left header", + GetDfltFrameFormat() ); + ::lcl_DescSetAttr( *pRight, *pFormat, false ); + // The section which the right header attribute is pointing + // is copied, and the Index to the StartNode is set to + // the left or first header attribute. + SwNodeIndex aTmp( GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmp, SwHeaderStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), 0, + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + aTmp = *pSttNd->EndOfSectionNode(); + GetNodes().Copy_( aRange, aTmp, false ); + aTmp = *pSttNd; + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(aTmp); + sw::CopyBookmarks(source, dest); + pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); + rDescFrameFormat.SetFormatAttr( SwFormatHeader( pFormat ) ); + } + else + ::lcl_DescSetAttr( *pRight, + *const_cast<SwFrameFormat*>(rFormatHead.GetHeaderFormat()), false ); + } + } +} + +void SwDoc::CopyMasterFooter(const SwPageDesc &rChged, const SwFormatFooter &rFoot, SwPageDesc &rDesc, bool bLeft, bool bFirst) +{ + assert(bLeft || bFirst); + SwFrameFormat& rDescFrameFormat = getFrameFormat(rDesc, bLeft, bFirst); + if (bFirst && bLeft) + { + // special case: always shared with something + rDescFrameFormat.SetFormatAttr( rChged.IsFirstShared() + ? rDesc.GetLeft().GetFooter() + : rDesc.GetFirstMaster().GetFooter()); + } + else if ((bFirst ? rChged.IsFirstShared() : rChged.IsFooterShared()) + || !rFoot.IsActive()) + { + // Left or first shares the Header with the Master. + rDescFrameFormat.SetFormatAttr( rDesc.GetMaster().GetFooter() ); + } + else if ( rFoot.IsActive() ) + { // Left or first gets its own Footer if the Format does not already have one. + // If the Format already has a Footer and it points to the same section as the Right one, + // it needs to get an own one. + // The content is evidently copied. + const SwFormatFooter &rFormatFoot = rDescFrameFormat.GetFooter(); + if ( !rFormatFoot.IsActive() ) + { + SwFormatFooter aFoot( getIDocumentLayoutAccess().MakeLayoutFormat( RndStdIds::FOOTER, nullptr ) ); + rDescFrameFormat.SetFormatAttr( aFoot ); + // Take over additional attributes (margins, borders ...). + ::lcl_DescSetAttr( *rFoot.GetFooterFormat(), *aFoot.GetFooterFormat(), false); + } + else + { + const SwFrameFormat *pRight = rFoot.GetFooterFormat(); + const SwFormatContent &aRCnt = pRight->GetContent(); + const SwFormatContent &aLCnt = rFormatFoot.GetFooterFormat()->GetContent(); + if( !aLCnt.GetContentIdx() ) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetFooter() ); + } + else if ((*aRCnt.GetContentIdx() == *aLCnt.GetContentIdx()) || + // The ContentIdx is _always_ different when called from + // SwDocStyleSheet::SetItemSet, because it deep-copies the + // PageDesc. So check if it was previously shared. + (bFirst ? rDesc.IsFirstShared() : rDesc.IsFooterShared())) + { + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), + bFirst ? "First footer" : "Left footer", + GetDfltFrameFormat() ); + ::lcl_DescSetAttr( *pRight, *pFormat, false ); + // The section to which the right footer attribute is pointing + // is copied, and the Index to the StartNode is set to + // the left footer attribute. + SwNodeIndex aTmp( GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmp, SwFooterStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), 0, + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + aTmp = *pSttNd->EndOfSectionNode(); + GetNodes().Copy_( aRange, aTmp, false ); + aTmp = *pSttNd; + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, aTmp); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(aTmp); + sw::CopyBookmarks(source, dest); + pFormat->SetFormatAttr( SwFormatContent( pSttNd ) ); + rDescFrameFormat.SetFormatAttr( SwFormatFooter( pFormat ) ); + } + else + ::lcl_DescSetAttr( *pRight, + *const_cast<SwFrameFormat*>(rFormatFoot.GetFooterFormat()), false ); + } + } +} + +void SwDoc::ChgPageDesc( size_t i, const SwPageDesc &rChged ) +{ + assert(i < m_PageDescs.size() && "PageDescs is out of range."); + + SwPageDesc& rDesc = *m_PageDescs[i]; + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoPageDesc>(rDesc, rChged, this)); + } + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Mirror at first if needed. + if ( rChged.GetUseOn() == UseOnPage::Mirror ) + const_cast<SwPageDesc&>(rChged).Mirror(); + else + { + // Or else transfer values from Master to Left + ::lcl_DescSetAttr(rChged.GetMaster(), + const_cast<SwPageDesc&>(rChged).GetLeft()); + } + ::lcl_DescSetAttr(rChged.GetMaster(), + const_cast<SwPageDesc&>(rChged).GetFirstMaster()); + ::lcl_DescSetAttr(rChged.GetLeft(), + const_cast<SwPageDesc&>(rChged).GetFirstLeft()); + + // Take over NumType. + if( rChged.GetNumType().GetNumberingType() != rDesc.GetNumType().GetNumberingType() ) + { + rDesc.SetNumType( rChged.GetNumType() ); + // Notify page number fields that NumFormat has changed + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::PageNumber )->UpdateFields(); + getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::RefPageGet )->UpdateFields(); + + // If the numbering scheme has changed we could have QuoVadis/ErgoSum texts + // that refer to a changed page, so we invalidate foot notes. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( SwFootnoteIdxs::size_type nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + + // Take over orientation + rDesc.SetLandscape( rChged.GetLandscape() ); + + // #i46909# no undo if header or footer changed + bool bHeaderFooterChanged = false; + + // Synch header. + const SwFormatHeader &rHead = rChged.GetMaster().GetHeader(); + if (undoGuard.UndoWasEnabled()) + { + // #i46909# no undo if header or footer changed + // Did something change in the nodes? + const SwFormatHeader &rOldHead = rDesc.GetMaster().GetHeader(); + bHeaderFooterChanged |= + ( rHead.IsActive() != rOldHead.IsActive() || + rChged.IsHeaderShared() != rDesc.IsHeaderShared() || + rChged.IsFirstShared() != rDesc.IsFirstShared() ); + } + rDesc.GetMaster().SetFormatAttr( rHead ); + CopyMasterHeader(rChged, rHead, rDesc, true, false); // Copy left header + CopyMasterHeader(rChged, rHead, rDesc, false, true); // Copy first master + CopyMasterHeader(rChged, rHead, rDesc, true, true); // Copy first left + rDesc.ChgHeaderShare( rChged.IsHeaderShared() ); + + // Synch Footer. + const SwFormatFooter &rFoot = rChged.GetMaster().GetFooter(); + if (undoGuard.UndoWasEnabled()) + { + // #i46909# no undo if header or footer changed + // Did something change in the Nodes? + const SwFormatFooter &rOldFoot = rDesc.GetMaster().GetFooter(); + bHeaderFooterChanged |= + ( rFoot.IsActive() != rOldFoot.IsActive() || + rChged.IsFooterShared() != rDesc.IsFooterShared() ); + } + rDesc.GetMaster().SetFormatAttr( rFoot ); + CopyMasterFooter(rChged, rFoot, rDesc, true, false); // Copy left footer + CopyMasterFooter(rChged, rFoot, rDesc, false, true); // Copy first master + CopyMasterFooter(rChged, rFoot, rDesc, true, true); // Copy first left + rDesc.ChgFooterShare( rChged.IsFooterShared() ); + // there is just one first shared flag for both header and footer? + rDesc.ChgFirstShare( rChged.IsFirstShared() ); + + if ( rDesc.GetName() != rChged.GetName() ) + rDesc.SetName( rChged.GetName() ); + + // A RegisterChange is triggered, if necessary + rDesc.SetRegisterFormatColl( rChged.GetRegisterFormatColl() ); + + // If UseOn or the Follow change, the paragraphs need to know about it. + bool bUseOn = false; + bool bFollow = false; + if (rDesc.GetUseOn() != rChged.GetUseOn()) + { + rDesc.SetUseOn( rChged.GetUseOn() ); + bUseOn = true; + } + if (rDesc.GetFollow() != rChged.GetFollow()) + { + if (rChged.GetFollow() == &rChged) + { + if (rDesc.GetFollow() != &rDesc) + { + rDesc.SetFollow( &rDesc ); + bFollow = true; + } + } + else + { + rDesc.SetFollow( rChged.m_pFollow ); + bFollow = true; + } + } + + if ( (bUseOn || bFollow) && pTmpRoot) + // Inform layout! + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + + // Take over the page attributes. + ::lcl_DescSetAttr( rChged.GetMaster(), rDesc.GetMaster() ); + ::lcl_DescSetAttr( rChged.GetLeft(), rDesc.GetLeft() ); + ::lcl_DescSetAttr( rChged.GetFirstMaster(), rDesc.GetFirstMaster() ); + ::lcl_DescSetAttr( rChged.GetFirstLeft(), rDesc.GetFirstLeft() ); + + // If the FootnoteInfo changes, the pages are triggered. + if( !(rDesc.GetFootnoteInfo() == rChged.GetFootnoteInfo()) ) + { + rDesc.SetFootnoteInfo( rChged.GetFootnoteInfo() ); + sw::PageFootnoteHint aHint; + rDesc.GetMaster().CallSwClientNotify(aHint); + rDesc.GetLeft().CallSwClientNotify(aHint); + rDesc.GetFirstMaster().CallSwClientNotify(aHint); + rDesc.GetFirstLeft().CallSwClientNotify(aHint); + } + getIDocumentState().SetModified(); + + // #i46909# no undo if header or footer changed + if( bHeaderFooterChanged ) + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + SfxBindings* pBindings = + ( GetDocShell() && GetDocShell()->GetDispatcher() ) ? GetDocShell()->GetDispatcher()->GetBindings() : nullptr; + if ( pBindings ) + { + pBindings->Invalidate( SID_ATTR_PAGE_COLUMN ); + pBindings->Invalidate( SID_ATTR_PAGE ); + pBindings->Invalidate( SID_ATTR_PAGE_SIZE ); + pBindings->Invalidate( SID_ATTR_PAGE_ULSPACE ); + pBindings->Invalidate( SID_ATTR_PAGE_LRSPACE ); + } + + //h/f of first-left page must not be unique but same as first master or left + assert((rDesc.IsFirstShared()) + ? rDesc.GetFirstLeft().GetHeader().GetHeaderFormat() == rDesc.GetLeft().GetHeader().GetHeaderFormat() + : rDesc.GetFirstLeft().GetHeader().GetHeaderFormat() == rDesc.GetFirstMaster().GetHeader().GetHeaderFormat()); + assert((rDesc.IsFirstShared()) + ? rDesc.GetFirstLeft().GetFooter().GetFooterFormat() == rDesc.GetLeft().GetFooter().GetFooterFormat() + : rDesc.GetFirstLeft().GetFooter().GetFooterFormat() == rDesc.GetFirstMaster().GetFooter().GetFooterFormat()); +} + +/// All descriptors whose Follow point to the to-be-deleted have to be adapted. +// #i7983# +void SwDoc::PreDelPageDesc(SwPageDesc const * pDel) +{ + if (nullptr == pDel) + return; + + // mba: test iteration as clients are removed while iteration + SwPageDescHint aHint( m_PageDescs[0] ); + pDel->CallSwClientNotify( aHint ); + + bool bHasLayout = getIDocumentLayoutAccess().HasLayout(); + if ( mpFootnoteInfo->DependsOn( pDel ) ) + { + mpFootnoteInfo->ChgPageDesc( m_PageDescs[0] ); + if ( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(false); + } + } + else if ( mpEndNoteInfo->DependsOn( pDel ) ) + { + mpEndNoteInfo->ChgPageDesc( m_PageDescs[0] ); + if ( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(true); + } + } + + for (SwPageDesc* pPageDesc : m_PageDescs) + { + if (pPageDesc->GetFollow() == pDel) + { + pPageDesc->SetFollow(nullptr); + if( bHasLayout ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + } + } +} + +void SwDoc::BroadcastStyleOperation(const OUString& rName, SfxStyleFamily eFamily, + SfxHintId nOp) +{ + if (mpDocShell) + { + SfxStyleSheetBasePool * pPool = mpDocShell->GetStyleSheetPool(); + + if (pPool) + { + SfxStyleSheetBase* pBase = pPool->Find(rName, eFamily); + + if (pBase != nullptr) + pPool->Broadcast(SfxStyleSheetHint( nOp, *pBase )); + } + } +} + +void SwDoc::DelPageDesc( size_t i, bool bBroadcast ) +{ + OSL_ENSURE(i < m_PageDescs.size(), "PageDescs is out of range."); + OSL_ENSURE( i != 0, "You cannot delete the default Pagedesc."); + if ( i == 0 ) + return; + + SwPageDesc &rDel = *m_PageDescs[i]; + + if (bBroadcast) + BroadcastStyleOperation(rDel.GetName(), SfxStyleFamily::Page, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoPageDescDelete>(rDel, this)); + } + + PreDelPageDesc(&rDel); // #i7983# + + m_PageDescs.erase(m_PageDescs.begin() + i); + getIDocumentState().SetModified(); +} + +SwPageDesc* SwDoc::MakePageDesc(const OUString &rName, const SwPageDesc *pCpy, + bool bRegardLanguage, bool bBroadcast) +{ + SwPageDesc *pNew; + if( pCpy ) + { + pNew = new SwPageDesc( *pCpy ); + pNew->SetName( rName ); + if( rName != pCpy->GetName() ) + { + pNew->SetPoolFormatId( USHRT_MAX ); + pNew->SetPoolHelpId( USHRT_MAX ); + pNew->SetPoolHlpFileId( UCHAR_MAX ); + } + } + else + { + pNew = new SwPageDesc( rName, GetDfltFrameFormat(), this ); + // Set the default page format. + lcl_DefaultPageFormat( USHRT_MAX, pNew->GetMaster(), pNew->GetLeft(), pNew->GetFirstMaster(), pNew->GetFirstLeft() ); + + SvxFrameDirection aFrameDirection = bRegardLanguage ? + GetDefaultFrameDirection(GetAppLanguage()) + : SvxFrameDirection::Horizontal_LR_TB; + + pNew->GetMaster().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetLeft().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetFirstMaster().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + pNew->GetFirstLeft().SetFormatAttr( SvxFrameDirectionItem(aFrameDirection, RES_FRAMEDIR) ); + } + + std::pair<SwPageDescs::const_iterator, bool> res = m_PageDescs.push_back( pNew ); + SAL_WARN_IF(!res.second, "sw", "MakePageDesc called with existing name" ); + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Page, + SfxHintId::StyleSheetCreated); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoPageDescCreate>(pNew, this)); + } + + getIDocumentState().SetModified(); + return pNew; +} + +void SwDoc::PrtOLENotify( bool bAll ) +{ + SwFEShell *pShell = nullptr; + { + SwViewShell *pSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh ) + { + for(SwViewShell& rShell : pSh->GetRingContainer()) + { + if(dynamic_cast<const SwFEShell*>( &rShell) != nullptr) + { + pShell = static_cast<SwFEShell*>(&rShell); + break; + } + } + } + } + if ( !pShell ) + { + // This doesn't make sense without a Shell and thus without a client, because + // the communication about size changes is implemented by these components. + // Because we don't have a Shell we remember this unfortunate situation + // in the document, + // which is made up for later on when creating the first Shell. + mbOLEPrtNotifyPending = true; + if ( bAll ) + mbAllOLENotify = true; + } + else + { + if ( mbAllOLENotify ) + bAll = true; + + mbOLEPrtNotifyPending = mbAllOLENotify = false; + + std::unique_ptr<SwOLENodes> pNodes = SwContentNode::CreateOLENodesArray( *GetDfltGrfFormatColl(), !bAll ); + if ( pNodes ) + { + ::StartProgress( STR_STATSTR_SWGPRTOLENOTIFY, + 0, pNodes->size(), GetDocShell()); + getIDocumentLayoutAccess().GetCurrentLayout()->StartAllAction(); + + for( SwOLENodes::size_type i = 0; i < pNodes->size(); ++i ) + { + ::SetProgressState( i, GetDocShell() ); + + SwOLENode* pOLENd = (*pNodes)[i]; + pOLENd->SetOLESizeInvalid( false ); + + // At first load the Infos and see if it's not already in the exclude list. + SvGlobalName aName; + + svt::EmbeddedObjectRef& xObj = pOLENd->GetOLEObj().GetObject(); + if ( xObj.is() ) + aName = SvGlobalName( xObj->getClassID() ); + else // Not yet loaded + { + // TODO/LATER: retrieve ClassID of an unloaded object + // aName = ???? + } + + bool bFound = false; + for ( std::vector<SvGlobalName>::size_type j = 0; + j < pGlobalOLEExcludeList->size() && !bFound; + ++j ) + { + bFound = (*pGlobalOLEExcludeList)[j] == aName; + } + if ( bFound ) + continue; + + // We don't know it, so the object has to be loaded. + // If it doesn't want to be informed + if ( xObj.is() ) + { + pGlobalOLEExcludeList->push_back( aName ); + } + } + pNodes.reset(); + getIDocumentLayoutAccess().GetCurrentLayout()->EndAllAction(); + ::EndProgress( GetDocShell() ); + } + } +} + +IMPL_LINK_NOARG( SwDoc, DoUpdateModifiedOLE, Timer *, void ) +{ + SwFEShell* pSh = static_cast<SwFEShell*>(GetEditShell()); + if( pSh ) + { + mbOLEPrtNotifyPending = mbAllOLENotify = false; + + std::unique_ptr<SwOLENodes> pNodes = SwContentNode::CreateOLENodesArray( *GetDfltGrfFormatColl(), true ); + if( pNodes ) + { + ::StartProgress( STR_STATSTR_SWGPRTOLENOTIFY, + 0, pNodes->size(), GetDocShell()); + getIDocumentLayoutAccess().GetCurrentLayout()->StartAllAction(); + SwMsgPoolItem aMsgHint( RES_UPDATE_ATTR ); + + for( SwOLENodes::size_type i = 0; i < pNodes->size(); ++i ) + { + ::SetProgressState( i, GetDocShell() ); + + SwOLENode* pOLENd = (*pNodes)[i]; + pOLENd->SetOLESizeInvalid( false ); + + // We don't know it, so the object has to be loaded. + // If it doesn't want to be informed + if( pOLENd->GetOLEObj().GetOleRef().is() ) // Broken? + { + pOLENd->ModifyNotification( &aMsgHint, &aMsgHint ); + } + } + getIDocumentLayoutAccess().GetCurrentLayout()->EndAllAction(); + ::EndProgress( GetDocShell() ); + } + } +} + +static SwPageDesc* lcl_FindPageDesc( const SwPageDescs *pPageDescs, + size_t *pPos, const OUString &rName ) +{ + SwPageDesc* res = nullptr; + SwPageDescs::const_iterator it = pPageDescs->find( rName ); + if( it != pPageDescs->end() ) + { + res = *it; + if( pPos ) + *pPos = std::distance( pPageDescs->begin(), it ); + } + else if( pPos ) + *pPos = SIZE_MAX; + return res; +} + +SwPageDesc* SwDoc::FindPageDesc( const OUString & rName, size_t* pPos ) const +{ + return lcl_FindPageDesc( &m_PageDescs, pPos, rName ); +} + +bool SwDoc::ContainsPageDesc( const SwPageDesc *pDesc, size_t* pPos ) const +{ + if( pDesc == nullptr ) + return false; + if( !m_PageDescs.contains( const_cast <SwPageDesc*>( pDesc ) ) ) { + if( pPos ) + *pPos = SIZE_MAX; + return false; + } + if( ! pPos ) + return true; + + SwPageDesc* desc = lcl_FindPageDesc( + &m_PageDescs, pPos, pDesc->GetName() ); + SAL_WARN_IF( desc != pDesc, "sw", "SwPageDescs container is broken!" ); + return true; +} + +void SwDoc::DelPageDesc( const OUString & rName, bool bBroadcast ) +{ + size_t nI; + + if (FindPageDesc(rName, &nI)) + DelPageDesc(nI, bBroadcast); +} + +void SwDoc::ChgPageDesc( const OUString & rName, const SwPageDesc & rDesc) +{ + size_t nI; + + if (FindPageDesc(rName, &nI)) + ChgPageDesc(nI, rDesc); +} + +/* + * The HTML import cannot resist changing the page descriptions, I don't + * know why. This function is meant to check the page descriptors for invalid + * values. + */ +void SwDoc::CheckDefaultPageFormat() +{ + for ( size_t i = 0; i < GetPageDescCnt(); ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + + SwFrameFormat& rMaster = rDesc.GetMaster(); + SwFrameFormat& rLeft = rDesc.GetLeft(); + + const SwFormatFrameSize& rMasterSize = rMaster.GetFrameSize(); + const SwFormatFrameSize& rLeftSize = rLeft.GetFrameSize(); + + const bool bSetSize = INVALID_TWIPS == rMasterSize.GetWidth() || + INVALID_TWIPS == rMasterSize.GetHeight() || + INVALID_TWIPS == rLeftSize.GetWidth() || + INVALID_TWIPS == rLeftSize.GetHeight(); + + if ( bSetSize ) + lcl_DefaultPageFormat( rDesc.GetPoolFormatId(), rDesc.GetMaster(), rDesc.GetLeft(), rDesc.GetFirstMaster(), rDesc.GetFirstLeft() ); + } +} + +void SwDoc::SetDefaultPageMode(bool bSquaredPageMode) +{ + if( !bSquaredPageMode == !IsSquaredPageMode() ) + return; + + const SwTextGridItem& rGrid = GetDefault( RES_TEXTGRID ); + SwTextGridItem aNewGrid = rGrid; + aNewGrid.SetSquaredMode(bSquaredPageMode); + aNewGrid.Init(); + SetDefault(aNewGrid); + + for ( size_t i = 0; i < GetPageDescCnt(); ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + + SwFrameFormat& rMaster = rDesc.GetMaster(); + SwFrameFormat& rLeft = rDesc.GetLeft(); + + SwTextGridItem aGrid(rMaster.GetFormatAttr(RES_TEXTGRID)); + aGrid.SwitchPaperMode( bSquaredPageMode ); + rMaster.SetFormatAttr(aGrid); + rLeft.SetFormatAttr(aGrid); + } +} + +bool SwDoc::IsSquaredPageMode() const +{ + const SwTextGridItem& rGrid = GetDefault( RES_TEXTGRID ); + return rGrid.IsSquaredMode(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docdraw.cxx b/sw/source/core/doc/docdraw.cxx new file mode 100644 index 000000000..cdc449943 --- /dev/null +++ b/sw/source/core/doc/docdraw.cxx @@ -0,0 +1,634 @@ +/* -*- 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/flditem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/colritem.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdogrp.hxx> +#include <editeng/measfld.hxx> +#include <sal/log.hxx> +#include <fmtanchr.hxx> +#include <charatr.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <viewimp.hxx> +#include <doc.hxx> +#include <docfunc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <drawdoc.hxx> +#include <UndoDraw.hxx> +#include <swundo.hxx> +#include <dcontact.hxx> +#include <dview.hxx> +#include <mvsave.hxx> +#include <flyfrm.hxx> +#include <dflyobj.hxx> +#include <txtfrm.hxx> +#include <editeng/frmdiritem.hxx> +#include <fmtornt.hxx> +#include <svx/svditer.hxx> + +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::linguistic2; + +/** local method to determine positioning and alignment attributes for a drawing + * object, which is newly connected to the layout. + * + * Used for a newly formed group object <SwDoc::GroupSelection(..)> + * and the members of a destroyed group <SwDoc::UnGroupSelection(..)> + */ +static void lcl_AdjustPositioningAttr( SwDrawFrameFormat* _pFrameFormat, + const SdrObject& _rSdrObj ) +{ + const SwContact* pContact = GetUserCall( &_rSdrObj ); + OSL_ENSURE( pContact, "<lcl_AdjustPositioningAttr(..)> - missing contact object." ); + + // determine position of new group object relative to its anchor frame position + SwTwips nHoriRelPos = 0; + SwTwips nVertRelPos = 0; + { + const SwFrame* pAnchorFrame = pContact->GetAnchoredObj( &_rSdrObj )->GetAnchorFrame(); + OSL_ENSURE( !pAnchorFrame || + !pAnchorFrame->IsTextFrame() || + !static_cast<const SwTextFrame*>(pAnchorFrame)->IsFollow(), + "<lcl_AdjustPositioningAttr(..)> - anchor frame is a follow." ); + bool bVert = false; + bool bR2L = false; + // #i45952# - use anchor position of anchor frame, if it exist. + Point aAnchorPos; + if ( pAnchorFrame ) + { + // #i45952# + aAnchorPos = pAnchorFrame->GetFrameAnchorPos( ::HasWrap( &_rSdrObj ) ); + bVert = pAnchorFrame->IsVertical(); + bR2L = pAnchorFrame->IsRightToLeft(); + } + else + { + // #i45952# + aAnchorPos = _rSdrObj.GetAnchorPos(); + // If no anchor frame exist - e.g. because no layout exists - the + // default layout direction is taken. + const SvxFrameDirectionItem& rDirItem = + _pFrameFormat->GetAttrSet().GetPool()->GetDefaultItem( RES_FRAMEDIR ); + switch ( rDirItem.GetValue() ) + { + case SvxFrameDirection::Vertical_LR_TB: + { + // vertical from left-to-right + bVert = true; + bR2L = true; + OSL_FAIL( "<lcl_AdjustPositioningAttr(..)> - vertical from left-to-right not supported." ); + } + break; + case SvxFrameDirection::Vertical_RL_TB: + { + // vertical from right-to-left + bVert = true; + bR2L = false; + } + break; + case SvxFrameDirection::Horizontal_RL_TB: + { + // horizontal from right-to-left + bVert = false; + bR2L = true; + } + break; + case SvxFrameDirection::Horizontal_LR_TB: + { + // horizontal from left-to-right + bVert = false; + bR2L = false; + } + break; + case SvxFrameDirection::Environment: + SAL_WARN("sw.core", "lcl_AdjustPositioningAttr(..) SvxFrameDirection::Environment not supported"); + break; + default: break; + } + + } + // use geometry of drawing object + const SwRect aObjRect = _rSdrObj.GetSnapRect(); + + if ( bVert ) + { + if ( bR2L ) { + //SvxFrameDirection::Vertical_LR_TB + nHoriRelPos = aObjRect.Left() - aAnchorPos.getX(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } else { + //SvxFrameDirection::Vertical_RL_TB + nHoriRelPos = aObjRect.Top() - aAnchorPos.getY(); + nVertRelPos = aAnchorPos.getX() - aObjRect.Right(); + } + } + else if ( bR2L ) + { + nHoriRelPos = aAnchorPos.getX() - aObjRect.Right(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } + else + { + nHoriRelPos = aObjRect.Left() - aAnchorPos.getX(); + nVertRelPos = aObjRect.Top() - aAnchorPos.getY(); + } + } + + _pFrameFormat->SetFormatAttr( SwFormatHoriOrient( nHoriRelPos, text::HoriOrientation::NONE, text::RelOrientation::FRAME ) ); + _pFrameFormat->SetFormatAttr( SwFormatVertOrient( nVertRelPos, text::VertOrientation::NONE, text::RelOrientation::FRAME ) ); + // #i44334#, #i44681# - positioning attributes already set + _pFrameFormat->PosAttrSet(); + // #i34750# - keep current object rectangle for drawing + // objects. The object rectangle is used on events from the drawing layer + // to adjust the positioning attributes - see <SwDrawContact::Changed_(..)>. + { + const SwAnchoredObject* pAnchoredObj = pContact->GetAnchoredObj( &_rSdrObj ); + if ( auto pAnchoredDrawObj = dynamic_cast<const SwAnchoredDrawObject*>( pAnchoredObj) ) + { + const SwRect aObjRect = _rSdrObj.GetSnapRect(); + const_cast<SwAnchoredDrawObject*>(pAnchoredDrawObj) + ->SetLastObjRect( aObjRect.SVRect() ); + } + } +} + +SwDrawContact* SwDoc::GroupSelection( SdrView& rDrawView ) +{ + // replace marked 'virtual' drawing objects by the corresponding 'master' + // drawing objects. + SwDrawView::ReplaceMarkedDrawVirtObjs( rDrawView ); + + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + bool bNoGroup = ( nullptr == pObj->getParentSdrObjectFromSdrObject() ); + SwDrawContact* pNewContact = nullptr; + if( bNoGroup ) + { + SwDrawFrameFormat *pFormat = nullptr; + + // Revoke anchor attribute. + SwDrawContact *pMyContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + const SwFormatAnchor aAnch( pMyContact->GetFormat()->GetAnchor() ); + + std::unique_ptr<SwUndoDrawGroup> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoDrawGroup( static_cast<sal_uInt16>(rMrkList.GetMarkCount()) , this)); + + // #i53320# + bool bGroupMembersNotPositioned( false ); + { + SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast<SwAnchoredDrawObject*>(pMyContact->GetAnchoredObj( pObj )); + bGroupMembersNotPositioned = pAnchoredDrawObj->NotYetPositioned(); + } + // Destroy ContactObjects and formats. + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + + // #i53320# +#if OSL_DEBUG_LEVEL > 0 + SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast<SwAnchoredDrawObject*>(pContact->GetAnchoredObj( pObj )); + OSL_ENSURE( bGroupMembersNotPositioned == pAnchoredDrawObj->NotYetPositioned(), + "<SwDoc::GroupSelection(..)> - group members have different positioning status!" ); +#endif + + pFormat = static_cast<SwDrawFrameFormat*>(pContact->GetFormat()); + // Deletes itself! + pContact->Changed(*pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + if( pUndo ) + pUndo->AddObj( i, pFormat, pObj ); + else + DelFrameFormat( pFormat ); + + // #i45952# - re-introduce position normalization of group member + // objects, because its anchor position is cleared, when they are + // grouped. + Point aAnchorPos( pObj->GetAnchorPos() ); + pObj->NbcSetAnchorPos( Point( 0, 0 ) ); + pObj->NbcMove( Size( aAnchorPos.getX(), aAnchorPos.getY() ) ); + } + + pFormat = MakeDrawFrameFormat( GetUniqueDrawObjectName(), + GetDfltFrameFormat() ); + pFormat->SetFormatAttr( aAnch ); + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + + rDrawView.GroupMarked(); + OSL_ENSURE( rMrkList.GetMarkCount() == 1, "GroupMarked more or none groups." ); + + SdrObject* pNewGroupObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + pNewGroupObj->SetName(pFormat->GetName()); + pNewContact = new SwDrawContact( pFormat, pNewGroupObj ); + // #i35635# + pNewContact->MoveObjToVisibleLayer( pNewGroupObj ); + pNewContact->ConnectToLayout(); + // #i53320# - No adjustment of the positioning and alignment + // attributes, if group members aren't positioned yet. + if ( !bGroupMembersNotPositioned ) + { + // #i26791# - Adjust positioning and alignment attributes. + lcl_AdjustPositioningAttr( pFormat, *pNewGroupObj ); + } + + if( pUndo ) + { + pUndo->SetGroupFormat( pFormat ); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + else + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + } + + rDrawView.GroupMarked(); + OSL_ENSURE( rMrkList.GetMarkCount() == 1, "GroupMarked more or none groups." ); + } + + return pNewContact; +} + +void SwDoc::UnGroupSelection( SdrView& rDrawView ) +{ + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if( bUndo ) + { + GetIDocumentUndoRedo().ClearRedo(); + } + + // replace marked 'virtual' drawing objects by the corresponding 'master' + // drawing objects. + SwDrawView::ReplaceMarkedDrawVirtObjs( rDrawView ); + + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + std::unique_ptr<std::vector< std::pair< SwDrawFrameFormat*, SdrObject* > >[]> pFormatsAndObjs; + const size_t nMarkCount( rMrkList.GetMarkCount() ); + if ( nMarkCount ) + { + pFormatsAndObjs.reset( new std::vector< std::pair< SwDrawFrameFormat*, SdrObject* > >[nMarkCount] ); + SdrObject *pMyObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( !pMyObj->getParentSdrObjectFromSdrObject() ) + { + for ( size_t i = 0; i < nMarkCount; ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( dynamic_cast<const SdrObjGroup*>(pObj) != nullptr ) + { + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + SwFormatAnchor aAnch( pContact->GetFormat()->GetAnchor() ); + SdrObjList *pLst = static_cast<SdrObjGroup*>(pObj)->GetSubList(); + + SwUndoDrawUnGroup* pUndo = nullptr; + if( bUndo ) + { + pUndo = new SwUndoDrawUnGroup( static_cast<SdrObjGroup*>(pObj), this ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + for ( size_t i2 = 0; i2 < pLst->GetObjCount(); ++i2 ) + { + SdrObject* pSubObj = pLst->GetObj( i2 ); + SwDrawFrameFormat *pFormat = MakeDrawFrameFormat( GetUniqueShapeName(), + GetDfltFrameFormat() ); + pFormat->SetFormatAttr( aAnch ); + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + pFormatsAndObjs[i].emplace_back( pFormat, pSubObj ); + + if( bUndo ) + pUndo->AddObj( static_cast<sal_uInt16>(i2), pFormat ); + } + } + } + } + } + rDrawView.UnGroupMarked(); + // creation of <SwDrawContact> instances for the former group members and + // its connection to the Writer layout. + for ( size_t i = 0; i < nMarkCount; ++i ) + { + SwUndoDrawUnGroupConnectToLayout* pUndo = nullptr; + if( bUndo ) + { + pUndo = new SwUndoDrawUnGroupConnectToLayout(this); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + while ( !pFormatsAndObjs[i].empty() ) + { + SwDrawFrameFormat* pFormat( pFormatsAndObjs[i].back().first ); + SdrObject* pObj( pFormatsAndObjs[i].back().second ); + pFormatsAndObjs[i].pop_back(); + + SwDrawContact* pContact = new SwDrawContact( pFormat, pObj ); + pContact->MoveObjToVisibleLayer( pObj ); + pContact->ConnectToLayout(); + lcl_AdjustPositioningAttr( pFormat, *pObj ); + + if ( bUndo ) + { + pUndo->AddFormatAndObj( pFormat, pObj ); + } + } + } +} + +bool SwDoc::DeleteSelection( SwDrawView& rDrawView ) +{ + bool bCallBase = false; + const SdrMarkList &rMrkList = rDrawView.GetMarkedObjectList(); + if( rMrkList.GetMarkCount() ) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + bool bDelMarked = true; + + if( 1 == rMrkList.GetMarkCount() ) + { + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) != nullptr ) + { + SwFlyFrameFormat* pFrameFormat = + static_cast<SwVirtFlyDrawObj*>(pObj)->GetFlyFrame()->GetFormat(); + if( pFrameFormat ) + { + getIDocumentLayoutAccess().DelLayoutFormat( pFrameFormat ); + bDelMarked = false; + } + } + } + + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + SdrObject *pObj = rMrkList.GetMark( i )->GetMarkedSdrObj(); + if( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + { + SwDrawContact *pC = static_cast<SwDrawContact*>(GetUserCall(pObj)); + SwDrawFrameFormat *pFrameFormat = static_cast<SwDrawFrameFormat*>(pC->GetFormat()); + if( pFrameFormat && + RndStdIds::FLY_AS_CHAR == pFrameFormat->GetAnchor().GetAnchorId() ) + { + rDrawView.MarkObj( pObj, rDrawView.Imp().GetPageView(), true ); + --i; + getIDocumentLayoutAccess().DelLayoutFormat( pFrameFormat ); + } + } + } + + if( rMrkList.GetMarkCount() && bDelMarked ) + { + SdrObject *pObj = rMrkList.GetMark( 0 )->GetMarkedSdrObj(); + if( !pObj->getParentSdrObjectFromSdrObject() ) + { + std::unique_ptr<SwUndoDrawDelete> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoDrawDelete( static_cast<sal_uInt16>(rMrkList.GetMarkCount()), this )); + + // Destroy ContactObjects, save formats. + for( size_t i = 0; i < rMrkList.GetMarkCount(); ++i ) + { + const SdrMark& rMark = *rMrkList.GetMark( i ); + pObj = rMark.GetMarkedSdrObj(); + SwDrawContact *pContact = static_cast<SwDrawContact*>(pObj->GetUserCall()); + if( pContact ) // of course not for grouped objects + { + SwDrawFrameFormat *pFormat = static_cast<SwDrawFrameFormat*>(pContact->GetFormat()); + // before delete of selection is performed, marked + // <SwDrawVirtObj>-objects have to be replaced by its + // reference objects. Thus, assert, if a + // <SwDrawVirt>-object is found in the mark list. + if ( dynamic_cast<const SwDrawVirtObj*>( pObj) != nullptr ) + { + OSL_FAIL( "<SwDrawVirtObj> is still marked for delete. application will crash!" ); + } + // Deletes itself! + pContact->Changed(*pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + if( pUndo ) + pUndo->AddObj( pFormat, rMark ); + else + DelFrameFormat( pFormat ); + } + } + + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + } + bCallBase = true; + } + getIDocumentState().SetModified(); + + GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + + return bCallBase; +} + +ZSortFly::ZSortFly(const SwFrameFormat* pFrameFormat, const SwFormatAnchor* pFlyAn, sal_uInt32 nArrOrdNum) + : m_pFormat(pFrameFormat) + , m_pAnchor(pFlyAn) + , m_nOrdNum(nArrOrdNum) +{ + SAL_WARN_IF(m_pFormat->Which() != RES_FLYFRMFMT && m_pFormat->Which() != RES_DRAWFRMFMT, "sw.core", "What kind of format is this?"); + m_pFormat->CallSwClientNotify(sw::GetZOrderHint(m_nOrdNum)); +} + +/// In the Outliner, set a link to the method for field display in edit objects. +void SwDoc::SetCalcFieldValueHdl(Outliner* pOutliner) +{ + pOutliner->SetCalcFieldValueHdl(LINK(this, SwDoc, CalcFieldValueHdl)); +} + +/// Recognise fields/URLs in the Outliner and set how they are displayed. +IMPL_LINK(SwDoc, CalcFieldValueHdl, EditFieldInfo*, pInfo, void) +{ + if (!pInfo) + return; + + const SvxFieldItem& rField = pInfo->GetField(); + const SvxFieldData* pField = rField.GetField(); + + if (auto pDateField = dynamic_cast<const SvxDateField*>( pField)) + { + // Date field + pInfo->SetRepresentation( + pDateField->GetFormatted( + *GetNumberFormatter(), LANGUAGE_SYSTEM) ); + } + else if (auto pURLField = dynamic_cast<const SvxURLField*>( pField)) + { + // URL field + switch ( pURLField->GetFormat() ) + { + case SvxURLFormat::AppDefault: //!!! Can be set in App??? + case SvxURLFormat::Repr: + pInfo->SetRepresentation(pURLField->GetRepresentation()); + break; + + case SvxURLFormat::Url: + pInfo->SetRepresentation(pURLField->GetURL()); + break; + } + + sal_uInt16 nChrFormat; + + if (IsVisitedURL(pURLField->GetURL())) + nChrFormat = RES_POOLCHR_INET_VISIT; + else + nChrFormat = RES_POOLCHR_INET_NORMAL; + + SwFormat *pFormat = getIDocumentStylePoolAccess().GetCharFormatFromPool(nChrFormat); + + Color aColor(COL_LIGHTBLUE); + if (pFormat) + aColor = pFormat->GetColor().GetValue(); + + pInfo->SetTextColor(aColor); + } + else if (dynamic_cast<const SdrMeasureField*>( pField)) + { + // Clear measure field + pInfo->SetFieldColor(std::optional<Color>()); + } + else if ( auto pTimeField = dynamic_cast<const SvxExtTimeField*>( pField) ) + { + // Time field + pInfo->SetRepresentation( + pTimeField->GetFormatted(*GetNumberFormatter(), LANGUAGE_SYSTEM) ); + } + else + { + OSL_FAIL("unknown field command"); + pInfo->SetRepresentation( OUString( '?' ) ); + } +} + +// #i62875# +namespace docfunc +{ + bool ExistsDrawObjs( SwDoc& p_rDoc ) + { + bool bExistsDrawObjs( false ); + + if ( p_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && + p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ) ) + { + const SdrPage& rSdrPage( *(p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 )) ); + + SdrObjListIter aIter( &rSdrPage, SdrIterMode::Flat ); + while( aIter.IsMore() ) + { + SdrObject* pObj( aIter.Next() ); + if ( !dynamic_cast<SwVirtFlyDrawObj*>(pObj) && + !dynamic_cast<SwFlyDrawObj*>(pObj) ) + { + bExistsDrawObjs = true; + break; + } + } + } + + return bExistsDrawObjs; + } + + bool AllDrawObjsOnPage( SwDoc& p_rDoc ) + { + bool bAllDrawObjsOnPage( true ); + + if ( p_rDoc.getIDocumentDrawModelAccess().GetDrawModel() && + p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 ) ) + { + const SdrPage& rSdrPage( *(p_rDoc.getIDocumentDrawModelAccess().GetDrawModel()->GetPage( 0 )) ); + + SdrObjListIter aIter( &rSdrPage, SdrIterMode::Flat ); + while( aIter.IsMore() ) + { + SdrObject* pObj( aIter.Next() ); + if ( !dynamic_cast<SwVirtFlyDrawObj*>(pObj) && + !dynamic_cast<SwFlyDrawObj*>(pObj) ) + { + SwDrawContact* pDrawContact = + dynamic_cast<SwDrawContact*>(::GetUserCall( pObj )); + if ( pDrawContact ) + { + SwAnchoredDrawObject* pAnchoredDrawObj = + dynamic_cast<SwAnchoredDrawObject*>(pDrawContact->GetAnchoredObj( pObj )); + + // error handling + { + if ( !pAnchoredDrawObj ) + { + OSL_FAIL( "<docfunc::AllDrawObjsOnPage() - missing anchored draw object" ); + bAllDrawObjsOnPage = false; + break; + } + } + + if ( pAnchoredDrawObj->NotYetPositioned() ) + { + // The drawing object isn't yet layouted. + // Thus, it isn't known, if all drawing objects are on page. + bAllDrawObjsOnPage = false; + break; + } + else if ( pAnchoredDrawObj->IsOutsidePage() ) + { + bAllDrawObjsOnPage = false; + break; + } + } + else + { + // contact object of drawing object doesn't exists. + // Thus, the drawing object isn't yet positioned. + // Thus, it isn't known, if all drawing objects are on page. + bAllDrawObjsOnPage = false; + break; + } + } + } + } + + return bAllDrawObjsOnPage; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx new file mode 100644 index 000000000..07902efe3 --- /dev/null +++ b/sw/source/core/doc/docedt.cxx @@ -0,0 +1,886 @@ +/* -*- 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 <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <acorrect.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <docsh.hxx> +#include <docary.hxx> +#include <mdiexp.hxx> +#include <mvsave.hxx> +#include <redline.hxx> +#include <rootfrm.hxx> +#include <splargs.hxx> +#include <swcrsr.hxx> +#include <txtfrm.hxx> +#include <unoflatpara.hxx> +#include <SwGrammarMarkUp.hxx> +#include <docedt.hxx> +#include <frmfmt.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> +#include <frameformats.hxx> + +#include <vector> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/frame/XModel.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::linguistic2; +using namespace ::com::sun::star::i18n; + + +void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos, + const SwNodeIndex* pInsertPos ) +{ + SwPosition aPos(rStartPos); + for(const SaveFly & rSave : rArr) + { + // create new anchor + SwFrameFormat* pFormat = rSave.pFrameFormat; + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + + if (rSave.isAtInsertNode) + { + if( pInsertPos != nullptr ) + { + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + aPos.nNode = *pInsertPos; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), + rSave.nContentIndex); + } + else + { + assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR); + aPos = rStartPos; + } + } + else + { + aPos.nNode = rStartPos.nNode; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), 0); + } + } + else + { + aPos.nNode = rStartPos.nNode.GetIndex() + rSave.nNdDiff; + aPos.nContent.Assign(dynamic_cast<SwIndexReg*>(&aPos.nNode.GetNode()), + rSave.nNdDiff == 0 + ? rStartPos.nContent.GetIndex() + rSave.nContentIndex + : rSave.nContentIndex); + } + + aAnchor.SetAnchor( &aPos ); + pFormat->GetDoc()->GetSpzFrameFormats()->push_back( pFormat ); + // SetFormatAttr should call Modify() and add it to the node + pFormat->SetFormatAttr( aAnchor ); + SwContentNode* pCNd = aPos.nNode.GetNode().GetContentNode(); + if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) + pFormat->MakeFrames(); + } + sw::CheckAnchoredFlyConsistency(*rStartPos.nNode.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) +{ + SwFrameFormats& rFormats = *rRg.aStart.GetNode().GetDoc()->GetSpzFrameFormats(); + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + SwFrameFormat *const pFormat = rFormats[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + rRg.aStart <= pAPos->nNode && pAPos->nNode < rRg.aEnd ) + { + SaveFly aSave( pAPos->nNode.GetIndex() - rRg.aStart.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? pAPos->nContent.GetIndex() + : 0, + pFormat, false ); + rArr.push_back( aSave ); + pFormat->DelFrames(); + // set a dummy anchor position to maintain anchoring invariants + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + aAnchor.SetAnchor(nullptr); + pFormat->SetFormatAttr(aAnchor); + rFormats.erase( rFormats.begin() + n-- ); + } + } + sw::CheckAnchoredFlyConsistency(*rRg.aStart.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, + SaveFlyArr& rArr, bool bMoveAllFlys ) +{ + SwFrameFormats& rFormats = *rPam.GetPoint()->nNode.GetNode().GetDoc()->GetSpzFrameFormats(); + SwFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + + const SwPosition* pPos = rPam.Start(); + const SwNodeIndex& rSttNdIdx = pPos->nNode; + + SwPosition atParaEnd(*rPam.End()); + if (bMoveAllFlys) + { + assert(!rPam.End()->nNode.GetNode().IsTextNode() // can be table end-node + || rPam.End()->nContent.GetIndex() == rPam.End()->nNode.GetNode().GetTextNode()->Len()); + ++atParaEnd.nNode; + atParaEnd.nContent.Assign(atParaEnd.nNode.GetNode().GetContentNode(), 0); + } + + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + pFormat = rFormats[n]; + pAnchor = &pFormat->GetAnchor(); + const SwPosition* pAPos = pAnchor->GetContentAnchor(); + const SwNodeIndex* pContentIdx; + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + // do not move if the InsPos is in the ContentArea of the Fly + ( nullptr == ( pContentIdx = pFormat->GetContent().GetContentIdx() ) || + !(*pContentIdx < rInsPos.nNode && + rInsPos.nNode < pContentIdx->GetNode().EndOfSectionIndex()))) + { + bool bInsPos = false; + + if ( (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && IsDestroyFrameAnchoredAtChar(*pAPos, *rPam.Start(), *rPam.End())) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && IsSelectFrameAnchoredAtPara(*pAPos, *rPam.Start(), atParaEnd, + bMoveAllFlys + ? DelContentType::CheckNoCntnt|DelContentType::AllMask + : DelContentType::AllMask)) + || (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos.nNode == pAPos->nNode))) + || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos == *pAPos)))) + { + SaveFly aSave( pAPos->nNode.GetIndex() - rSttNdIdx.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? (pAPos->nNode == rSttNdIdx) + ? pAPos->nContent.GetIndex() - rPam.Start()->nContent.GetIndex() + : pAPos->nContent.GetIndex() + : 0, + pFormat, bInsPos ); + rArr.push_back( aSave ); + pFormat->DelFrames(); + // set a dummy anchor position to maintain anchoring invariants + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + aAnchor.SetAnchor(nullptr); + pFormat->SetFormatAttr(aAnchor); + rFormats.erase( rFormats.begin() + n-- ); + } + } + } + sw::CheckAnchoredFlyConsistency(*rPam.GetPoint()->nNode.GetNode().GetDoc()); +} + +/// Delete and move all Flys at the paragraph, that are within the selection. +/// If there is a Fly at the SPoint, it is moved onto the Mark. +void DelFlyInRange( const SwNodeIndex& rMkNdIdx, + const SwNodeIndex& rPtNdIdx, + SwIndex const*const pMkIdx, SwIndex const*const pPtIdx) +{ + assert((pMkIdx == nullptr) == (pPtIdx == nullptr)); + SwPosition const point(pPtIdx + ? SwPosition(rPtNdIdx, *pPtIdx) + : SwPosition(rPtNdIdx)); + SwPosition const mark(pPtIdx + ? SwPosition(rMkNdIdx, *pMkIdx) + : SwPosition(rMkNdIdx)); + SwPosition const& rStart = mark <= point ? mark : point; + SwPosition const& rEnd = mark <= point ? point : mark; + + SwDoc* pDoc = rMkNdIdx.GetNode().GetDoc(); + SwFrameFormats& rTable = *pDoc->GetSpzFrameFormats(); + for ( auto i = rTable.size(); i; ) + { + SwFrameFormat *pFormat = rTable[--i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + SwPosition const*const pAPos = rAnch.GetContentAnchor(); + if (pAPos && + (((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + && IsSelectFrameAnchoredAtPara(*pAPos, rStart, rEnd, pPtIdx + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt)) + || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, pPtIdx + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt)))) + { + // If the Fly is deleted, all Flys in its content have to be deleted too. + const SwFormatContent &rContent = pFormat->GetContent(); + // But only fly formats own their content, not draw formats. + if (rContent.GetContentIdx() && pFormat->Which() == RES_FLYFRMFMT) + { + DelFlyInRange( *rContent.GetContentIdx(), + SwNodeIndex( *rContent.GetContentIdx()-> + GetNode().EndOfSectionNode() )); + // Position could have been moved! + if (i > rTable.size()) + i = rTable.size(); + else if (pFormat != rTable[i]) + i = std::distance(rTable.begin(), rTable.find( pFormat )); + } + + pDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + + // DelLayoutFormat can also trigger the deletion of objects. + if (i > rTable.size()) + i = rTable.size(); + } + } +} + +// #i59534: Redo of insertion of multiple text nodes runs into trouble +// because of unnecessary expanded redlines +// From now on this class saves the redline positions of all redlines which ends exact at the +// insert position (node _and_ content index) +SaveRedlEndPosForRestore::SaveRedlEndPosForRestore( const SwNodeIndex& rInsIdx, sal_Int32 nCnt ) + : mnSaveContent( nCnt ) +{ + SwNode& rNd = rInsIdx.GetNode(); + SwDoc* pDest = rNd.GetDoc(); + if( !pDest->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwRedlineTable::size_type nFndPos; + const SwPosition* pEnd; + SwPosition aSrcPos( rInsIdx, SwIndex( rNd.GetContentNode(), nCnt )); + pDest->getIDocumentRedlineAccess().GetRedline( aSrcPos, &nFndPos ); + const SwRangeRedline* pRedl; + while( nFndPos-- + && *( pEnd = ( pRedl = pDest->getIDocumentRedlineAccess().GetRedlineTable()[ nFndPos ] )->End() ) == aSrcPos + && *pRedl->Start() < aSrcPos ) + { + if( !mpSaveIndex ) + { + mpSaveIndex.reset(new SwNodeIndex( rInsIdx, -1 )); + } + mvSavArr.push_back( const_cast<SwPosition*>(pEnd) ); + } + } +} + +SaveRedlEndPosForRestore::~SaveRedlEndPosForRestore() +{ + mpSaveIndex.reset(); +} + +void SaveRedlEndPosForRestore::Restore() +{ + if (mvSavArr.empty()) + return; + ++(*mpSaveIndex); + SwContentNode* pNode = mpSaveIndex->GetNode().GetContentNode(); + // If there's no content node at the remembered position, we will not restore the old position + // This may happen if a table (or section?) will be inserted. + if( pNode ) + { + SwPosition aPos( *mpSaveIndex, SwIndex( pNode, mnSaveContent )); + for( auto n = mvSavArr.size(); n; ) + *mvSavArr[ --n ] = aPos; + } +} + +/// Convert list of ranges of whichIds to a corresponding list of whichIds +static std::vector<sal_uInt16> lcl_RangesToVector(const sal_uInt16 * pRanges) +{ + std::vector<sal_uInt16> aResult; + + int i = 0; + while (pRanges[i] != 0) + { + OSL_ENSURE(pRanges[i+1] != 0, "malformed ranges"); + + for (sal_uInt16 j = pRanges[i]; j <= pRanges[i+1]; j++) + aResult.push_back(j); + + i += 2; + } + + return aResult; +} + +void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev ) +{ + rJoinText = false; + rJoinPrev = false; + if( rPam.GetPoint()->nNode != rPam.GetMark()->nNode ) + { + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + SwTextNode *pSttNd = pStt->nNode.GetNode().GetTextNode(); + if( pSttNd ) + { + SwTextNode *pEndNd = pEnd->nNode.GetNode().GetTextNode(); + rJoinText = nullptr != pEndNd; + if( rJoinText ) + { + bool bExchange = pStt == rPam.GetPoint(); + if( !pStt->nContent.GetIndex() && + pEndNd->GetText().getLength() != pEnd->nContent.GetIndex()) + bExchange = !bExchange; + if( bExchange ) + rPam.Exchange(); + rJoinPrev = rPam.GetPoint() == pStt; + OSL_ENSURE( !pStt->nContent.GetIndex() && + pEndNd->GetText().getLength() != pEnd->nContent.GetIndex() + ? (rPam.GetPoint()->nNode < rPam.GetMark()->nNode) + : (rPam.GetPoint()->nNode > rPam.GetMark()->nNode), + "sw_GetJoinFlags"); + } + } + } +} + +bool sw_JoinText( SwPaM& rPam, bool bJoinPrev ) +{ + SwNodeIndex aIdx( rPam.GetPoint()->nNode ); + SwTextNode *pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aOldIdx( aIdx ); + SwTextNode *pOldTextNd = pTextNd; + + if( pTextNd && pTextNd->CanJoinNext( &aIdx ) ) + { + SwDoc* pDoc = rPam.GetDoc(); + if( bJoinPrev ) + { + // We do not need to handle xmlids in this case, because + // it is only invoked if one paragraph is/becomes completely empty + // (see sw_GetJoinFlags) + { + // If PageBreaks are deleted/set, it must not be added to the Undo history! + // Also, deleting the Node is not added to the Undo history! + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + /* PageBreaks, PageDesc, ColumnBreaks */ + // If we need to change something about the logic to copy the PageBreaks, + // PageDesc, etc. we also have to change SwUndoDelete. + // There, we copy the AUTO PageBreak from the GetMarkNode! + + /* The MarkNode */ + pTextNd = aIdx.GetNode().GetTextNode(); + if (pTextNd->HasSwAttrSet()) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( + RES_BREAK, false, &pItem ) ) + pTextNd->ResetAttr( RES_BREAK ); + if( pTextNd->HasSwAttrSet() && + SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( + RES_PAGEDESC, false, &pItem ) ) + pTextNd->ResetAttr( RES_PAGEDESC ); + } + + /* The PointNode */ + if( pOldTextNd->HasSwAttrSet() ) + { + const SfxPoolItem* pItem; + SfxItemSet aSet( pDoc->GetAttrPool(), aBreakSetRange ); + const SfxItemSet* pSet = pOldTextNd->GetpSwAttrSet(); + if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, + false, &pItem ) ) + aSet.Put( *pItem ); + if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + aSet.Put( *pItem ); + if( aSet.Count() ) + pTextNd->SetAttr( aSet ); + } + pOldTextNd->FormatToTextAttr( pTextNd ); + + const std::shared_ptr< sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(pDoc, aOldIdx.GetIndex(), SAL_MAX_INT32); + + SwIndex aAlphaIdx(pTextNd); + pOldTextNd->CutText( pTextNd, aAlphaIdx, SwIndex(pOldTextNd), + pOldTextNd->Len() ); + SwPosition aAlphaPos( aIdx, aAlphaIdx ); + pDoc->CorrRel( rPam.GetPoint()->nNode, aAlphaPos, 0, true ); + + // move all Bookmarks/TOXMarks + if( !pContentStore->Empty() ) + pContentStore->Restore( pDoc, aIdx.GetIndex() ); + + // If the passed PaM is not in the Cursor ring, + // treat it separately (e.g. when it's being called from AutoFormat) + if( pOldTextNd == rPam.GetBound().nContent.GetIdxReg() ) + rPam.GetBound() = aAlphaPos; + if( pOldTextNd == rPam.GetBound( false ).nContent.GetIdxReg() ) + rPam.GetBound( false ) = aAlphaPos; + } + // delete the Node, at last! + SwNode::Merge const eOldMergeFlag(pOldTextNd->GetRedlineMergeFlag()); + if (eOldMergeFlag == SwNode::Merge::First + && !pTextNd->IsCreateFrameWhenHidingRedlines()) + { + sw::MoveDeletedPrevFrames(*pOldTextNd, *pTextNd); + } + pDoc->GetNodes().Delete( aOldIdx ); + sw::CheckResetRedlineMergeFlag(*pTextNd, + eOldMergeFlag == SwNode::Merge::NonFirst + ? sw::Recreate::Predecessor + : sw::Recreate::No); + } + else + { + SwTextNode* pDelNd = aIdx.GetNode().GetTextNode(); + if( pTextNd->Len() ) + pDelNd->FormatToTextAttr( pTextNd ); + else + { + /* This case was missed: + + <something></something> <-- pTextNd + <other>ccc</other> <-- pDelNd + + <something> and <other> are paragraph + attributes. The attribute <something> stayed if not + overwritten by an attribute in "ccc". Fixed by + first resetting all character attributes in first + paragraph (pTextNd). + */ + std::vector<sal_uInt16> aShorts = + lcl_RangesToVector(aCharFormatSetRange); + pTextNd->ResetAttr(aShorts); + + if( pDelNd->HasSwAttrSet() ) + { + // only copy the character attributes + SfxItemSet aTmpSet( pDoc->GetAttrPool(), aCharFormatSetRange ); + aTmpSet.Put( *pDelNd->GetpSwAttrSet() ); + pTextNd->SetAttr( aTmpSet ); + } + } + + pDoc->CorrRel( aIdx, *rPam.GetPoint(), 0, true ); + // #i100466# adjust given <rPam>, if it does not belong to the cursors + if ( pDelNd == rPam.GetBound().nContent.GetIdxReg() ) + { + rPam.GetBound() = SwPosition( SwNodeIndex( *pTextNd ), SwIndex( pTextNd ) ); + } + if( pDelNd == rPam.GetBound( false ).nContent.GetIdxReg() ) + { + rPam.GetBound( false ) = SwPosition( SwNodeIndex( *pTextNd ), SwIndex( pTextNd ) ); + } + pTextNd->JoinNext(); + } + return true; + } + else return false; +} + +static void lcl_syncGrammarError( SwTextNode &rTextNode, linguistic2::ProofreadingResult& rResult, + const ModelToViewHelper &rConversionMap ) +{ + if( rTextNode.IsGrammarCheckDirty() ) + return; + SwGrammarMarkUp* pWrong = rTextNode.GetGrammarCheck(); + linguistic2::SingleProofreadingError* pArray = rResult.aErrors.getArray(); + sal_uInt16 j = 0; + if( pWrong ) + { + for( sal_Int32 i = 0; i < rResult.aErrors.getLength(); ++i ) + { + const linguistic2::SingleProofreadingError &rError = rResult.aErrors[i]; + const sal_Int32 nStart = rConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos; + const sal_Int32 nEnd = rConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos; + if( i != j ) + pArray[j] = pArray[i]; + if( pWrong->LookForEntry( nStart, nEnd ) ) + ++j; + } + } + if( rResult.aErrors.getLength() > j ) + rResult.aErrors.realloc( j ); +} + +uno::Any SwDoc::Spell( SwPaM& rPaM, + uno::Reference< XSpellChecker1 > const &xSpeller, + sal_uInt16* pPageCnt, sal_uInt16* pPageSt, + bool bGrammarCheck, + SwRootFrame const*const pLayout, + SwConversionArgs *pConvArgs ) const +{ + SwPosition* pSttPos = rPaM.Start(), *pEndPos = rPaM.End(); + + std::unique_ptr<SwSpellArgs> pSpellArgs; + if (pConvArgs) + { + pConvArgs->SetStart(pSttPos->nNode.GetNode().GetTextNode(), pSttPos->nContent); + pConvArgs->SetEnd( pEndPos->nNode.GetNode().GetTextNode(), pEndPos->nContent ); + } + else + pSpellArgs.reset(new SwSpellArgs( xSpeller, + pSttPos->nNode.GetNode().GetTextNode(), pSttPos->nContent, + pEndPos->nNode.GetNode().GetTextNode(), pEndPos->nContent, + bGrammarCheck )); + + sal_uLong nCurrNd = pSttPos->nNode.GetIndex(); + sal_uLong nEndNd = pEndPos->nNode.GetIndex(); + + uno::Any aRet; + if( nCurrNd <= nEndNd ) + { + SwContentFrame* pContentFrame; + bool bGoOn = true; + while( bGoOn ) + { + SwNode* pNd = GetNodes()[ nCurrNd ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + if( nullptr != ( pContentFrame = pNd->GetTextNode()->getLayoutFrame( getIDocumentLayoutAccess().GetCurrentLayout() )) ) + { + // skip protected and hidden Cells and Flys + if( pContentFrame->IsProtected() ) + { + nCurrNd = pNd->EndOfSectionIndex(); + } + else if( !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() ) + { + if( pPageCnt && *pPageCnt && pPageSt ) + { + sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum(); + if( !*pPageSt ) + { + *pPageSt = nPageNr; + if( *pPageCnt < *pPageSt ) + *pPageCnt = *pPageSt; + } + long nStat; + if( nPageNr >= *pPageSt ) + nStat = nPageNr - *pPageSt + 1; + else + nStat = nPageNr + *pPageCnt - *pPageSt + 1; + ::SetProgressState( nStat, GetDocShell() ); + } + //Spell() changes the pSpellArgs in case an error is found + sal_Int32 nBeginGrammarCheck = 0; + sal_Int32 nEndGrammarCheck = 0; + if( pSpellArgs && pSpellArgs->bIsGrammarCheck) + { + nBeginGrammarCheck = pSpellArgs->pStartNode == pNd ? pSpellArgs->pStartIdx->GetIndex() : 0; + // if grammar checking starts inside of a sentence the start position has to be adjusted + if( nBeginGrammarCheck ) + { + SwIndex aStartIndex( dynamic_cast< SwTextNode* >( pNd ), nBeginGrammarCheck ); + SwPosition aStart( *pNd, aStartIndex ); + SwCursor aCursor(aStart, nullptr); + SwPosition aOrigPos = *aCursor.GetPoint(); + aCursor.GoSentence( SwCursor::START_SENT ); + if( aOrigPos != *aCursor.GetPoint() ) + { + nBeginGrammarCheck = aCursor.GetPoint()->nContent.GetIndex(); + } + } + nEndGrammarCheck = (pSpellArgs->pEndNode == pNd) + ? pSpellArgs->pEndIdx->GetIndex() + : pNd->GetTextNode() + ->GetText().getLength(); + } + + sal_Int32 nSpellErrorPosition = pNd->GetTextNode()->GetText().getLength(); + if( (!pConvArgs && pNd->GetTextNode()->Spell( pSpellArgs.get() )) || + ( pConvArgs && pNd->GetTextNode()->Convert( *pConvArgs ))) + { + // Cancel and remember position + pSttPos->nNode = nCurrNd; + pEndPos->nNode = nCurrNd; + nCurrNd = nEndNd; + if( pSpellArgs ) + nSpellErrorPosition = pSpellArgs->pStartIdx->GetIndex() > pSpellArgs->pEndIdx->GetIndex() ? + pSpellArgs->pEndIdx->GetIndex() : + pSpellArgs->pStartIdx->GetIndex(); + } + + if( pSpellArgs && pSpellArgs->bIsGrammarCheck ) + { + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() ); + if (xGCIterator.is()) + { + uno::Reference< lang::XComponent > xDoc = GetDocShell()->GetBaseModel(); + // Expand the string: + const ModelToViewHelper aConversionMap(*pNd->GetTextNode(), pLayout); + const OUString& aExpandText = aConversionMap.getViewText(); + + // get XFlatParagraph to use... + uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNd->GetTextNode(), aExpandText, aConversionMap ); + + // get error position of cursor in XFlatParagraph + linguistic2::ProofreadingResult aResult; + bool bGrammarErrors; + do + { + aConversionMap.ConvertToViewPosition( nBeginGrammarCheck ); + aResult = xGCIterator->checkSentenceAtPosition( + xDoc, xFlatPara, aExpandText, lang::Locale(), nBeginGrammarCheck, -1, -1 ); + + lcl_syncGrammarError( *pNd->GetTextNode(), aResult, aConversionMap ); + + // get suggestions to use for the specific error position + bGrammarErrors = aResult.aErrors.hasElements(); + // if grammar checking doesn't have any progress then quit + if( aResult.nStartOfNextSentencePosition <= nBeginGrammarCheck ) + break; + // prepare next iteration + nBeginGrammarCheck = aResult.nStartOfNextSentencePosition; + } + while( nSpellErrorPosition > aResult.nBehindEndOfSentencePosition && !bGrammarErrors && aResult.nBehindEndOfSentencePosition < nEndGrammarCheck ); + + if( bGrammarErrors && nSpellErrorPosition >= aResult.nBehindEndOfSentencePosition ) + { + aRet <<= aResult; + //put the cursor to the current error + const linguistic2::SingleProofreadingError &rError = aResult.aErrors[0]; + nCurrNd = pNd->GetIndex(); + pSttPos->nNode = nCurrNd; + pEndPos->nNode = nCurrNd; + pSpellArgs->pStartNode = pNd->GetTextNode(); + pSpellArgs->pEndNode = pNd->GetTextNode(); + pSpellArgs->pStartIdx->Assign(pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos ); + pSpellArgs->pEndIdx->Assign(pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart + rError.nErrorLength ).mnPos ); + nCurrNd = nEndNd; + } + } + } + } + } + break; + case SwNodeType::Section: + if( static_cast<SwSectionNode*>(pNd)->GetSection().IsProtect() || + static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ) + nCurrNd = pNd->EndOfSectionIndex(); + break; + case SwNodeType::End: + { + break; + } + default: break; + } + + bGoOn = nCurrNd < nEndNd; + ++nCurrNd; + } + } + + if( !aRet.hasValue() ) + { + if (pConvArgs) + aRet <<= pConvArgs->aConvText; + else + aRet <<= pSpellArgs->xSpellAlt; + } + + return aRet; +} + +namespace { + +class SwHyphArgs : public SwInterHyphInfo +{ + const SwNode *m_pStart; + const SwNode *m_pEnd; + SwNode *m_pNode; + sal_uInt16 *m_pPageCnt; + sal_uInt16 *m_pPageSt; + + sal_uInt32 m_nNode; + sal_Int32 m_nPamStart; + sal_Int32 m_nPamLen; + +public: + SwHyphArgs( const SwPaM *pPam, const Point &rPoint, + sal_uInt16* pPageCount, sal_uInt16* pPageStart ); + void SetPam( SwPaM *pPam ) const; + void SetNode( SwNode *pNew ) { m_pNode = pNew; } + inline void SetRange( const SwNode *pNew ); + void NextNode() { ++m_nNode; } + sal_uInt16 *GetPageCnt() { return m_pPageCnt; } + sal_uInt16 *GetPageSt() { return m_pPageSt; } +}; + +} + +SwHyphArgs::SwHyphArgs( const SwPaM *pPam, const Point &rCursorPos, + sal_uInt16* pPageCount, sal_uInt16* pPageStart ) + : SwInterHyphInfo( rCursorPos ), m_pNode(nullptr), + m_pPageCnt( pPageCount ), m_pPageSt( pPageStart ) +{ + // The following constraints have to be met: + // 1) there is at least one Selection + // 2) SPoint() == Start() + OSL_ENSURE( pPam->HasMark(), "SwDoc::Hyphenate: blowing in the wind"); + OSL_ENSURE( *pPam->GetPoint() <= *pPam->GetMark(), + "SwDoc::Hyphenate: New York, New York"); + + const SwPosition *pPoint = pPam->GetPoint(); + m_nNode = pPoint->nNode.GetIndex(); + + // Set start + m_pStart = pPoint->nNode.GetNode().GetTextNode(); + m_nPamStart = pPoint->nContent.GetIndex(); + + // Set End and Length + const SwPosition *pMark = pPam->GetMark(); + m_pEnd = pMark->nNode.GetNode().GetTextNode(); + m_nPamLen = pMark->nContent.GetIndex(); + if( pPoint->nNode == pMark->nNode ) + m_nPamLen = m_nPamLen - pPoint->nContent.GetIndex(); +} + +inline void SwHyphArgs::SetRange( const SwNode *pNew ) +{ + m_nStart = m_pStart == pNew ? m_nPamStart : 0; + m_nEnd = m_pEnd == pNew ? m_nPamStart + m_nPamLen : SAL_MAX_INT32; +} + +void SwHyphArgs::SetPam( SwPaM *pPam ) const +{ + if( !m_pNode ) + *pPam->GetPoint() = *pPam->GetMark(); + else + { + pPam->GetPoint()->nNode = m_nNode; + pPam->GetPoint()->nContent.Assign( m_pNode->GetContentNode(), m_nWordStart ); + pPam->GetMark()->nNode = m_nNode; + pPam->GetMark()->nContent.Assign( m_pNode->GetContentNode(), + m_nWordStart + m_nWordLen ); + OSL_ENSURE( m_nNode == m_pNode->GetIndex(), + "SwHyphArgs::SetPam: Pam disaster" ); + } +} + +// Returns true if we can proceed. +static bool lcl_HyphenateNode( const SwNodePtr& rpNd, void* pArgs ) +{ + // Hyphenate returns true if there is a hyphenation point and sets pPam + SwTextNode *pNode = rpNd->GetTextNode(); + SwHyphArgs *pHyphArgs = static_cast<SwHyphArgs*>(pArgs); + if( pNode ) + { + // sw_redlinehide: this will be called once per node for merged nodes; + // the fully deleted ones won't have frames so are skipped. + SwContentFrame* pContentFrame = pNode->getLayoutFrame( pNode->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pContentFrame && !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() ) + { + sal_uInt16 *pPageSt = pHyphArgs->GetPageSt(); + sal_uInt16 *pPageCnt = pHyphArgs->GetPageCnt(); + if( pPageCnt && *pPageCnt && pPageSt ) + { + sal_uInt16 nPageNr = pContentFrame->GetPhyPageNum(); + if( !*pPageSt ) + { + *pPageSt = nPageNr; + if( *pPageCnt < *pPageSt ) + *pPageCnt = *pPageSt; + } + long nStat = nPageNr >= *pPageSt ? nPageNr - *pPageSt + 1 + : nPageNr + *pPageCnt - *pPageSt + 1; + ::SetProgressState( nStat, pNode->GetDoc()->GetDocShell() ); + } + pHyphArgs->SetRange( rpNd ); + if( pNode->Hyphenate( *pHyphArgs ) ) + { + pHyphArgs->SetNode( rpNd ); + return false; + } + } + } + pHyphArgs->NextNode(); + return true; +} + +uno::Reference< XHyphenatedWord > SwDoc::Hyphenate( + SwPaM *pPam, const Point &rCursorPos, + sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + OSL_ENSURE(this == pPam->GetDoc(), "SwDoc::Hyphenate: strangers in the night"); + + if( *pPam->GetPoint() > *pPam->GetMark() ) + pPam->Exchange(); + + SwHyphArgs aHyphArg( pPam, rCursorPos, pPageCnt, pPageSt ); + SwNodeIndex aTmpIdx( pPam->GetMark()->nNode, 1 ); + GetNodes().ForEach( pPam->GetPoint()->nNode, aTmpIdx, + lcl_HyphenateNode, &aHyphArg ); + aHyphArg.SetPam( pPam ); + return aHyphArg.GetHyphWord(); // will be set by lcl_HyphenateNode +} + +// Save the current values to add them as automatic entries to AutoCorrect. +void SwDoc::SetAutoCorrExceptWord( std::unique_ptr<SwAutoCorrExceptWord> pNew ) +{ + mpACEWord = std::move(pNew); +} + +void SwDoc::DeleteAutoCorrExceptWord() +{ + mpACEWord.reset(); +} + +void SwDoc::CountWords( const SwPaM& rPaM, SwDocStat& rStat ) +{ + // This is a modified version of SwDoc::TransliterateText + const SwPosition* pStt = rPaM.Start(); + const SwPosition* pEnd = pStt == rPaM.GetPoint() ? rPaM.GetMark() + : rPaM.GetPoint(); + + const sal_uLong nSttNd = pStt->nNode.GetIndex(); + const sal_uLong nEndNd = pEnd->nNode.GetIndex(); + + const sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + const sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + const SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no region ? + { + // do nothing + return; + } + + if( nSttNd != nEndNd ) + { + SwNodeIndex aIdx( pStt->nNode ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + pTNd->CountWords( rStat, nSttCnt, pTNd->GetText().getLength() ); + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + if( nullptr != ( pTNd = aIdx.GetNode().GetTextNode() )) + pTNd->CountWords( rStat, 0, pTNd->GetText().getLength() ); + + if( nEndCnt && nullptr != ( pTNd = pEnd->nNode.GetNode().GetTextNode() )) + pTNd->CountWords( rStat, 0, nEndCnt ); + } + else if( pTNd && nSttCnt < nEndCnt ) + pTNd->CountWords( rStat, nSttCnt, nEndCnt ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfld.cxx b/sw/source/core/doc/docfld.cxx new file mode 100644 index 000000000..7ea93bb4a --- /dev/null +++ b/sw/source/core/doc/docfld.cxx @@ -0,0 +1,1168 @@ +/* -*- 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_features.h> + +#include <hintids.hxx> + +#include <comphelper/string.hxx> +#include <unotools/charclass.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <calc.hxx> +#include <txtfld.hxx> +#include <fmtfld.hxx> +#include <txttxmrk.hxx> +#include <docfld.hxx> +#include <docufld.hxx> +#include <usrfld.hxx> +#include <expfld.hxx> +#include <dbfld.hxx> +#include <reffld.hxx> +#include <dbmgr.hxx> +#include <section.hxx> +#include <docary.hxx> +#include <authfld.hxx> +#include <txtinet.hxx> +#include <fmtcntnt.hxx> + +#include <calbck.hxx> + +using namespace ::com::sun::star::uno; + +// the StartIndex can be supplied optionally (e.g. if it was queried before - is a virtual +// method otherwise!) +SetGetExpField::SetGetExpField( + const SwNodeIndex& rNdIdx, + const SwTextField* pField, + const SwIndex* pIdx ) +{ + m_eSetGetExpFieldType = TEXTFIELD; + m_CNTNT.pTextField = pField; + m_nNode = rNdIdx.GetIndex(); + if( pIdx ) + m_nContent = pIdx->GetIndex(); + else if( pField ) + m_nContent = pField->GetStart(); + else + m_nContent = 0; +} + +SetGetExpField::SetGetExpField( const SwNodeIndex& rNdIdx, + const SwTextINetFormat& rINet ) +{ + m_eSetGetExpFieldType = TEXTINET; + m_CNTNT.pTextINet = &rINet; + m_nNode = rNdIdx.GetIndex(); + m_nContent = rINet.GetStart(); +} + +// Extension for Sections: +// these always have content position 0xffffffff! +// There is never a field on this, only up to COMPLETE_STRING possible +SetGetExpField::SetGetExpField( const SwSectionNode& rSectNd, + const SwPosition* pPos ) +{ + m_eSetGetExpFieldType = SECTIONNODE; + m_CNTNT.pSection = &rSectNd.GetSection(); + + if( pPos ) + { + m_nNode = pPos->nNode.GetIndex(); + m_nContent = pPos->nContent.GetIndex(); + } + else + { + m_nNode = rSectNd.GetIndex(); + m_nContent = 0; + } +} + +SetGetExpField::SetGetExpField( const SwTableBox& rTBox ) +{ + m_eSetGetExpFieldType = TABLEBOX; + m_CNTNT.pTBox = &rTBox; + + m_nNode = 0; + m_nContent = 0; + if( rTBox.GetSttNd() ) + { + SwNodeIndex aIdx( *rTBox.GetSttNd() ); + const SwContentNode* pNd = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + if( pNd ) + m_nNode = pNd->GetIndex(); + } +} + +SetGetExpField::SetGetExpField( const SwNodeIndex& rNdIdx, + const SwTextTOXMark& rTOX ) +{ + m_eSetGetExpFieldType = TEXTTOXMARK; + m_CNTNT.pTextTOX = &rTOX; + m_nNode = rNdIdx.GetIndex(); + m_nContent = rTOX.GetStart(); +} + +SetGetExpField::SetGetExpField( const SwPosition& rPos ) +{ + m_eSetGetExpFieldType = CRSRPOS; + m_CNTNT.pPos = &rPos; + m_nNode = rPos.nNode.GetIndex(); + m_nContent = rPos.nContent.GetIndex(); +} + +SetGetExpField::SetGetExpField( const SwFlyFrameFormat& rFlyFormat, + const SwPosition* pPos ) +{ + m_eSetGetExpFieldType = FLYFRAME; + m_CNTNT.pFlyFormat = &rFlyFormat; + if( pPos ) + { + m_nNode = pPos->nNode.GetIndex(); + m_nContent = pPos->nContent.GetIndex(); + } + else + { + const SwFormatContent& rContent = rFlyFormat.GetContent(); + m_nNode = rContent.GetContentIdx()->GetIndex() + 1; + m_nContent = 0; + } +} + +void SetGetExpField::GetPosOfContent( SwPosition& rPos ) const +{ + const SwNode* pNd = GetNodeFromContent(); + if( pNd ) + pNd = pNd->GetContentNode(); + + if( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)), GetCntPosFromContent() ); + } + else + { + rPos.nNode = m_nNode; + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), m_nContent ); + } +} + +void SetGetExpField::SetBodyPos( const SwContentFrame& rFrame ) +{ + if( !rFrame.IsInDocBody() ) + { + SwNodeIndex aIdx( rFrame.IsTextFrame() + ? *static_cast<SwTextFrame const&>(rFrame).GetTextNodeFirst() + : *static_cast<SwNoTextFrame const&>(rFrame).GetNode() ); + SwDoc& rDoc = *aIdx.GetNodes().GetDoc(); + SwPosition aPos( aIdx ); + bool const bResult = ::GetBodyTextNode( rDoc, aPos, rFrame ); + OSL_ENSURE(bResult, "Where is the field?"); + m_nNode = aPos.nNode.GetIndex(); + m_nContent = aPos.nContent.GetIndex(); + } +} + +bool SetGetExpField::operator==( const SetGetExpField& rField ) const +{ + return m_nNode == rField.m_nNode + && m_nContent == rField.m_nContent + && ( !m_CNTNT.pTextField + || !rField.m_CNTNT.pTextField + || m_CNTNT.pTextField == rField.m_CNTNT.pTextField ); +} + +bool SetGetExpField::operator<( const SetGetExpField& rField ) const +{ + if( m_nNode < rField.m_nNode || ( m_nNode == rField.m_nNode && m_nContent < rField.m_nContent )) + return true; + else if( m_nNode != rField.m_nNode || m_nContent != rField.m_nContent ) + return false; + + const SwNode *pFirst = GetNodeFromContent(), + *pNext = rField.GetNodeFromContent(); + + // Position is the same: continue only if both field pointers are set! + if( !pFirst || !pNext ) + return false; + + // same Section? + if( pFirst->StartOfSectionNode() != pNext->StartOfSectionNode() ) + { + // is one in the table? + const SwNode *pFirstStt, *pNextStt; + const SwTableNode* pTableNd = pFirst->FindTableNode(); + if( pTableNd ) + pFirstStt = pTableNd->StartOfSectionNode(); + else + pFirstStt = pFirst->StartOfSectionNode(); + + if( nullptr != ( pTableNd = pNext->FindTableNode() ) ) + pNextStt = pTableNd->StartOfSectionNode(); + else + pNextStt = pNext->StartOfSectionNode(); + + if( pFirstStt != pNextStt ) + { + if( pFirst->IsTextNode() && pNext->IsTextNode() && + ( pFirst->FindFlyStartNode() || pNext->FindFlyStartNode() )) + { + // FIXME: in NewFieldPortion(), SwGetExpField are expanded via + // DocumentFieldsManager::FieldsToExpand() calling + // std::upper_bound binary search function - the sort order + // depends on the fly positions in the layout, but the fly + // positions depend on the expansion of the SwGetExpField! + // This circular dep will cause trouble, it would be better to + // use only model positions (anchor), but then how to compare + // at-page anchored flys which don't have a model anchor? + return ::IsFrameBehind( *pNext->GetTextNode(), m_nContent, *pFirst->GetTextNode(), m_nContent ); + } + return pFirstStt->GetIndex() < pNextStt->GetIndex(); + } + } + + // same Section: is the field in the same Node? + if( pFirst != pNext ) + return pFirst->GetIndex() < pNext->GetIndex(); + + // same Node in the Section, check Position in the Node + return GetCntPosFromContent() < rField.GetCntPosFromContent(); +} + +const SwNode* SetGetExpField::GetNodeFromContent() const +{ + const SwNode* pRet = nullptr; + if( m_CNTNT.pTextField ) + switch( m_eSetGetExpFieldType ) + { + case TEXTFIELD: + pRet = &m_CNTNT.pTextField->GetTextNode(); + break; + + case TEXTINET: + pRet = &m_CNTNT.pTextINet->GetTextNode(); + break; + + case SECTIONNODE: + pRet = m_CNTNT.pSection->GetFormat()->GetSectionNode(); + break; + + case CRSRPOS: + pRet = &m_CNTNT.pPos->nNode.GetNode(); + break; + + case TEXTTOXMARK: + pRet = &m_CNTNT.pTextTOX->GetTextNode(); + break; + + case TABLEBOX: + if( m_CNTNT.pTBox->GetSttNd() ) + { + SwNodeIndex aIdx( *m_CNTNT.pTBox->GetSttNd() ); + pRet = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + } + break; + + case FLYFRAME: + { + SwNodeIndex aIdx( *m_CNTNT.pFlyFormat->GetContent().GetContentIdx() ); + pRet = aIdx.GetNode().GetNodes().GoNext( &aIdx ); + } + break; + } + return pRet; +} + +sal_Int32 SetGetExpField::GetCntPosFromContent() const +{ + sal_Int32 nRet = 0; + if( m_CNTNT.pTextField ) + switch( m_eSetGetExpFieldType ) + { + case TEXTFIELD: + nRet = m_CNTNT.pTextField->GetStart(); + break; + case TEXTINET: + nRet = m_CNTNT.pTextINet->GetStart(); + break; + case TEXTTOXMARK: + nRet = m_CNTNT.pTextTOX->GetStart(); + break; + case CRSRPOS: + nRet = m_CNTNT.pPos->nContent.GetIndex(); + break; + default: + break; + } + return nRet; +} + +HashStr::HashStr( const OUString& rName, const OUString& rText, + HashStr* pNxt ) + : SwHash( rName ), aSetStr( rText ) +{ + pNext.reset( pNxt ); +} + +/// Look up the Name, if it is present, return its String, otherwise return an empty String +OUString LookString( SwHashTable<HashStr> const & rTable, const OUString& rName ) +{ + HashStr* pFnd = rTable.Find( comphelper::string::strip(rName, ' ') ); + if( pFnd ) + return pFnd->aSetStr; + + return OUString(); +} + +SwDBData const & SwDoc::GetDBData() +{ + return GetDBDesc(); +} + +const SwDBData& SwDoc::GetDBDesc() +{ +#if HAVE_FEATURE_DBCONNECTIVITY + if(maDBData.sDataSource.isEmpty()) + { + const SwFieldTypes::size_type nSize = getIDocumentFieldsAccess().GetFieldTypes()->size(); + for(SwFieldTypes::size_type i = 0; i < nSize && maDBData.sDataSource.isEmpty(); ++i) + { + SwFieldType& rFieldType = *((*getIDocumentFieldsAccess().GetFieldTypes())[i]); + SwFieldIds nWhich = rFieldType.Which(); + if(IsUsed(rFieldType)) + { + switch(nWhich) + { + case SwFieldIds::Database: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + { + std::vector<SwFormatField*> vFields; + rFieldType.GatherFields(vFields); + if(vFields.size()) + { + if(SwFieldIds::Database == nWhich) + maDBData = static_cast<SwDBFieldType*>(vFields.front()->GetField()->GetTyp())->GetDBData(); + else + maDBData = static_cast<SwDBNameInfField*> (vFields.front()->GetField())->GetRealDBData(); + } + } + break; + default: break; + } + } + } + } + if(maDBData.sDataSource.isEmpty()) + maDBData = SwDBManager::GetAddressDBName(); +#endif + return maDBData; +} + +void SwDoc::SetInitDBFields( bool b ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) b; +#else + GetDBManager()->SetInitDBFields( b ); +#endif +} + +#if HAVE_FEATURE_DBCONNECTIVITY + +/// Get all databases that are used by fields +static OUString lcl_DBDataToString(const SwDBData& rData) +{ + return rData.sDataSource + OUStringChar(DB_DELIM) + + rData.sCommand + OUStringChar(DB_DELIM) + + OUString::number(rData.nCommandType); +} + +#endif + +void SwDoc::GetAllUsedDB( std::vector<OUString>& rDBNameList, + const std::vector<OUString>* pAllDBNames ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBNameList; + (void) pAllDBNames; +#else + std::vector<OUString> aUsedDBNames; + std::vector<OUString> aAllDBNames; + + if( !pAllDBNames ) + { + GetAllDBNames( aAllDBNames ); + pAllDBNames = &aAllDBNames; + } + + SwSectionFormats& rArr = GetSections(); + for (auto n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + + if( pSect ) + { + AddUsedDBToList( rDBNameList, FindUsedDBs( *pAllDBNames, + pSect->GetCondition(), aUsedDBNames ) ); + aUsedDBNames.clear(); + } + } + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(nWhichHint)) + { + const SwFormatField* pFormatField = static_cast<const SwFormatField*>(pItem); + const SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + const SwField* pField = pFormatField->GetField(); + switch (pField->GetTyp()->Which()) + { + case SwFieldIds::Database: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast<const SwDBField*>(pField)->GetDBData() )); + break; + + case SwFieldIds::DbSetNumber: + case SwFieldIds::DatabaseName: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast<const SwDBNameInfField*>(pField)->GetRealDBData() )); + break; + + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + AddUsedDBToList( rDBNameList, + lcl_DBDataToString(static_cast<const SwDBNameInfField*>(pField)->GetRealDBData() )); + [[fallthrough]]; // JP: is that right like that? + + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + AddUsedDBToList(rDBNameList, FindUsedDBs( *pAllDBNames, + pField->GetPar1(), aUsedDBNames )); + aUsedDBNames.clear(); + break; + + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::Table: + AddUsedDBToList(rDBNameList, FindUsedDBs( *pAllDBNames, + pField->GetFormula(), aUsedDBNames )); + aUsedDBNames.clear(); + break; + default: break; + } + } + } +#endif +} + +void SwDoc::GetAllDBNames( std::vector<OUString>& rAllDBNames ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rAllDBNames; +#else + SwDBManager* pMgr = GetDBManager(); + + const SwDSParams_t& rArr = pMgr->GetDSParamArray(); + for (const auto& pParam : rArr) + { + rAllDBNames.emplace_back(pParam->sDataSource + OUStringChar(DB_DELIM) + pParam->sCommand); + } +#endif +} + +std::vector<OUString>& SwDoc::FindUsedDBs( const std::vector<OUString>& rAllDBNames, + const OUString& rFormula, + std::vector<OUString>& rUsedDBNames ) +{ + const CharClass& rCC = GetAppCharClass(); +#ifndef UNX + const OUString sFormula(rCC.uppercase( rFormula )); +#else + const OUString sFormula(rFormula); +#endif + + for (const auto &sItem : rAllDBNames) + { + sal_Int32 nPos = sFormula.indexOf( sItem ); + if( nPos>=0 && + sFormula[ nPos + sItem.getLength() ] == '.' && + (!nPos || !rCC.isLetterNumeric( sFormula, nPos - 1 ))) + { + // Look up table name + nPos += sItem.getLength() + 1; + const sal_Int32 nEndPos = sFormula.indexOf('.', nPos); + if( nEndPos>=0 ) + { + rUsedDBNames.emplace_back(sItem + OUStringChar(DB_DELIM) + sFormula.copy( nPos, nEndPos - nPos )); + } + } + } + return rUsedDBNames; +} + +void SwDoc::AddUsedDBToList( std::vector<OUString>& rDBNameList, + const std::vector<OUString>& rUsedDBNames ) +{ + for ( const auto &sName : rUsedDBNames ) + AddUsedDBToList( rDBNameList, sName ); +} + +void SwDoc::AddUsedDBToList( std::vector<OUString>& rDBNameList, const OUString& rDBName) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rDBNameList; + (void) rDBName; +#else + if( rDBName.isEmpty() ) + return; + +#ifdef UNX + for( const auto &sName : rDBNameList ) + if( rDBName == sName.getToken(0, ';') ) + return; +#else + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + for( const auto &sName : rDBNameList ) + if( rSCmp.isEqual( rDBName, sName.getToken(0, ';') ) ) + return; +#endif + + SwDBData aData; + sal_Int32 nIdx{ 0 }; + aData.sDataSource = rDBName.getToken(0, DB_DELIM, nIdx); + aData.sCommand = rDBName.getToken(0, DB_DELIM, nIdx); + aData.nCommandType = -1; + GetDBManager()->CreateDSData(aData); + rDBNameList.push_back(rDBName); +#endif +} + +void SwDoc::ChangeDBFields( const std::vector<OUString>& rOldNames, + const OUString& rNewName ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rOldNames; + (void) rNewName; +#else + SwDBData aNewDBData; + sal_Int32 nIdx{ 0 }; + aNewDBData.sDataSource = rNewName.getToken(0, DB_DELIM, nIdx); + aNewDBData.sCommand = rNewName.getToken(0, DB_DELIM, nIdx); + aNewDBData.nCommandType = static_cast<short>(rNewName.getToken(0, DB_DELIM, nIdx).toInt32()); + + SwSectionFormats& rArr = GetSections(); + for (auto n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + + if( pSect ) + { + pSect->SetCondition(ReplaceUsedDBs(rOldNames, rNewName, pSect->GetCondition())); + } + } + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : GetAttrPool().GetItemSurrogates(nWhichHint)) + { + SwFormatField* pFormatField = const_cast<SwFormatField*>(static_cast<const SwFormatField*>(pItem)); + SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + SwField* pField = pFormatField->GetField(); + bool bExpand = false; + + switch( pField->GetTyp()->Which() ) + { + case SwFieldIds::Database: +#if HAVE_FEATURE_DBCONNECTIVITY + if (IsNameInArray(rOldNames, lcl_DBDataToString(static_cast<SwDBField*>(pField)->GetDBData()))) + { + SwDBFieldType* pOldTyp = static_cast<SwDBFieldType*>(pField->GetTyp()); + + SwDBFieldType* pTyp = static_cast<SwDBFieldType*>(getIDocumentFieldsAccess().InsertFieldType( + SwDBFieldType(this, pOldTyp->GetColumnName(), aNewDBData))); + + pFormatField->RegisterToFieldType( *pTyp ); + pField->ChgTyp(pTyp); + + static_cast<SwDBField*>(pField)->ClearInitialized(); + static_cast<SwDBField*>(pField)->InitContent(); + + bExpand = true; + } +#endif + break; + + case SwFieldIds::DbSetNumber: + case SwFieldIds::DatabaseName: + if (IsNameInArray(rOldNames, + lcl_DBDataToString(static_cast<SwDBNameInfField*>(pField)->GetRealDBData()))) + { + static_cast<SwDBNameInfField*>(pField)->SetDBData(aNewDBData); + bExpand = true; + } + break; + + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + if (IsNameInArray(rOldNames, + lcl_DBDataToString(static_cast<SwDBNameInfField*>(pField)->GetRealDBData()))) + { + static_cast<SwDBNameInfField*>(pField)->SetDBData(aNewDBData); + } + [[fallthrough]]; + case SwFieldIds::HiddenText: + case SwFieldIds::HiddenPara: + pField->SetPar1( ReplaceUsedDBs(rOldNames, rNewName, pField->GetPar1()) ); + bExpand = true; + break; + + case SwFieldIds::SetExp: + case SwFieldIds::GetExp: + case SwFieldIds::Table: + pField->SetPar2( ReplaceUsedDBs(rOldNames, rNewName, pField->GetFormula()) ); + bExpand = true; + break; + default: break; + } + + if (bExpand) + pTextField->ExpandTextField( true ); + } + } + getIDocumentState().SetModified(); +#endif +} + +namespace +{ + +OUString lcl_CutOffDBCommandType(const OUString& rName) +{ + return rName.replaceFirst(OUStringChar(DB_DELIM), ".").getToken(0, DB_DELIM); +} + +} + +OUString SwDoc::ReplaceUsedDBs( const std::vector<OUString>& rUsedDBNames, + const OUString& rNewName, const OUString& rFormula ) +{ + const CharClass& rCC = GetAppCharClass(); + const OUString sNewName( lcl_CutOffDBCommandType(rNewName) ); + OUString sFormula(rFormula); + + for(const auto & rUsedDBName : rUsedDBNames) + { + const OUString sDBName( lcl_CutOffDBCommandType(rUsedDBName) ); + + if (sDBName!=sNewName) + { + sal_Int32 nPos = 0; + for (;;) + { + nPos = sFormula.indexOf(sDBName, nPos); + if (nPos<0) + { + break; + } + + if( sFormula[nPos + sDBName.getLength()] == '.' && + (!nPos || !rCC.isLetterNumeric( sFormula, nPos - 1 ))) + { + sFormula = sFormula.replaceAt(nPos, sDBName.getLength(), sNewName); + //prevent re-searching - this is useless and provokes + //endless loops when names containing each other and numbers are exchanged + //e.g.: old ?12345.12345 new: i12345.12345 + nPos += sNewName.getLength(); + } + } + } + } + return sFormula; +} + +bool SwDoc::IsNameInArray( const std::vector<OUString>& rArr, const OUString& rName ) +{ +#ifdef UNX + for( const auto &sName : rArr ) + if( rName == sName ) + return true; +#else + const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore(); + for( const auto &sName : rArr ) + if( rSCmp.isEqual( rName, sName )) + return true; +#endif + return false; +} + +void SwDoc::ChangeAuthorityData( const SwAuthEntry* pNewData ) +{ + const SwFieldTypes::size_type nSize = getIDocumentFieldsAccess().GetFieldTypes()->size(); + + for( SwFieldTypes::size_type i = INIT_FLDTYPES; i < nSize; ++i ) + { + SwFieldType* pFieldType = (*getIDocumentFieldsAccess().GetFieldTypes())[i].get(); + if( SwFieldIds::TableOfAuthorities == pFieldType->Which() ) + { + SwAuthorityFieldType* pAuthType = static_cast<SwAuthorityFieldType*>(pFieldType); + pAuthType->ChangeEntryContent(pNewData); + break; + } + } + +} + +void SwDocUpdateField::InsDelFieldInFieldLst( bool bIns, const SwTextField& rField ) +{ + const SwFieldIds nWhich = rField.GetFormatField().GetField()->GetTyp()->Which(); + switch( nWhich ) + { + case SwFieldIds::Database: + case SwFieldIds::SetExp: + case SwFieldIds::HiddenPara: + case SwFieldIds::HiddenText: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbSetNumber: + case SwFieldIds::GetExp: + break; // these have to be added/removed! + + default: + return; + } + + SetFieldsDirty( true ); + if (!m_pFieldSortList) + { + if( !bIns ) // if list is present and deleted + return; // don't do a thing + m_pFieldSortList.reset(new SetGetExpFields); + } + + if( bIns ) // insert anew: + GetBodyNode( rField, nWhich ); + else + { + // look up via the pTextField pointer. It is a sorted list, but it's sorted by node + // position. Until this is found, the search for the pointer is already done. + for (SetGetExpFields::size_type n = 0; n < m_pFieldSortList->size(); ++n) + { + if (&rField == (*m_pFieldSortList)[n]->GetPointer()) + { + m_pFieldSortList->erase(n); + n--; // one field can occur multiple times + } + } + } +} + +void SwDocUpdateField::MakeFieldList( SwDoc& rDoc, bool bAll, int eGetMode ) +{ + if (!m_pFieldSortList || bAll + || ((eGetMode & m_nFieldListGetMode) != eGetMode) + || rDoc.GetNodes().Count() != m_nNodes) + { + MakeFieldList_( rDoc, eGetMode ); + } +} + +void SwDocUpdateField::MakeFieldList_( SwDoc& rDoc, int eGetMode ) +{ + // new version: walk all fields of the attribute pool + m_pFieldSortList.reset(new SetGetExpFields); + + // consider and unhide sections + // with hide condition, only in mode GETFLD_ALL (<eGetMode == GETFLD_ALL>) + // notes by OD: + // eGetMode == GETFLD_CALC in call from methods SwDoc::FieldsToCalc + // eGetMode == GETFLD_EXPAND in call from method SwDoc::FieldsToExpand + // eGetMode == GETFLD_ALL in call from method SwDoc::UpdateExpFields + // I figured out that hidden section only have to be shown, + // if fields have updated (call by SwDoc::UpdateExpFields) and thus + // the hide conditions of section have to be updated. + // For correct updating the hide condition of a section, its position + // have to be known in order to insert the hide condition as a new + // expression field into the sorted field list (<m_pFieldSortList>). + if ( eGetMode == GETFLD_ALL ) + // Collect the sections first. Supply sections that are hidden by condition + // with frames so that the contained fields are sorted properly. + { + // In order for the frames to be created the right way, they have to be expanded + // from top to bottom + std::vector<sal_uLong> aTmpArr; + std::vector<sal_uLong>::size_type nArrStt = 0; + SwSectionFormats& rArr = rDoc.GetSections(); + SwSectionNode* pSectNd = nullptr; + sal_uLong nSttContent = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + + for (SwSectionFormats::size_type n = rArr.size(); n; ) + { + SwSection* pSect = rArr[ --n ]->GetSection(); + if( !pSect || !pSect->IsHidden() || pSect->GetCondition().isEmpty() ) + continue; + pSectNd = pSect->GetFormat()->GetSectionNode(); + if( pSectNd ) + { + sal_uLong nIdx = pSectNd->GetIndex(); + aTmpArr.push_back( nIdx ); + if( nIdx < nSttContent ) + ++nArrStt; + } + } + std::sort(aTmpArr.begin(), aTmpArr.end()); + + // Display all first so that we have frames. The BodyAnchor is defined by that. + // First the ContentArea, then the special areas! + for (std::vector<sal_uLong>::size_type n = nArrStt; n < aTmpArr.size(); ++n) + { + pSectNd = rDoc.GetNodes()[ aTmpArr[ n ] ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode" ); + pSectNd->GetSection().SetCondHidden( false ); + } + for (std::vector<sal_uLong>::size_type n = 0; n < nArrStt; ++n) + { + pSectNd = rDoc.GetNodes()[ aTmpArr[ n ] ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode" ); + pSectNd->GetSection().SetCondHidden( false ); + } + + // add all to the list so that they are sorted + for (const auto &nId : aTmpArr) + { + GetBodyNode( *rDoc.GetNodes()[ nId ]->GetSectionNode() ); + } + } + + const OUString sTrue("TRUE"); + const OUString sFalse("FALSE"); + +#if HAVE_FEATURE_DBCONNECTIVITY + bool bIsDBManager = nullptr != rDoc.GetDBManager(); +#endif + + for (sal_uInt16 const nWhichHint : { RES_TXTATR_FIELD, RES_TXTATR_INPUTFIELD }) + { + for (const SfxPoolItem* pItem : rDoc.GetAttrPool().GetItemSurrogates(nWhichHint)) + { + const SwFormatField* pFormatField = static_cast<const SwFormatField*>(pItem); + const SwTextField* pTextField = pFormatField->GetTextField(); + if (!pTextField || !pTextField->GetTextNode().GetNodes().IsDocNodes()) + continue; + + OUString sFormula; + const SwField* pField = pFormatField->GetField(); + const SwFieldIds nWhich = pField->GetTyp()->Which(); + switch (nWhich) + { + case SwFieldIds::DbSetNumber: + case SwFieldIds::GetExp: + if (GETFLD_ALL == eGetMode) + sFormula = sTrue; + break; + + case SwFieldIds::Database: + if (GETFLD_EXPAND & eGetMode) + sFormula = sTrue; + break; + + case SwFieldIds::SetExp: + if ((eGetMode != GETFLD_EXPAND) || + (nsSwGetSetExpType::GSE_STRING & pField->GetSubType())) + { + sFormula = sTrue; + } + break; + + case SwFieldIds::HiddenPara: + if (GETFLD_ALL == eGetMode) + { + sFormula = pField->GetPar1(); + if (sFormula.isEmpty() || sFormula==sFalse) + const_cast<SwHiddenParaField*>(static_cast<const SwHiddenParaField*>(pField))->SetHidden( false ); + else if (sFormula==sTrue) + const_cast<SwHiddenParaField*>(static_cast<const SwHiddenParaField*>(pField))->SetHidden( true ); + else + break; + + sFormula.clear(); + // trigger formatting + const_cast<SwFormatField*>(pFormatField)->ModifyNotification( nullptr, nullptr ); + } + break; + + case SwFieldIds::HiddenText: + if (GETFLD_ALL == eGetMode) + { + sFormula = pField->GetPar1(); + if (sFormula.isEmpty() || sFormula==sFalse) + const_cast<SwHiddenTextField*>(static_cast<const SwHiddenTextField*>(pField))->SetValue( true ); + else if (sFormula==sTrue) + const_cast<SwHiddenTextField*>(static_cast<const SwHiddenTextField*>(pField))->SetValue( false ); + else + break; + + sFormula.clear(); + + // evaluate field + const_cast<SwHiddenTextField*>(static_cast<const SwHiddenTextField*>(pField))->Evaluate(&rDoc); + // trigger formatting + const_cast<SwFormatField*>(pFormatField)->UpdateTextNode(nullptr, nullptr); + } + break; + +#if HAVE_FEATURE_DBCONNECTIVITY + case SwFieldIds::DbNumSet: + { + SwDBData aDBData(const_cast<SwDBNumSetField*>(static_cast<const SwDBNumSetField*>(pField))->GetDBData(&rDoc)); + + if ( (bIsDBManager && rDoc.GetDBManager()->OpenDataSource(aDBData.sDataSource, aDBData.sCommand)) + && (GETFLD_ALL == eGetMode + || (GETFLD_CALC & eGetMode + && static_cast<const SwDBNumSetField*>(pField)->IsCondValid())) + ) + { + sFormula = pField->GetPar1(); + } + } + break; + case SwFieldIds::DbNextSet: + { + SwDBData aDBData(const_cast<SwDBNextSetField*>(static_cast<const SwDBNextSetField*>(pField))->GetDBData(&rDoc)); + + if ( (bIsDBManager && rDoc.GetDBManager()->OpenDataSource(aDBData.sDataSource, aDBData.sCommand)) + && (GETFLD_ALL == eGetMode + || (GETFLD_CALC & eGetMode + && static_cast<const SwDBNextSetField*>(pField)->IsCondValid())) + ) + { + sFormula = pField->GetPar1(); + } + } + break; +#endif + default: break; + } + + if (!sFormula.isEmpty()) + { + GetBodyNode( *pTextField, nWhich ); + } + } + } + m_nFieldListGetMode = eGetMode; + m_nNodes = rDoc.GetNodes().Count(); +} + +void SwDocUpdateField::GetBodyNode( const SwTextField& rTField, SwFieldIds nFieldWhich ) +{ + const SwTextNode& rTextNd = rTField.GetTextNode(); + const SwDoc& rDoc = *rTextNd.GetDoc(); + + // always the first! (in tab headline, header-/footer) + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame* pFrame = rTextNd.getLayoutFrame( + rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); + + std::unique_ptr<SetGetExpField> pNew; + bool bIsInBody = false; + + if( !pFrame || pFrame->IsInDocBody() ) + { + // create index to determine the TextNode + SwNodeIndex aIdx( rTextNd ); + bIsInBody = rDoc.GetNodes().GetEndOfExtras().GetIndex() < aIdx.GetIndex(); + + // We don't want to update fields in redlines, or those + // in frames whose anchor is in redline. However, we do want to update + // fields in hidden sections. So: In order to be updated, a field 1) + // must have a frame, or 2) it must be in the document body. + if( (pFrame != nullptr) || bIsInBody ) + pNew.reset(new SetGetExpField( aIdx, &rTField )); + } + else + { + // create index to determine the TextNode + SwPosition aPos( rDoc.GetNodes().GetEndOfPostIts() ); + bool const bResult = GetBodyTextNode( rDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the Field"); + pNew.reset(new SetGetExpField( aPos.nNode, &rTField, &aPos.nContent )); + } + + // always set the BodyTextFlag in GetExp or DB fields + if( SwFieldIds::GetExp == nFieldWhich ) + { + SwGetExpField* pGetField = const_cast<SwGetExpField*>(static_cast<const SwGetExpField*>(rTField.GetFormatField().GetField())); + pGetField->ChgBodyTextFlag( bIsInBody ); + } +#if HAVE_FEATURE_DBCONNECTIVITY + else if( SwFieldIds::Database == nFieldWhich ) + { + SwDBField* pDBField = const_cast<SwDBField*>(static_cast<const SwDBField*>(rTField.GetFormatField().GetField())); + pDBField->ChgBodyTextFlag( bIsInBody ); + } +#endif + if( pNew != nullptr ) + m_pFieldSortList->insert( std::move(pNew) ); +} + +void SwDocUpdateField::GetBodyNode( const SwSectionNode& rSectNd ) +{ + const SwDoc& rDoc = *rSectNd.GetDoc(); + std::unique_ptr<SetGetExpField> pNew; + + if( rSectNd.GetIndex() < rDoc.GetNodes().GetEndOfExtras().GetIndex() ) + { + do { // middle check loop + + // we need to get the anchor first + // create index to determine the TextNode + SwPosition aPos( rSectNd ); + SwContentNode* pCNd = rDoc.GetNodes().GoNext( &aPos.nNode ); // to the next ContentNode + + if( !pCNd || !pCNd->IsTextNode() ) + break; + + // always the first! (in tab headline, header-/footer) + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame* pFrame = pCNd->getLayoutFrame( + rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + if( !pFrame ) + break; + + bool const bResult = GetBodyTextNode( rDoc, aPos, *pFrame ); + OSL_ENSURE(bResult, "where is the Field"); + pNew.reset(new SetGetExpField( rSectNd, &aPos )); + + } while( false ); + } + + if( !pNew ) + pNew.reset(new SetGetExpField( rSectNd )); + + m_pFieldSortList->insert( std::move(pNew) ); +} + +void SwDocUpdateField::InsertFieldType( const SwFieldType& rType ) +{ + OUString sFieldName; + switch( rType.Which() ) + { + case SwFieldIds::User : + sFieldName = static_cast<const SwUserFieldType&>(rType).GetName(); + break; + case SwFieldIds::SetExp: + sFieldName = static_cast<const SwSetExpFieldType&>(rType).GetName(); + break; + default: + OSL_ENSURE( false, "No valid field type" ); + } + + if( !sFieldName.isEmpty() ) + { + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + sal_uInt16 n; + + SwCalcFieldType* pFnd = GetFieldTypeTable().Find( sFieldName, &n ); + + if( !pFnd ) + { + SwCalcFieldType* pNew = new SwCalcFieldType( sFieldName, &rType ); + pNew->pNext.reset( m_FieldTypeTable[n].release() ); + m_FieldTypeTable[n].reset(pNew); + } + } +} + +void SwDocUpdateField::RemoveFieldType( const SwFieldType& rType ) +{ + OUString sFieldName; + switch( rType.Which() ) + { + case SwFieldIds::User : + sFieldName = static_cast<const SwUserFieldType&>(rType).GetName(); + break; + case SwFieldIds::SetExp: + sFieldName = static_cast<const SwSetExpFieldType&>(rType).GetName(); + break; + default: break; + } + + if( !sFieldName.isEmpty() ) + { + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + sal_uInt16 n; + + SwCalcFieldType* pFnd = GetFieldTypeTable().Find( sFieldName, &n ); + if( pFnd ) + { + if (m_FieldTypeTable[n].get() == pFnd) + { + m_FieldTypeTable[n].reset(static_cast<SwCalcFieldType*>(pFnd->pNext.release())); + } + else + { + SwHash* pPrev = m_FieldTypeTable[n].get(); + while( pPrev->pNext.get() != pFnd ) + pPrev = pPrev->pNext.get(); + pPrev->pNext = std::move(pFnd->pNext); + // no need to explicitly delete here, the embedded linked list uses unique_ptr + } + } + } +} + +SwDocUpdateField::SwDocUpdateField(SwDoc& rDoc) + : m_FieldTypeTable(TBLSZ) + , m_nNodes(0) + , m_nFieldListGetMode(0) + , m_rDoc(rDoc) + , m_bInUpdateFields(false) + , m_bFieldsDirty(false) +{ +} + +SwDocUpdateField::~SwDocUpdateField() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfly.cxx b/sw/source/core/doc/docfly.cxx new file mode 100644 index 000000000..988e59c76 --- /dev/null +++ b/sw/source/core/doc/docfly.cxx @@ -0,0 +1,1159 @@ +/* -*- 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 <svl/itemiter.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdmark.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <dcontact.hxx> +#include <ndgrf.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <ndindex.hxx> +#include <docary.hxx> +#include <drawdoc.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtflcnt.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <flyfrm.hxx> +#include <textboxhelper.hxx> +#include <txatbase.hxx> +#include <frmfmt.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <crstate.hxx> +#include <UndoCore.hxx> +#include <UndoAttribute.hxx> +#include <fmtcnct.hxx> +#include <dflyobj.hxx> +#include <undoflystrattr.hxx> +#include <calbck.hxx> +#include <frameformats.hxx> +#include <memory> +#include <svx/xbtmpit.hxx> +#include <svx/xflftrit.hxx> +#include <svx/xlndsit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xflhtit.hxx> + +using namespace ::com::sun::star; + +size_t SwDoc::GetFlyCount( FlyCntType eType, bool bIgnoreTextBoxes ) const +{ + const SwFrameFormats& rFormats = *GetSpzFrameFormats(); + const size_t nSize = rFormats.size(); + size_t nCount = 0; + const SwNodeIndex* pIdx; + + for ( size_t i = 0; i < nSize; ++i) + { + const SwFrameFormat* pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + continue; + + if( RES_FLYFRMFMT != pFlyFormat->Which() ) + continue; + pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNodes().IsDocNodes() ) + { + const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + + switch( eType ) + { + case FLYCNTTYPE_FRM: + if(!pNd->IsNoTextNode()) + nCount++; + break; + + case FLYCNTTYPE_GRF: + if( pNd->IsGrfNode() ) + nCount++; + break; + + case FLYCNTTYPE_OLE: + if(pNd->IsOLENode()) + nCount++; + break; + + default: + nCount++; + } + } + } + return nCount; +} + +/// @attention If you change this, also update SwXFrameEnumeration in unocoll. +SwFrameFormat* SwDoc::GetFlyNum( size_t nIdx, FlyCntType eType, bool bIgnoreTextBoxes ) +{ + SwFrameFormats& rFormats = *GetSpzFrameFormats(); + SwFrameFormat* pRetFormat = nullptr; + const size_t nSize = rFormats.size(); + const SwNodeIndex* pIdx; + size_t nCount = 0; + + for( size_t i = 0; !pRetFormat && i < nSize; ++i ) + { + SwFrameFormat* pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + continue; + + if( RES_FLYFRMFMT != pFlyFormat->Which() ) + continue; + pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNodes().IsDocNodes() ) + { + const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + switch( eType ) + { + case FLYCNTTYPE_FRM: + if( !pNd->IsNoTextNode() && nIdx == nCount++) + pRetFormat = pFlyFormat; + break; + case FLYCNTTYPE_GRF: + if(pNd->IsGrfNode() && nIdx == nCount++ ) + pRetFormat = pFlyFormat; + break; + case FLYCNTTYPE_OLE: + if(pNd->IsOLENode() && nIdx == nCount++) + pRetFormat = pFlyFormat; + break; + default: + if(nIdx == nCount++) + pRetFormat = pFlyFormat; + } + } + } + return pRetFormat; +} + +std::vector<SwFrameFormat const*> SwDoc::GetFlyFrameFormats( + FlyCntType const eType, bool const bIgnoreTextBoxes) +{ + SwFrameFormats& rFormats = *GetSpzFrameFormats(); + const size_t nSize = rFormats.size(); + + std::vector<SwFrameFormat const*> ret; + ret.reserve(nSize); + + for (size_t i = 0; i < nSize; ++i) + { + SwFrameFormat const*const pFlyFormat = rFormats[ i ]; + + if (bIgnoreTextBoxes && SwTextBoxHelper::isTextBox(pFlyFormat, RES_FLYFRMFMT)) + { + continue; + } + + if (RES_FLYFRMFMT != pFlyFormat->Which()) + { + continue; + } + + SwNodeIndex const*const pIdx(pFlyFormat->GetContent().GetContentIdx()); + if (pIdx && pIdx->GetNodes().IsDocNodes()) + { + SwNode const*const pNd = GetNodes()[ pIdx->GetIndex() + 1 ]; + switch (eType) + { + case FLYCNTTYPE_FRM: + if (!pNd->IsNoTextNode()) + ret.push_back(pFlyFormat); + break; + case FLYCNTTYPE_GRF: + if (pNd->IsGrfNode()) + ret.push_back(pFlyFormat); + break; + case FLYCNTTYPE_OLE: + if (pNd->IsOLENode()) + ret.push_back(pFlyFormat); + break; + default: + ret.push_back(pFlyFormat); + } + } + } + + return ret; +} + +static Point lcl_FindAnchorLayPos( SwDoc& rDoc, const SwFormatAnchor& rAnch, + const SwFrameFormat* pFlyFormat ) +{ + Point aRet; + if( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + switch( rAnch.GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + if( pFlyFormat && rAnch.GetContentAnchor() ) + { + const SwFrame* pOld = static_cast<const SwFlyFrameFormat*>(pFlyFormat)->GetFrame( &aRet ); + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + if( rAnch.GetContentAnchor() ) + { + const SwPosition *pPos = rAnch.GetContentAnchor(); + const SwContentNode* pNd = pPos->nNode.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(aRet, false); + const SwFrame* pOld = pNd ? pNd->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + if( rAnch.GetContentAnchor() ) + { + const SwFlyFrameFormat* pFormat = static_cast<SwFlyFrameFormat*>(rAnch.GetContentAnchor()-> + nNode.GetNode().GetFlyFormat()); + const SwFrame* pOld = pFormat ? pFormat->GetFrame( &aRet ) : nullptr; + if( pOld ) + aRet = pOld->getFrameArea().Pos(); + } + break; + + case RndStdIds::FLY_AT_PAGE: + { + sal_uInt16 nPgNum = rAnch.GetPageNum(); + const SwPageFrame *pPage = static_cast<SwPageFrame*>(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower()); + for( sal_uInt16 i = 1; (i <= nPgNum) && pPage; ++i, + pPage =static_cast<const SwPageFrame*>(pPage->GetNext()) ) + if( i == nPgNum ) + { + aRet = pPage->getFrameArea().Pos(); + break; + } + } + break; + default: + break; + } + return aRet; +} + +#define MAKEFRMS 0 +#define IGNOREANCHOR 1 +#define DONTMAKEFRMS 2 + +sal_Int8 SwDoc::SetFlyFrameAnchor( SwFrameFormat& rFormat, SfxItemSet& rSet, bool bNewFrames ) +{ + // Changing anchors is almost always allowed. + // Exception: Paragraph and character bound frames must not become + // page bound, if they are located in the header or footer. + const SwFormatAnchor &rOldAnch = rFormat.GetAnchor(); + const RndStdIds nOld = rOldAnch.GetAnchorId(); + + SwFormatAnchor aNewAnch( rSet.Get( RES_ANCHOR ) ); + RndStdIds nNew = aNewAnch.GetAnchorId(); + + // Is the new anchor valid? + if( !aNewAnch.GetContentAnchor() && (RndStdIds::FLY_AT_FLY == nNew || + (RndStdIds::FLY_AT_PARA == nNew) || (RndStdIds::FLY_AS_CHAR == nNew) || + (RndStdIds::FLY_AT_CHAR == nNew) )) + { + return IGNOREANCHOR; + } + + if( nOld == nNew ) + return DONTMAKEFRMS; + + Point aOldAnchorPos( ::lcl_FindAnchorLayPos( *this, rOldAnch, &rFormat )); + Point aNewAnchorPos( ::lcl_FindAnchorLayPos( *this, aNewAnch, nullptr )); + + // Destroy the old Frames. + // The Views are hidden implicitly, so hiding them another time would be + // kind of a show! + rFormat.DelFrames(); + + if ( RndStdIds::FLY_AS_CHAR == nOld ) + { + // We need to handle InContents in a special way: + // The TextAttribut needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + const SwPosition *pPos = rOldAnch.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + OSL_ENSURE( pHint && pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint && pHint->GetFlyCnt().GetFrameFormat() == &rFormat, + "Wrong TextFlyCnt-Hint." ); + if (pHint) + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat(); + + // They are disconnected. We now have to destroy the attribute. + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + // We can finally set the attribute. It needs to be the first one! + // Undo depends on it! + rFormat.SetFormatAttr( aNewAnch ); + + // Correct the position + const SfxPoolItem* pItem; + switch( nNew ) + { + case RndStdIds::FLY_AS_CHAR: + // If no position attributes are received, we have to make sure + // that no forbidden automatic alignment is left. + { + const SwPosition *pPos = aNewAnch.GetContentAnchor(); + SwTextNode *pNd = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Cursor does not point to TextNode." ); + + SwFormatFlyCnt aFormat( static_cast<SwFlyFrameFormat*>(&rFormat) ); + pNd->InsertItem( aFormat, pPos->nContent.GetIndex(), 0 ); + } + + if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) + { + SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); + bool bSet = true; + switch( aOldV.GetVertOrient() ) + { + case text::VertOrientation::LINE_TOP: aOldV.SetVertOrient( text::VertOrientation::TOP ); break; + case text::VertOrientation::LINE_CENTER: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; + case text::VertOrientation::LINE_BOTTOM: aOldV.SetVertOrient( text::VertOrientation::BOTTOM); break; + case text::VertOrientation::NONE: aOldV.SetVertOrient( text::VertOrientation::CENTER); break; + default: + bSet = false; + } + if( bSet ) + rSet.Put( aOldV ); + } + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: // LAYER_IMPL + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + case RndStdIds::FLY_AT_PAGE: + { + // If no position attributes are coming in, we correct the position in a way + // such that the fly's document coordinates are preserved. + // If only the alignment changes in the position attributes (text::RelOrientation::FRAME + // vs. text::RelOrientation::PRTAREA), we also correct the position. + if( SfxItemState::SET != rSet.GetItemState( RES_HORI_ORIENT, false, &pItem )) + pItem = nullptr; + + SwFormatHoriOrient aOldH( rFormat.GetHoriOrient() ); + bool bPutOldH(false); + + if( text::HoriOrientation::NONE == aOldH.GetHoriOrient() && ( !pItem || + aOldH.GetPos() == static_cast<const SwFormatHoriOrient*>(pItem)->GetPos() )) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldH.GetPos(); + nPos += aOldAnchorPos.getX() - aNewAnchorPos.getX(); + + if( pItem ) + { + SwFormatHoriOrient* pH = const_cast<SwFormatHoriOrient*>(static_cast<const SwFormatHoriOrient*>(pItem)); + aOldH.SetHoriOrient( pH->GetHoriOrient() ); + aOldH.SetRelationOrient( pH->GetRelationOrient() ); + } + aOldH.SetPos( nPos ); + bPutOldH = true; + } + if (nNew == RndStdIds::FLY_AT_PAGE) + { + sal_Int16 nRelOrient(pItem + ? static_cast<const SwFormatHoriOrient*>(pItem)->GetRelationOrient() + : aOldH.GetRelationOrient()); + if (sw::GetAtPageRelOrientation(nRelOrient, false)) + { + SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); + aOldH.SetRelationOrient(nRelOrient); + bPutOldH = true; + } + } + if (bPutOldH) + { + rSet.Put( aOldH ); + } + + if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false, &pItem )) + pItem = nullptr; + SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); + + // #i28922# - correction: compare <aOldV.GetVertOrient() with + // <text::VertOrientation::NONE> + if( text::VertOrientation::NONE == aOldV.GetVertOrient() && (!pItem || + aOldV.GetPos() == static_cast<const SwFormatVertOrient*>(pItem)->GetPos() ) ) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldV.GetPos(); + nPos += aOldAnchorPos.getY() - aNewAnchorPos.getY(); + if( pItem ) + { + SwFormatVertOrient* pV = const_cast<SwFormatVertOrient*>(static_cast<const SwFormatVertOrient*>(pItem)); + aOldV.SetVertOrient( pV->GetVertOrient() ); + aOldV.SetRelationOrient( pV->GetRelationOrient() ); + } + aOldV.SetPos( nPos ); + rSet.Put( aOldV ); + } + } + break; + default: + break; + } + + if( bNewFrames ) + rFormat.MakeFrames(); + + return MAKEFRMS; +} + +static bool +lcl_SetFlyFrameAttr(SwDoc & rDoc, + sal_Int8 (SwDoc::*pSetFlyFrameAnchor)(SwFrameFormat &, SfxItemSet &, bool), + SwFrameFormat & rFlyFormat, SfxItemSet & rSet) +{ + // #i32968# Inserting columns in the frame causes MakeFrameFormat to put two + // objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + // Is the anchor attribute included? + // If so, we pass it to a special method, which returns true + // if the Fly needs to be created anew, because we e.g change the FlyType. + sal_Int8 const nMakeFrames = + (SfxItemState::SET == rSet.GetItemState( RES_ANCHOR, false )) + ? (rDoc.*pSetFlyFrameAnchor)( rFlyFormat, rSet, false ) + : DONTMAKEFRMS; + + const SfxPoolItem* pItem; + SfxItemIter aIter( rSet ); + SfxItemSet aTmpSet( rDoc.GetAttrPool(), aFrameFormatSetRange ); + const SfxPoolItem* pItemIter = aIter.GetCurItem(); + do { + switch(pItemIter->Which()) + { + case RES_FILL_ORDER: + case RES_BREAK: + case RES_PAGEDESC: + case RES_CNTNT: + case RES_FOOTER: + OSL_FAIL( "Unknown Fly attribute." ); + [[fallthrough]]; + case RES_CHAIN: + rSet.ClearItem(pItemIter->Which()); + break; + case RES_ANCHOR: + if( DONTMAKEFRMS != nMakeFrames ) + break; + [[fallthrough]]; + default: + if( !IsInvalidItem(pItemIter) && ( SfxItemState::SET != + rFlyFormat.GetAttrSet().GetItemState(pItemIter->Which(), true, &pItem ) || + *pItem != *pItemIter)) + aTmpSet.Put(*pItemIter); + break; + } + + pItemIter = aIter.NextItem(); + + } while (pItemIter && (0 != pItemIter->Which())); + + if( aTmpSet.Count() ) + rFlyFormat.SetFormatAttr( aTmpSet ); + + if( MAKEFRMS == nMakeFrames ) + rFlyFormat.MakeFrames(); + + return aTmpSet.Count() || MAKEFRMS == nMakeFrames; +} + +void SwDoc::CheckForUniqueItemForLineFillNameOrIndex(SfxItemSet& rSet) +{ + SwDrawModel* pDrawModel = getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + SfxItemIter aIter(rSet); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if (IsInvalidItem(pItem)) + continue; + std::unique_ptr<SfxPoolItem> pResult; + + switch(pItem->Which()) + { + case XATTR_FILLBITMAP: + { + pResult = static_cast< const XFillBitmapItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEDASH: + { + pResult = static_cast< const XLineDashItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINESTART: + { + pResult = static_cast< const XLineStartItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEEND: + { + pResult = static_cast< const XLineEndItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLGRADIENT: + { + pResult = static_cast< const XFillGradientItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLFLOATTRANSPARENCE: + { + pResult = static_cast< const XFillFloatTransparenceItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLHATCH: + { + pResult = static_cast< const XFillHatchItem* >(pItem)->checkForUniqueItem(pDrawModel); + break; + } + } + + if(pResult) + { + rSet.Put(*pResult); + } + } +} + +bool SwDoc::SetFlyFrameAttr( SwFrameFormat& rFlyFormat, SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return false; + + std::unique_ptr<SwUndoFormatAttrHelper> pSaveUndo; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it + pSaveUndo.reset( new SwUndoFormatAttrHelper( rFlyFormat ) ); + } + + bool const bRet = lcl_SetFlyFrameAttr(*this, &SwDoc::SetFlyFrameAnchor, rFlyFormat, rSet); + + if (pSaveUndo && pSaveUndo->GetUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( pSaveUndo->ReleaseUndo() ); + } + + getIDocumentState().SetModified(); + + SwTextBoxHelper::syncFlyFrameAttr(rFlyFormat, rSet); + + return bRet; +} + +// #i73249# +void SwDoc::SetFlyFrameTitle( SwFlyFrameFormat& rFlyFrameFormat, + const OUString& sNewTitle ) +{ + if ( rFlyFrameFormat.GetObjTitle() == sNewTitle ) + { + return; + } + + ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoFlyStrAttr>( rFlyFrameFormat, + SwUndoId::FLYFRMFMT_TITLE, + rFlyFrameFormat.GetObjTitle(), + sNewTitle ) ); + } + + rFlyFrameFormat.SetObjTitle( sNewTitle, true ); + + getIDocumentState().SetModified(); +} + +void SwDoc::SetFlyFrameDescription( SwFlyFrameFormat& rFlyFrameFormat, + const OUString& sNewDescription ) +{ + if ( rFlyFrameFormat.GetObjDescription() == sNewDescription ) + { + return; + } + + ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoFlyStrAttr>( rFlyFrameFormat, + SwUndoId::FLYFRMFMT_DESCRIPTION, + rFlyFrameFormat.GetObjDescription(), + sNewDescription ) ); + } + + rFlyFrameFormat.SetObjDescription( sNewDescription, true ); + + getIDocumentState().SetModified(); +} + +bool SwDoc::SetFrameFormatToFly( SwFrameFormat& rFormat, SwFrameFormat& rNewFormat, + SfxItemSet* pSet, bool bKeepOrient ) +{ + bool bChgAnchor = false, bFrameSz = false; + + const SwFormatFrameSize aFrameSz( rFormat.GetFrameSize() ); + + SwUndoSetFlyFormat* pUndo = nullptr; + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if (bUndo) + { + pUndo = new SwUndoSetFlyFormat( rFormat, rNewFormat ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + // #i32968# Inserting columns in the section causes MakeFrameFormat to put + // 2 objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Set the column first, or we'll have trouble with + //Set/Reset/Synch. and so on + const SfxPoolItem* pItem; + if( SfxItemState::SET != rNewFormat.GetAttrSet().GetItemState( RES_COL )) + rFormat.ResetFormatAttr( RES_COL ); + + if( rFormat.DerivedFrom() != &rNewFormat ) + { + rFormat.SetDerivedFrom( &rNewFormat ); + + // 1. If not automatic = ignore; else = dispose + // 2. Dispose of it! + if( SfxItemState::SET == rNewFormat.GetAttrSet().GetItemState( RES_FRM_SIZE, false )) + { + rFormat.ResetFormatAttr( RES_FRM_SIZE ); + bFrameSz = true; + } + + const SfxItemSet* pAsk = pSet; + if( !pAsk ) pAsk = &rNewFormat.GetAttrSet(); + if( SfxItemState::SET == pAsk->GetItemState( RES_ANCHOR, false, &pItem ) + && static_cast<const SwFormatAnchor*>(pItem)->GetAnchorId() != + rFormat.GetAnchor().GetAnchorId() ) + { + if( pSet ) + bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, *pSet, false ); + else + { + // Needs to have the FlyFormat range, because we set attributes in it, + // in SetFlyFrameAnchor. + SfxItemSet aFlySet( *rNewFormat.GetAttrSet().GetPool(), + rNewFormat.GetAttrSet().GetRanges() ); + aFlySet.Put( *pItem ); + bChgAnchor = MAKEFRMS == SetFlyFrameAnchor( rFormat, aFlySet, false); + } + } + } + + // Only reset vertical and horizontal orientation, if we have automatic alignment + // set in the template. Otherwise use the old value. + // If we update the frame template the Fly should NOT lose its orientation (which + // is not being updated!). + // text::HoriOrientation::NONE and text::VertOrientation::NONE are allowed now + if (!bKeepOrient) + { + rFormat.ResetFormatAttr(RES_VERT_ORIENT); + rFormat.ResetFormatAttr(RES_HORI_ORIENT); + } + + rFormat.ResetFormatAttr( RES_PRINT, RES_SURROUND ); + rFormat.ResetFormatAttr( RES_LR_SPACE, RES_UL_SPACE ); + rFormat.ResetFormatAttr( RES_BACKGROUND, RES_COL ); + rFormat.ResetFormatAttr( RES_URL, RES_EDIT_IN_READONLY ); + + if( !bFrameSz ) + rFormat.SetFormatAttr( aFrameSz ); + + if( bChgAnchor ) + rFormat.MakeFrames(); + + if( pUndo ) + pUndo->EndListeningAll(); + + getIDocumentState().SetModified(); + + return bChgAnchor; +} + +void SwDoc::GetGrfNms( const SwFlyFrameFormat& rFormat, OUString* pGrfName, + OUString* pFltName ) +{ + SwNodeIndex aIdx( *rFormat.GetContent().GetContentIdx(), 1 ); + const SwGrfNode* pGrfNd = aIdx.GetNode().GetGrfNode(); + if( pGrfNd && pGrfNd->IsLinkedFile() ) + pGrfNd->GetFileFilterNms( pGrfName, pFltName ); +} + +bool SwDoc::ChgAnchor( const SdrMarkList& _rMrkList, + RndStdIds _eAnchorType, + const bool _bSameOnly, + const bool _bPosCorr ) +{ + OSL_ENSURE( getIDocumentLayoutAccess().GetCurrentLayout(), "No layout!" ); + + if ( !_rMrkList.GetMarkCount() || + _rMrkList.GetMark( 0 )->GetMarkedSdrObj()->getParentSdrObjectFromSdrObject() ) + { + return false; + } + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSATTR, nullptr ); + + bool bUnmark = false; + for ( size_t i = 0; i < _rMrkList.GetMarkCount(); ++i ) + { + SdrObject* pObj = _rMrkList.GetMark( i )->GetMarkedSdrObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + { + SwDrawContact* pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + + // consider, that drawing object has + // no user call. E.g.: a 'virtual' drawing object is disconnected by + // the anchor type change of the 'master' drawing object. + // Continue with next selected object and assert, if this isn't excepted. + if ( !pContact ) + { +#if OSL_DEBUG_LEVEL > 0 + bool bNoUserCallExcepted = + dynamic_cast<const SwDrawVirtObj*>( pObj) != nullptr && + !static_cast<SwDrawVirtObj*>(pObj)->IsConnected(); + OSL_ENSURE( bNoUserCallExcepted, "SwDoc::ChgAnchor(..) - no contact at selected drawing object" ); +#endif + continue; + } + + // #i26791# + const SwFrame* pOldAnchorFrame = pContact->GetAnchorFrame( pObj ); + const SwFrame* pNewAnchorFrame = pOldAnchorFrame; + + // #i54336# + // Instead of only keeping the index position for an as-character + // anchored object the complete <SwPosition> is kept, because the + // anchor index position could be moved, if the object again is + // anchored as character. + std::unique_ptr<const SwPosition> xOldAsCharAnchorPos; + const RndStdIds eOldAnchorType = pContact->GetAnchorId(); + if ( !_bSameOnly && eOldAnchorType == RndStdIds::FLY_AS_CHAR ) + { + xOldAsCharAnchorPos.reset(new SwPosition(pContact->GetContentAnchor())); + } + + if ( _bSameOnly ) + _eAnchorType = eOldAnchorType; + + SwFormatAnchor aNewAnch( _eAnchorType ); + SwAnchoredObject *pAnchoredObj = pContact->GetAnchoredObj(pObj); + tools::Rectangle aObjRect(pAnchoredObj->GetObjRect().SVRect()); + const Point aPt( aObjRect.TopLeft() ); + + switch ( _eAnchorType ) + { + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + const Point aNewPoint = ( pOldAnchorFrame->IsVertical() || + pOldAnchorFrame->IsRightToLeft() ) + ? aObjRect.TopRight() + : aPt; + + // allow drawing objects in header/footer + pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aNewPoint ); + if ( pNewAnchorFrame->IsTextFrame() && static_cast<const SwTextFrame*>(pNewAnchorFrame)->IsFollow() ) + { + pNewAnchorFrame = static_cast<const SwTextFrame*>(pNewAnchorFrame)->FindMaster(); + } + if ( pNewAnchorFrame->IsProtected() ) + { + pNewAnchorFrame = nullptr; + } + else + { + SwPosition aPos( pNewAnchorFrame->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pNewAnchorFrame)->GetTextNodeForParaProps() + : *static_cast<SwNoTextFrame const*>(pNewAnchorFrame)->GetNode() ); + + aNewAnch.SetType( _eAnchorType ); + aNewAnch.SetAnchor( &aPos ); + } + } + break; + + case RndStdIds::FLY_AT_FLY: // LAYER_IMPL + { + // Search the closest SwFlyFrame starting from the upper left corner. + SwFrame *pTextFrame; + { + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + SwPosition aPos( GetNodes() ); + Point aPoint( aPt ); + aPoint.setX(aPoint.getX() - 1); + getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + // consider that drawing objects can be in + // header/footer. Thus, <GetFrame()> by left-top-corner + std::pair<Point, bool> const tmp(aPt, false); + pTextFrame = aPos.nNode.GetNode(). + GetContentNode()->getLayoutFrame( + getIDocumentLayoutAccess().GetCurrentLayout(), + nullptr, &tmp); + } + const SwFrame *pTmp = ::FindAnchor( pTextFrame, aPt ); + pNewAnchorFrame = pTmp->FindFlyFrame(); + if( pNewAnchorFrame && !pNewAnchorFrame->IsProtected() ) + { + const SwFrameFormat *pTmpFormat = static_cast<const SwFlyFrame*>(pNewAnchorFrame)->GetFormat(); + const SwFormatContent& rContent = pTmpFormat->GetContent(); + SwPosition aPos( *rContent.GetContentIdx() ); + aNewAnch.SetAnchor( &aPos ); + break; + } + + aNewAnch.SetType( RndStdIds::FLY_AT_PAGE ); + [[fallthrough]]; + } + case RndStdIds::FLY_AT_PAGE: + { + pNewAnchorFrame = getIDocumentLayoutAccess().GetCurrentLayout()->Lower(); + while ( pNewAnchorFrame && !pNewAnchorFrame->getFrameArea().IsInside( aPt ) ) + pNewAnchorFrame = pNewAnchorFrame->GetNext(); + if ( !pNewAnchorFrame ) + continue; + + aNewAnch.SetPageNum( static_cast<const SwPageFrame*>(pNewAnchorFrame)->GetPhyPageNum()); + } + break; + case RndStdIds::FLY_AS_CHAR: + if( _bSameOnly ) // Change of position/size + { + if( !pOldAnchorFrame ) + { + pContact->ConnectToLayout(); + pOldAnchorFrame = pContact->GetAnchorFrame(); + } + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pOldAnchorFrame))->Prepare(); + } + else // Change of anchors + { + // allow drawing objects in header/footer + pNewAnchorFrame = ::FindAnchor( pOldAnchorFrame, aPt ); + if( pNewAnchorFrame->IsProtected() ) + { + pNewAnchorFrame = nullptr; + break; + } + + bUnmark = ( 0 != i ); + Point aPoint( aPt ); + aPoint.setX(aPoint.getX() - 1); // Do not load in the DrawObj! + aNewAnch.SetType( RndStdIds::FLY_AS_CHAR ); + assert(pNewAnchorFrame->IsTextFrame()); // because AS_CHAR + SwTextFrame const*const pFrame( + static_cast<SwTextFrame const*>(pNewAnchorFrame)); + SwPosition aPos( *pFrame->GetTextNodeForParaProps() ); + if ( pNewAnchorFrame->getFrameArea().IsInside( aPoint ) ) + { + // We need to find a TextNode, because only there we can anchor a + // content-bound DrawObject. + SwCursorMoveState aState( CursorMoveState::SetOnlyText ); + getIDocumentLayoutAccess().GetCurrentLayout()->GetModelPositionForViewPoint( &aPos, aPoint, &aState ); + } + else + { + if ( pNewAnchorFrame->getFrameArea().Bottom() < aPt.Y() ) + { + aPos = pFrame->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + aPos = pFrame->MapViewToModelPos( + TextFrameIndex(pFrame->GetText().getLength())); + } + } + aNewAnch.SetAnchor( &aPos ); + SetAttr( aNewAnch, *pContact->GetFormat() ); + // #i26791# - adjust vertical positioning to 'center to + // baseline' + SetAttr( SwFormatVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME ), *pContact->GetFormat() ); + SwTextNode *pNd = aPos.nNode.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Cursor not positioned at TextNode." ); + + SwFormatFlyCnt aFormat( pContact->GetFormat() ); + pNd->InsertItem( aFormat, aPos.nContent.GetIndex(), 0 ); + } + break; + default: + OSL_ENSURE( false, "unexpected AnchorId." ); + } + + if ( (RndStdIds::FLY_AS_CHAR != _eAnchorType) && + pNewAnchorFrame && + ( !_bSameOnly || pNewAnchorFrame != pOldAnchorFrame ) ) + { + // #i26791# - Direct object positioning no longer needed. Apply + // of attributes (method call <SetAttr(..)>) takes care of the + // invalidation of the object position. + if ( _bPosCorr ) + { + // #i33313# - consider not connected 'virtual' drawing + // objects + if ( dynamic_cast<const SwDrawVirtObj*>( pObj) != nullptr && + !static_cast<SwDrawVirtObj*>(pObj)->IsConnected() ) + { + SwRect aNewObjRect( aObjRect ); + static_cast<SwAnchoredDrawObject*>(pContact->GetAnchoredObj( nullptr )) + ->AdjustPositioningAttr( pNewAnchorFrame, + &aNewObjRect ); + } + else + { + static_cast<SwAnchoredDrawObject*>(pContact->GetAnchoredObj( pObj )) + ->AdjustPositioningAttr( pNewAnchorFrame ); + } + } + if (aNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + SwFormatHoriOrient item(pContact->GetFormat()->GetHoriOrient()); + sal_Int16 nRelOrient(item.GetRelationOrient()); + if (sw::GetAtPageRelOrientation(nRelOrient, false)) + { + SAL_INFO("sw.ui", "fixing horizontal RelOrientation for at-page anchor"); + item.SetRelationOrient(nRelOrient); + SetAttr(item, *pContact->GetFormat()); + } + } + // tdf#136385 set the anchor last - otherwise it messes up the + // position in SwDrawContact::Changed_() callback + SetAttr(aNewAnch, *pContact->GetFormat()); + } + + // we have changed the anchoring attributes, and those are used to + // order the object in its sorted list, so update its position + pAnchoredObj->UpdateObjInSortedList(); + + // #i54336# + if (xOldAsCharAnchorPos) + { + if ( pNewAnchorFrame) + { + // We need to handle InContents in a special way: + // The TextAttribut needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + const sal_Int32 nIndx( xOldAsCharAnchorPos->nContent.GetIndex() ); + SwTextNode* pTextNode( xOldAsCharAnchorPos->nNode.GetNode().GetTextNode() ); + assert(pTextNode && "<SwDoc::ChgAnchor(..)> - missing previous anchor text node for as-character anchored object"); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIndx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing FlyInCnt-Hint."); + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat(); + + // They are disconnected. We now have to destroy the attribute. + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIndx, nIndx ); + } + } + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + return bUnmark; +} + +SwChainRet SwDoc::Chainable( const SwFrameFormat &rSource, const SwFrameFormat &rDest ) +{ + // The Source must not yet have a Follow. + const SwFormatChain &rOldChain = rSource.GetChain(); + if ( rOldChain.GetNext() ) + return SwChainRet::SOURCE_CHAINED; + + // Target must not be equal to Source and we also must not have a closed chain. + const SwFrameFormat *pFormat = &rDest; + do { + if( pFormat == &rSource ) + return SwChainRet::SELF; + pFormat = pFormat->GetChain().GetNext(); + } while ( pFormat ); + + // There must not be a chaining from outside to inside or the other way around. + if( rDest.IsLowerOf( rSource ) || rSource .IsLowerOf( rDest ) ) + return SwChainRet::SELF; + + // The Target must not yet have a Master. + const SwFormatChain &rChain = rDest.GetChain(); + if( rChain.GetPrev() ) + return SwChainRet::IS_IN_CHAIN; + + // Target must be empty. + const SwNodeIndex* pCntIdx = rDest.GetContent().GetContentIdx(); + if( !pCntIdx ) + return SwChainRet::NOT_FOUND; + + SwNodeIndex aNxtIdx( *pCntIdx, 1 ); + const SwTextNode* pTextNd = aNxtIdx.GetNode().GetTextNode(); + if( !pTextNd ) + return SwChainRet::NOT_FOUND; + + const sal_uLong nFlySttNd = pCntIdx->GetIndex(); + if( 2 != ( pCntIdx->GetNode().EndOfSectionIndex() - nFlySttNd ) || + pTextNd->GetText().getLength() ) + { + return SwChainRet::NOT_EMPTY; + } + + for( auto pSpzFrameFm : *GetSpzFrameFormats() ) + { + const SwFormatAnchor& rAnchor = pSpzFrameFm->GetAnchor(); + // #i20622# - to-frame anchored objects are allowed. + if ( (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_PARA) && + (rAnchor.GetAnchorId() != RndStdIds::FLY_AT_CHAR) ) + continue; + if ( nullptr == rAnchor.GetContentAnchor() ) + continue; + sal_uLong nTstSttNd = rAnchor.GetContentAnchor()->nNode.GetIndex(); + if( nFlySttNd <= nTstSttNd && nTstSttNd < nFlySttNd + 2 ) + { + return SwChainRet::NOT_EMPTY; + } + } + + // We also need to consider the right area. + // Both Flys need to be located in the same area (Body, Header/Footer, Fly). + // If the Source is not the selected frame, it's enough to find a suitable + // one. e.g. if it's requested by the API. + + // both in the same fly, header, footer or on the page? + const SwFormatAnchor &rSrcAnchor = rSource.GetAnchor(), + &rDstAnchor = rDest.GetAnchor(); + sal_uLong nEndOfExtras = GetNodes().GetEndOfExtras().GetIndex(); + bool bAllowed = false; + if ( RndStdIds::FLY_AT_PAGE == rSrcAnchor.GetAnchorId() ) + { + if ( (RndStdIds::FLY_AT_PAGE == rDstAnchor.GetAnchorId()) || + ( rDstAnchor.GetContentAnchor() && + rDstAnchor.GetContentAnchor()->nNode.GetIndex() > nEndOfExtras )) + bAllowed = true; + } + else if( rSrcAnchor.GetContentAnchor() && rDstAnchor.GetContentAnchor() ) + { + const SwNodeIndex &rSrcIdx = rSrcAnchor.GetContentAnchor()->nNode, + &rDstIdx = rDstAnchor.GetContentAnchor()->nNode; + const SwStartNode* pSttNd = nullptr; + if( rSrcIdx == rDstIdx || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindFlyStartNode() ) && + pSttNd == rDstIdx.GetNode().FindFlyStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindFooterStartNode() ) && + pSttNd == rDstIdx.GetNode().FindFooterStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcIdx.GetNode().FindHeaderStartNode() ) && + pSttNd == rDstIdx.GetNode().FindHeaderStartNode() ) || + ( !pSttNd && rDstIdx.GetIndex() > nEndOfExtras && + rSrcIdx.GetIndex() > nEndOfExtras )) + bAllowed = true; + } + + return bAllowed ? SwChainRet::OK : SwChainRet::WRONG_AREA; +} + +SwChainRet SwDoc::Chain( SwFrameFormat &rSource, const SwFrameFormat &rDest ) +{ + SwChainRet nErr = Chainable( rSource, rDest ); + if ( nErr == SwChainRet::OK ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::CHAINE, nullptr ); + + SwFlyFrameFormat& rDestFormat = const_cast<SwFlyFrameFormat&>(static_cast<const SwFlyFrameFormat&>(rDest)); + + // Attach Follow to the Master. + SwFormatChain aChain = rDestFormat.GetChain(); + aChain.SetPrev( &static_cast<SwFlyFrameFormat&>(rSource) ); + SetAttr( aChain, rDestFormat ); + + SfxItemSet aSet( GetAttrPool(), svl::Items<RES_FRM_SIZE, RES_FRM_SIZE, + RES_CHAIN, RES_CHAIN>{} ); + + // Attach Follow to the Master. + aChain.SetPrev( &static_cast<SwFlyFrameFormat&>(rSource) ); + SetAttr( aChain, rDestFormat ); + + // Attach Master to the Follow. + // Make sure that the Master has a fixed height. + aChain = rSource.GetChain(); + aChain.SetNext( &rDestFormat ); + aSet.Put( aChain ); + + SwFormatFrameSize aSize( rSource.GetFrameSize() ); + if ( aSize.GetHeightSizeType() != SwFrameSize::Fixed ) + { + SwFlyFrame *pFly = SwIterator<SwFlyFrame,SwFormat>( rSource ).First(); + if ( pFly ) + aSize.SetHeight( pFly->getFrameArea().Height() ); + aSize.SetHeightSizeType( SwFrameSize::Fixed ); + aSet.Put( aSize ); + } + SetAttr( aSet, rSource ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::CHAINE, nullptr ); + } + return nErr; +} + +void SwDoc::Unchain( SwFrameFormat &rFormat ) +{ + SwFormatChain aChain( rFormat.GetChain() ); + if ( aChain.GetNext() ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::UNCHAIN, nullptr ); + SwFrameFormat *pFollow = aChain.GetNext(); + aChain.SetNext( nullptr ); + SetAttr( aChain, rFormat ); + aChain = pFollow->GetChain(); + aChain.SetPrev( nullptr ); + SetAttr( aChain, *pFollow ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::UNCHAIN, nullptr ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docfmt.cxx b/sw/source/core/doc/docfmt.cxx new file mode 100644 index 000000000..7b877a7cd --- /dev/null +++ b/sw/source/core/doc/docfmt.cxx @@ -0,0 +1,2151 @@ +/* -*- 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 <hintids.hxx> +#include <svl/itemiter.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/rsiditem.hxx> +#include <editeng/colritem.hxx> +#include <svl/zforlist.hxx> +#include <comphelper/doublecheckedinit.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/misccfg.hxx> +#include <sal/log.hxx> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <fmtpdsc.hxx> +#include <fmthdft.hxx> +#include <fmtcntnt.hxx> +#include <doc.hxx> +#include <docfunc.hxx> +#include <drawdoc.hxx> +#include <MarkManager.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <hints.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <UndoCore.hxx> +#include <UndoAttribute.hxx> +#include <UndoInsert.hxx> +#include <pagedesc.hxx> +#include <rolbck.hxx> +#include <mvsave.hxx> +#include <txatbase.hxx> +#include <swtblfmt.hxx> +#include <charfmt.hxx> +#include <docary.hxx> +#include <paratr.hxx> +#include <redline.hxx> +#include <reffld.hxx> +#include <fmtinfmt.hxx> +#include <breakit.hxx> +#include <SwUndoFmt.hxx> +#include <UndoManager.hxx> +#include <swmodule.hxx> +#include <modcfg.hxx> +#include <frameformats.hxx> +#include <memory> + +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; + +/* + * Internal functions + */ + +static void SetTextFormatCollNext( SwTextFormatColl* pTextColl, const SwTextFormatColl* pDel ) +{ + if ( &pTextColl->GetNextTextFormatColl() == pDel ) + { + pTextColl->SetNextTextFormatColl( *pTextColl ); + } +} + +static bool lcl_RstAttr( const SwNodePtr& rpNd, void* pArgs ) +{ + const sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast<sw::DocumentContentOperationsManager::ParaRstFormat*>(pArgs); + SwContentNode* pNode = rpNd->GetContentNode(); + if (pPara && pPara->pLayout && pPara->pLayout->IsHideRedlines() + && pNode && pNode->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if( pNode && pNode->HasSwAttrSet() ) + { + const bool bLocked = pNode->IsModifyLocked(); + pNode->LockModify(); + + SwDoc* pDoc = pNode->GetDoc(); + + // remove unused attribute RES_LR_SPACE + // add list attributes + SfxItemSet aSavedAttrsSet( + pDoc->GetAttrPool(), + svl::Items< + RES_PARATR_NUMRULE, RES_PARATR_NUMRULE, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1, + RES_PAGEDESC, RES_BREAK>{}); + const SfxItemSet* pAttrSetOfNode = pNode->GetpSwAttrSet(); + + std::vector<sal_uInt16> aClearWhichIds; + // restoring all paragraph list attributes + { + SfxItemSet aListAttrSet( pDoc->GetAttrPool(), svl::Items<RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END - 1>{} ); + aListAttrSet.Set(*pAttrSetOfNode); + if ( aListAttrSet.Count() ) + { + aSavedAttrsSet.Put(aListAttrSet); + SfxItemIter aIter( aListAttrSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + while( pItem ) + { + aClearWhichIds.push_back( pItem->Which() ); + pItem = aIter.NextItem(); + } + } + } + + const SfxPoolItem* pItem; + + sal_uInt16 const aSavIds[3] = { RES_PAGEDESC, RES_BREAK, RES_PARATR_NUMRULE }; + for (sal_uInt16 aSavId : aSavIds) + { + if (SfxItemState::SET == pAttrSetOfNode->GetItemState(aSavId, false, &pItem)) + { + bool bSave = false; + switch( aSavId ) + { + case RES_PAGEDESC: + bSave = nullptr != static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc(); + break; + case RES_BREAK: + bSave = SvxBreak::NONE != static_cast<const SvxFormatBreakItem*>(pItem)->GetBreak(); + break; + case RES_PARATR_NUMRULE: + bSave = !static_cast<const SwNumRuleItem*>(pItem)->GetValue().isEmpty(); + break; + } + if( bSave ) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(aSavId); + } + } + } + + // do not clear items directly from item set and only clear to be kept + // attributes, if no deletion item set is found. + const bool bKeepAttributes = + !pPara || !pPara->pDelSet || pPara->pDelSet->Count() == 0; + if ( bKeepAttributes ) + { + pNode->ResetAttr( aClearWhichIds ); + } + + if( !bLocked ) + pNode->UnlockModify(); + + if( pPara ) + { + SwRegHistory aRegH( pNode, *pNode, pPara->pHistory ); + + if( pPara->pDelSet && pPara->pDelSet->Count() ) + { + OSL_ENSURE( !bKeepAttributes, + "<lcl_RstAttr(..)> - certain attributes are kept, but not needed." ); + SfxItemIter aIter( *pPara->pDelSet ); + for (pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if ( ( pItem->Which() != RES_PAGEDESC && + pItem->Which() != RES_BREAK && + pItem->Which() != RES_PARATR_NUMRULE ) || + ( aSavedAttrsSet.GetItemState( pItem->Which(), false ) != SfxItemState::SET ) ) + { + pNode->ResetAttr( pItem->Which() ); + } + } + } + else if( pPara->bResetAll ) + pNode->ResetAllAttr(); + else + pNode->ResetAttr( RES_PARATR_BEGIN, POOLATTR_END - 1 ); + } + else + pNode->ResetAllAttr(); + + // only restore saved attributes, if needed + if (bKeepAttributes && aSavedAttrsSet.Count()) + { + pNode->LockModify(); + + pNode->SetAttr(aSavedAttrsSet); + + if( !bLocked ) + pNode->UnlockModify(); + } + } + return true; +} + +void SwDoc::RstTextAttrs(const SwPaM &rRg, bool bInclRefToxMark, + bool bExactRange, SwRootFrame const*const pLayout) +{ + SwHistory* pHst = nullptr; + SwDataChanged aTmp( rRg ); + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoResetAttr> pUndo(new SwUndoResetAttr( rRg, RES_CHRFMT )); + pHst = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout ); + aPara.bInclRefToxMark = bInclRefToxMark; + aPara.bExactRange = bExactRange; + GetNodes().ForEach( pStt->nNode.GetIndex(), pEnd->nNode.GetIndex()+1, + sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrs( const SwPaM &rRg, + bool bTextAttr, + const std::set<sal_uInt16> &rAttrs, + const bool bSendDataChangedEvents, + SwRootFrame const*const pLayout) +{ + SwPaM* pPam = const_cast<SwPaM*>(&rRg); + if( !bTextAttr && !rAttrs.empty() && RES_TXTATR_END > *(rAttrs.begin()) ) + bTextAttr = true; + + if( !rRg.HasMark() ) + { + SwTextNode* pTextNd = rRg.GetPoint()->nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return ; + + pPam = new SwPaM( *rRg.GetPoint() ); + + SwIndex& rSt = pPam->GetPoint()->nContent; + sal_Int32 nMkPos, nPtPos = rSt.GetIndex(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetIndex(), RES_TXTATR_INETFMT)); + if (pURLAttr && !pURLAttr->GetINetFormat().GetValue().isEmpty()) + { + nMkPos = pURLAttr->GetStart(); + nPtPos = *pURLAttr->End(); + } + else + { + assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is()); + Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary( + pTextNd->GetText(), nPtPos, + g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ), + WordType::ANY_WORD /*ANYWORD_IGNOREWHITESPACES*/, + true); + + if( aBndry.startPos < nPtPos && nPtPos < aBndry.endPos ) + { + nMkPos = aBndry.startPos; + nPtPos = aBndry.endPos; + } + else + { + nPtPos = nMkPos = rSt.GetIndex(); + if( bTextAttr ) + pTextNd->DontExpandFormat( rSt ); + } + } + + rSt = nMkPos; + pPam->SetMark(); + pPam->GetPoint()->nContent = nPtPos; + } + + // #i96644# + std::unique_ptr< SwDataChanged > xDataChanged; + if ( bSendDataChangedEvents ) + { + xDataChanged.reset( new SwDataChanged( *pPam ) ); + } + SwHistory* pHst = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoResetAttr> pUndo(new SwUndoResetAttr( rRg, + bTextAttr ? sal_uInt16(RES_CONDTXTFMTCOLL) : sal_uInt16(RES_TXTFMTCOLL) )); + if( !rAttrs.empty() ) + { + pUndo->SetAttrs( rAttrs ); + } + pHst = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + const SwPosition *pStt = pPam->Start(), *pEnd = pPam->End(); + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout); + + // mst: not including META here; it seems attrs with CH_TXTATR are omitted + SfxItemSet aDelSet(GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_INETFMT, RES_TXTATR_UNKNOWN_CONTAINER, + RES_PARATR_BEGIN, RES_FRMATR_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>{}); + for( std::set<sal_uInt16>::const_reverse_iterator it = rAttrs.rbegin(); it != rAttrs.rend(); ++it ) + { + if( POOLATTR_END > *it ) + aDelSet.Put( *GetDfltAttr( *it )); + } + if( aDelSet.Count() ) + aPara.pDelSet = &aDelSet; + + bool bAdd = true; + SwNodeIndex aTmpStt( pStt->nNode ); + SwNodeIndex aTmpEnd( pEnd->nNode ); + if( pStt->nContent.GetIndex() ) // just one part + { + // set up a later, and all CharFormatAttr -> TextFormatAttr + SwTextNode* pTNd = aTmpStt.GetNode().GetTextNode(); + if( pTNd && pTNd->HasSwAttrSet() && pTNd->GetpSwAttrSet()->Count() ) + { + if (pHst) + { + SwRegHistory history(pTNd, *pTNd, pHst); + pTNd->FormatToTextAttr(pTNd); + } + else + { + pTNd->FormatToTextAttr(pTNd); + } + } + + ++aTmpStt; + } + if( pEnd->nContent.GetIndex() == pEnd->nNode.GetNode().GetContentNode()->Len() ) + { + // set up a later, and all CharFormatAttr -> TextFormatAttr + ++aTmpEnd; + bAdd = false; + } + else if( pStt->nNode != pEnd->nNode || !pStt->nContent.GetIndex() ) + { + SwTextNode* pTNd = aTmpEnd.GetNode().GetTextNode(); + if( pTNd && pTNd->HasSwAttrSet() && pTNd->GetpSwAttrSet()->Count() ) + { + if (pHst) + { + SwRegHistory history(pTNd, *pTNd, pHst); + pTNd->FormatToTextAttr(pTNd); + } + else + { + pTNd->FormatToTextAttr(pTNd); + } + } + } + + if( aTmpStt < aTmpEnd ) + GetNodes().ForEach( pStt->nNode, aTmpEnd, lcl_RstAttr, &aPara ); + else if( !rRg.HasMark() ) + { + aPara.bResetAll = false ; + ::lcl_RstAttr( &pStt->nNode.GetNode(), &aPara ); + aPara.bResetAll = true ; + } + + if( bTextAttr ) + { + if( bAdd ) + ++aTmpEnd; + GetNodes().ForEach( pStt->nNode, aTmpEnd, sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + getIDocumentState().SetModified(); + + xDataChanged.reset(); //before delete pPam + + if( pPam != &rRg ) + delete pPam; +} + +/// Set the rsid of the next nLen symbols of rRg to the current session number +void SwDoc::UpdateRsid( const SwPaM &rRg, const sal_Int32 nLen ) +{ + if (!SW_MOD()->GetModuleConfig()->IsStoreRsid()) + return; + + SwTextNode *pTextNode = rRg.GetPoint()->nNode.GetNode().GetTextNode(); + if (!pTextNode) + { + return; + } + const sal_Int32 nStart(rRg.GetPoint()->nContent.GetIndex() - nLen); + SvxRsidItem aRsid( mnRsid, RES_CHRATR_RSID ); + + SfxItemSet aSet(GetAttrPool(), svl::Items<RES_CHRATR_RSID, RES_CHRATR_RSID>{}); + aSet.Put(aRsid); + bool const bRet(pTextNode->SetAttr(aSet, nStart, + rRg.GetPoint()->nContent.GetIndex())); + + if (bRet && GetIDocumentUndoRedo().DoesUndo()) + { + SwUndo *const pLastUndo = GetUndoManager().GetLastUndo(); + SwUndoInsert *const pUndoInsert(dynamic_cast<SwUndoInsert*>(pLastUndo)); + // this function is called after Insert so expects to find SwUndoInsert + assert(pUndoInsert); + if (pUndoInsert) + { + pUndoInsert->SetWithRsid(); + } + } +} + +bool SwDoc::UpdateParRsid( SwTextNode *pTextNode, sal_uInt32 nVal ) +{ + if (!SW_MOD()->GetModuleConfig()->IsStoreRsid()) + return false; + + if (!pTextNode) + { + return false; + } + + SvxRsidItem aRsid( nVal ? nVal : mnRsid, RES_PARATR_RSID ); + return pTextNode->SetAttr( aRsid ); +} + +/// Set the attribute according to the stated format. +/// If Undo is enabled, the old values is added to the Undo history. +void SwDoc::SetAttr( const SfxPoolItem& rAttr, SwFormat& rFormat ) +{ + SfxItemSet aSet( GetAttrPool(), {{rAttr.Which(), rAttr.Which()}} ); + aSet.Put( rAttr ); + SetAttr( aSet, rFormat ); +} + +/// Set the attribute according to the stated format. +/// If Undo is enabled, the old values is added to the Undo history. +void SwDoc::SetAttr( const SfxItemSet& rSet, SwFormat& rFormat ) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + SwUndoFormatAttrHelper aTmp( rFormat ); + rFormat.SetFormatAttr( rSet ); + if ( aTmp.GetUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( aTmp.ReleaseUndo() ); + } + else + { + GetIDocumentUndoRedo().ClearRedo(); + } + } + else + { + rFormat.SetFormatAttr( rSet ); + } + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrAtFormat( const sal_uInt16 nWhichId, + SwFormat& rChangedFormat ) +{ + std::unique_ptr<SwUndo> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoFormatResetAttr( rChangedFormat, nWhichId )); + + const bool bAttrReset = rChangedFormat.ResetFormatAttr( nWhichId ); + + if ( bAttrReset ) + { + if ( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + getIDocumentState().SetModified(); + } +} + +static bool lcl_SetNewDefTabStops( SwTwips nOldWidth, SwTwips nNewWidth, + SvxTabStopItem& rChgTabStop ) +{ + // Set the default values of all TabStops to the new value. + // Attention: we always work with the PoolAttribute here, so that + // we don't calculate the same value on the same TabStop (pooled!) for all sets. + // We send a FormatChg to modify. + + sal_uInt16 nOldCnt = rChgTabStop.Count(); + if( !nOldCnt || nOldWidth == nNewWidth ) + return false; + + // Find the default's beginning + sal_uInt16 n; + for( n = nOldCnt; n ; --n ) + if( SvxTabAdjust::Default != rChgTabStop[n - 1].GetAdjustment() ) + break; + ++n; + if( n < nOldCnt ) // delete the DefTabStops + rChgTabStop.Remove( n, nOldCnt - n ); + return true; +} + +/// Set the attribute as new default attribute in this document. +/// If Undo is enabled, the old value is added to the Undo history. +void SwDoc::SetDefault( const SfxPoolItem& rAttr ) +{ + SfxItemSet aSet( GetAttrPool(), {{rAttr.Which(), rAttr.Which()}} ); + aSet.Put( rAttr ); + SetDefault( aSet ); +} + +void SwDoc::SetDefault( const SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return; + + SwModify aCallMod; + SwAttrSet aOld( GetAttrPool(), rSet.GetRanges() ), + aNew( GetAttrPool(), rSet.GetRanges() ); + SfxItemIter aIter( rSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + SfxItemPool* pSdrPool = GetAttrPool().GetSecondaryPool(); + do + { + bool bCheckSdrDflt = false; + const sal_uInt16 nWhich = pItem->Which(); + aOld.Put( GetAttrPool().GetDefaultItem( nWhich ) ); + GetAttrPool().SetPoolDefaultItem( *pItem ); + aNew.Put( GetAttrPool().GetDefaultItem( nWhich ) ); + + if (isCHRATR(nWhich) || isTXTATR(nWhich)) + { + aCallMod.Add( mpDfltTextFormatColl.get() ); + aCallMod.Add( mpDfltCharFormat.get() ); + bCheckSdrDflt = nullptr != pSdrPool; + } + else if ( isPARATR(nWhich) || + isPARATR_LIST(nWhich) ) + { + aCallMod.Add( mpDfltTextFormatColl.get() ); + bCheckSdrDflt = nullptr != pSdrPool; + } + else if (isGRFATR(nWhich)) + { + aCallMod.Add( mpDfltGrfFormatColl.get() ); + } + else if (isFRMATR(nWhich) || isDrawingLayerAttribute(nWhich) ) + { + aCallMod.Add( mpDfltGrfFormatColl.get() ); + aCallMod.Add( mpDfltTextFormatColl.get() ); + aCallMod.Add( mpDfltFrameFormat.get() ); + } + else if (isBOXATR(nWhich)) + { + aCallMod.Add( mpDfltFrameFormat.get() ); + } + + // also copy the defaults + if( bCheckSdrDflt ) + { + sal_uInt16 nSlotId = GetAttrPool().GetSlotId( nWhich ); + if( 0 != nSlotId && nSlotId != nWhich ) + { + sal_uInt16 nEdtWhich = pSdrPool->GetWhich( nSlotId ); + if( 0 != nEdtWhich && nSlotId != nEdtWhich ) + { + std::unique_ptr<SfxPoolItem> pCpy(pItem->Clone()); + pCpy->SetWhich( nEdtWhich ); + pSdrPool->SetPoolDefaultItem( *pCpy ); + } + } + } + + pItem = aIter.NextItem(); + } while (pItem); + + if( aNew.Count() && aCallMod.HasWriterListeners() ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDefaultAttr>( aOld, this ) ); + } + + const SfxPoolItem* pTmpItem; + if( ( SfxItemState::SET == + aNew.GetItemState( RES_PARATR_TABSTOP, false, &pTmpItem ) ) && + static_cast<const SvxTabStopItem*>(pTmpItem)->Count() ) + { + // Set the default values of all TabStops to the new value. + // Attention: we always work with the PoolAttribute here, so that + // we don't calculate the same value on the same TabStop (pooled!) for all sets. + // We send a FormatChg to modify. + SwTwips nNewWidth = (*static_cast<const SvxTabStopItem*>(pTmpItem))[ 0 ].GetTabPos(), + nOldWidth = aOld.Get(RES_PARATR_TABSTOP)[ 0 ].GetTabPos(); + + bool bChg = false; + for (const SfxPoolItem* pItem2 : GetAttrPool().GetItemSurrogates(RES_PARATR_TABSTOP)) + { + auto pTabStopItem = dynamic_cast<const SvxTabStopItem*>(pItem2); + if(pTabStopItem) + bChg |= lcl_SetNewDefTabStops( nOldWidth, nNewWidth, + *const_cast<SvxTabStopItem*>(pTabStopItem) ); + } + + aNew.ClearItem( RES_PARATR_TABSTOP ); + aOld.ClearItem( RES_PARATR_TABSTOP ); + if( bChg ) + { + SwFormatChg aChgFormat( mpDfltCharFormat.get() ); + // notify the frames + aCallMod.ModifyNotification( &aChgFormat, &aChgFormat ); + } + } + } + + if( aNew.Count() && aCallMod.HasWriterListeners() ) + { + SwAttrSetChg aChgOld( aOld, aOld ); + SwAttrSetChg aChgNew( aNew, aNew ); + aCallMod.ModifyNotification( &aChgOld, &aChgNew ); // all changed are sent + } + + // remove the default formats from the object again + SwIterator<SwClient, SwModify> aClientIter(aCallMod); + for(SwClient* pClient = aClientIter.First(); pClient; pClient = aClientIter.Next()) + aCallMod.Remove( pClient ); + + getIDocumentState().SetModified(); +} + +/// Get the default attribute in this document +const SfxPoolItem& SwDoc::GetDefault( sal_uInt16 nFormatHint ) const +{ + return GetAttrPool().GetDefaultItem( nFormatHint ); +} + +/// Delete the formats +void SwDoc::DelCharFormat(size_t nFormat, bool bBroadcast) +{ + SwCharFormat * pDel = (*mpCharFormatTable)[nFormat]; + + if (bBroadcast) + BroadcastStyleOperation(pDel->GetName(), SfxStyleFamily::Char, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoCharFormatDelete>(pDel, this)); + } + + delete (*mpCharFormatTable)[nFormat]; + mpCharFormatTable->erase(mpCharFormatTable->begin() + nFormat); + + getIDocumentState().SetModified(); +} + +void SwDoc::DelCharFormat( SwCharFormat const *pFormat, bool bBroadcast ) +{ + size_t nFormat = mpCharFormatTable->GetPos( pFormat ); + OSL_ENSURE( SIZE_MAX != nFormat, "Format not found," ); + DelCharFormat( nFormat, bBroadcast ); +} + +void SwDoc::DelFrameFormat( SwFrameFormat *pFormat, bool bBroadcast ) +{ + if( dynamic_cast<const SwTableBoxFormat*>( pFormat) != nullptr || dynamic_cast<const SwTableLineFormat*>( pFormat) != nullptr ) + { + OSL_ENSURE( false, "Format is not in the DocArray any more, " + "so it can be deleted with delete" ); + delete pFormat; + } + else + { + // The format has to be in the one or the other, we'll see in which one. + if (mpFrameFormatTable->ContainsFormat(*pFormat)) + { + if (bBroadcast) + BroadcastStyleOperation(pFormat->GetName(), + SfxStyleFamily::Frame, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFrameFormatDelete>(pFormat, this)); + } + + mpFrameFormatTable->erase( pFormat ); + delete pFormat; + } + else + { + bool contains = GetSpzFrameFormats()->ContainsFormat(*pFormat); + OSL_ENSURE( contains, "FrameFormat not found." ); + if( contains ) + { + GetSpzFrameFormats()->erase( pFormat ); + delete pFormat; + } + } + } +} + +void SwDoc::DelTableFrameFormat( SwTableFormat *pFormat ) +{ + SwFrameFormats::const_iterator it = mpTableFrameFormatTable->find( pFormat ); + OSL_ENSURE( it != mpTableFrameFormatTable->end(), "Format not found," ); + mpTableFrameFormatTable->erase( it ); + delete pFormat; +} + +SwFrameFormat* SwDoc::FindFrameFormatByName( const OUString& rName ) const +{ + return static_cast<SwFrameFormat*>(FindFormatByName( static_cast<SwFormatsBase&>(*mpFrameFormatTable), rName )); +} + +/// Create the formats +SwFlyFrameFormat *SwDoc::MakeFlyFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwFlyFrameFormat *pFormat = new SwFlyFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + GetSpzFrameFormats()->push_back(pFormat); + getIDocumentState().SetModified(); + return pFormat; +} + +SwDrawFrameFormat *SwDoc::MakeDrawFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwDrawFrameFormat *pFormat = new SwDrawFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom); + GetSpzFrameFormats()->push_back(pFormat); + getIDocumentState().SetModified(); + return pFormat; +} + +size_t SwDoc::GetTableFrameFormatCount(bool bUsed) const +{ + if (!bUsed) + { + return mpTableFrameFormatTable->size(); + } + + SwAutoFormatGetDocNode aGetHt(&GetNodes()); + size_t nCount = 0; + for (SwFrameFormat* const & pFormat : *mpTableFrameFormatTable) + { + if (!pFormat->GetInfo(aGetHt)) + nCount++; + } + return nCount; +} + +SwFrameFormat& SwDoc::GetTableFrameFormat(size_t nFormat, bool bUsed) const +{ + if (!bUsed) + { + return *((*mpTableFrameFormatTable)[nFormat]); + } + + SwAutoFormatGetDocNode aGetHt(&GetNodes()); + + size_t index = 0; + + for (SwFrameFormat* const & pFormat : *mpTableFrameFormatTable) + { + if (!pFormat->GetInfo(aGetHt)) + { + if (index == nFormat) + return *pFormat; + else + index++; + } + } + throw std::out_of_range("Format index out of range."); +} + +SwTableFormat* SwDoc::MakeTableFrameFormat( const OUString &rFormatName, + SwFrameFormat *pDerivedFrom ) +{ + SwTableFormat* pFormat = new SwTableFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + mpTableFrameFormatTable->push_back( pFormat ); + getIDocumentState().SetModified(); + + return pFormat; +} + +SwFrameFormat *SwDoc::MakeFrameFormat(const OUString &rFormatName, + SwFrameFormat *pDerivedFrom, + bool bBroadcast, bool bAuto) +{ + SwFrameFormat *pFormat = new SwFrameFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + + pFormat->SetAuto(bAuto); + mpFrameFormatTable->push_back( pFormat ); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFrameFormatCreate>(pFormat, pDerivedFrom, this)); + } + + if (bBroadcast) + { + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Frame, + SfxHintId::StyleSheetCreated); + } + + return pFormat; +} + +SwFormat *SwDoc::MakeFrameFormat_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool bAuto) +{ + SwFrameFormat *pFrameFormat = dynamic_cast<SwFrameFormat*>(pDerivedFrom); + pFrameFormat = MakeFrameFormat( rFormatName, pFrameFormat, bBroadcast, bAuto ); + return dynamic_cast<SwFormat*>(pFrameFormat); +} + +SwCharFormat *SwDoc::MakeCharFormat( const OUString &rFormatName, + SwCharFormat *pDerivedFrom, + bool bBroadcast ) +{ + SwCharFormat *pFormat = new SwCharFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + mpCharFormatTable->push_back( pFormat ); + pFormat->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoCharFormatCreate>(pFormat, pDerivedFrom, this)); + } + + if (bBroadcast) + { + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Char, + SfxHintId::StyleSheetCreated); + } + + return pFormat; +} + +SwFormat *SwDoc::MakeCharFormat_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool /*bAuto*/) +{ + SwCharFormat *pCharFormat = dynamic_cast<SwCharFormat*>(pDerivedFrom); + pCharFormat = MakeCharFormat( rFormatName, pCharFormat, bBroadcast ); + return dynamic_cast<SwFormat*>(pCharFormat); +} + +/// Create the FormatCollections +SwTextFormatColl* SwDoc::MakeTextFormatColl( const OUString &rFormatName, + SwTextFormatColl *pDerivedFrom, + bool bBroadcast) +{ + SwTextFormatColl *pFormatColl = new SwTextFormatColl( GetAttrPool(), rFormatName, + pDerivedFrom ); + mpTextFormatCollTable->push_back(pFormatColl); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoTextFormatCollCreate>(pFormatColl, pDerivedFrom, + this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Para, + SfxHintId::StyleSheetCreated); + + return pFormatColl; +} + +SwFormat *SwDoc::MakeTextFormatColl_(const OUString &rFormatName, + SwFormat *pDerivedFrom, + bool bBroadcast, bool /*bAuto*/) +{ + SwTextFormatColl *pTextFormatColl = dynamic_cast<SwTextFormatColl*>(pDerivedFrom); + pTextFormatColl = MakeTextFormatColl( rFormatName, pTextFormatColl, bBroadcast ); + return dynamic_cast<SwFormat*>(pTextFormatColl); +} + +//FEATURE::CONDCOLL +SwConditionTextFormatColl* SwDoc::MakeCondTextFormatColl( const OUString &rFormatName, + SwTextFormatColl *pDerivedFrom, + bool bBroadcast) +{ + SwConditionTextFormatColl*pFormatColl = new SwConditionTextFormatColl( GetAttrPool(), + rFormatName, pDerivedFrom ); + mpTextFormatCollTable->push_back(pFormatColl); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoCondTextFormatCollCreate>(pFormatColl, pDerivedFrom, + this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rFormatName, SfxStyleFamily::Para, + SfxHintId::StyleSheetCreated); + + return pFormatColl; +} +//FEATURE::CONDCOLL + +// GRF +SwGrfFormatColl* SwDoc::MakeGrfFormatColl( const OUString &rFormatName, + SwGrfFormatColl *pDerivedFrom ) +{ + SwGrfFormatColl *pFormatColl = new SwGrfFormatColl( GetAttrPool(), rFormatName, + pDerivedFrom ); + mpGrfFormatCollTable->push_back( pFormatColl ); + pFormatColl->SetAuto(false); + getIDocumentState().SetModified(); + return pFormatColl; +} + +void SwDoc::DelTextFormatColl(size_t nFormatColl, bool bBroadcast) +{ + OSL_ENSURE( nFormatColl, "Remove of Coll 0." ); + + // Who has the to-be-deleted as their Next? + SwTextFormatColl *pDel = (*mpTextFormatCollTable)[nFormatColl]; + if( mpDfltTextFormatColl.get() == pDel ) + return; // never delete default! + + if (bBroadcast) + BroadcastStyleOperation(pDel->GetName(), SfxStyleFamily::Para, + SfxHintId::StyleSheetErased); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoTextFormatCollDelete> pUndo; + if (RES_CONDTXTFMTCOLL == pDel->Which()) + { + pUndo.reset(new SwUndoCondTextFormatCollDelete(pDel, this)); + } + else + { + pUndo.reset(new SwUndoTextFormatCollDelete(pDel, this)); + } + + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + // Remove the FormatColl + mpTextFormatCollTable->erase(mpTextFormatCollTable->begin() + nFormatColl); + // Correct next + for( SwTextFormatColls::const_iterator it = mpTextFormatCollTable->begin() + 1; it != mpTextFormatCollTable->end(); ++it ) + SetTextFormatCollNext( *it, pDel ); + delete pDel; + getIDocumentState().SetModified(); +} + +void SwDoc::DelTextFormatColl( SwTextFormatColl const *pColl, bool bBroadcast ) +{ + size_t nFormat = mpTextFormatCollTable->GetPos( pColl ); + OSL_ENSURE( SIZE_MAX != nFormat, "Collection not found," ); + DelTextFormatColl( nFormat, bBroadcast ); +} + +static bool lcl_SetTextFormatColl( const SwNodePtr& rpNode, void* pArgs ) +{ + SwContentNode* pCNd = rpNode->GetTextNode(); + + if( pCNd == nullptr) + return true; + + sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast<sw::DocumentContentOperationsManager::ParaRstFormat*>(pArgs); + + if (pPara->pLayout && pPara->pLayout->IsHideRedlines()) + { + if (pCNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if (pCNd->IsTextNode()) + { + pCNd = sw::GetParaPropsNode(*pPara->pLayout, SwNodeIndex(*pCNd)); + } + } + + SwTextFormatColl* pFormat = static_cast<SwTextFormatColl*>(pPara->pFormatColl); + if ( pPara->bReset ) + { + lcl_RstAttr(pCNd, pPara); + + // #i62675# check, if paragraph style has changed + if ( pPara->bResetListAttrs && + pFormat != pCNd->GetFormatColl() && + pFormat->GetItemState( RES_PARATR_NUMRULE ) == SfxItemState::SET ) + { + // Check, if the list style of the paragraph will change. + bool bChangeOfListStyleAtParagraph( true ); + SwTextNode& rTNd(dynamic_cast<SwTextNode&>(*pCNd)); + { + SwNumRule* pNumRuleAtParagraph(rTNd.GetNumRule()); + if ( pNumRuleAtParagraph ) + { + const SwNumRuleItem& rNumRuleItemAtParagraphStyle = + pFormat->GetNumRule(); + if ( rNumRuleItemAtParagraphStyle.GetValue() == + pNumRuleAtParagraph->GetName() ) + { + bChangeOfListStyleAtParagraph = false; + } + } + } + + if ( bChangeOfListStyleAtParagraph ) + { + std::unique_ptr< SwRegHistory > pRegH; + if ( pPara->pHistory ) + { + pRegH.reset(new SwRegHistory(&rTNd, rTNd, pPara->pHistory)); + } + + pCNd->ResetAttr( RES_PARATR_NUMRULE ); + + // reset all list attributes + pCNd->ResetAttr( RES_PARATR_LIST_LEVEL ); + pCNd->ResetAttr( RES_PARATR_LIST_ISRESTART ); + pCNd->ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + pCNd->ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + pCNd->ResetAttr( RES_PARATR_LIST_ID ); + } + } + } + + // add to History so that old data is saved, if necessary + if( pPara->pHistory ) + pPara->pHistory->Add( pCNd->GetFormatColl(), pCNd->GetIndex(), + SwNodeType::Text ); + + pCNd->ChgFormatColl( pFormat ); + + pPara->nWhich++; + + return true; +} + +bool SwDoc::SetTextFormatColl(const SwPaM &rRg, + SwTextFormatColl *pFormat, + const bool bReset, + const bool bResetListAttrs, + SwRootFrame const*const pLayout) +{ + SwDataChanged aTmp( rRg ); + const SwPosition *pStt = rRg.Start(), *pEnd = rRg.End(); + SwHistory* pHst = nullptr; + bool bRet = true; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoFormatColl> pUndo(new SwUndoFormatColl( rRg, pFormat, + bReset, + bResetListAttrs )); + pHst = pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout); + aPara.pFormatColl = pFormat; + aPara.bReset = bReset; + // #i62675# + aPara.bResetListAttrs = bResetListAttrs; + + GetNodes().ForEach( pStt->nNode.GetIndex(), pEnd->nNode.GetIndex()+1, + lcl_SetTextFormatColl, &aPara ); + if( !aPara.nWhich ) + bRet = false; // didn't find a valid Node + + if (bRet) + { + getIDocumentState().SetModified(); + } + + return bRet; +} + +/// Copy the formats to itself +SwFormat* SwDoc::CopyFormat( const SwFormat& rFormat, + const SwFormatsBase& rFormatArr, + FNCopyFormat fnCopyFormat, const SwFormat& rDfltFormat ) +{ + // It's no autoformat, default format or collection format, + // then search for it. + if( !rFormat.IsAuto() || !rFormat.GetRegisteredIn() ) + for( size_t n = 0; n < rFormatArr.GetFormatCount(); ++n ) + { + // Does the Doc already contain the template? + if( rFormatArr.GetFormat(n)->GetName()==rFormat.GetName() ) + return rFormatArr.GetFormat(n); + } + + // Search for the "parent" first + SwFormat* pParent = const_cast<SwFormat*>(&rDfltFormat); + if( rFormat.DerivedFrom() && pParent != rFormat.DerivedFrom() ) + pParent = CopyFormat( *rFormat.DerivedFrom(), rFormatArr, + fnCopyFormat, rDfltFormat ); + + // Create the format and copy the attributes + // #i40550# + SwFormat* pNewFormat = (this->*fnCopyFormat)( rFormat.GetName(), pParent, false, true ); + pNewFormat->SetAuto( rFormat.IsAuto() ); + pNewFormat->CopyAttrs( rFormat ); // copy the attributes + + pNewFormat->SetPoolFormatId( rFormat.GetPoolFormatId() ); + pNewFormat->SetPoolHelpId( rFormat.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewFormat->SetPoolHlpFileId( UCHAR_MAX ); + + return pNewFormat; +} + +/// copy the frame format +SwFrameFormat* SwDoc::CopyFrameFormat( const SwFrameFormat& rFormat ) +{ + return static_cast<SwFrameFormat*>(CopyFormat( rFormat, *GetFrameFormats(), &SwDoc::MakeFrameFormat_, + *GetDfltFrameFormat() )); +} + +/// copy the char format +SwCharFormat* SwDoc::CopyCharFormat( const SwCharFormat& rFormat ) +{ + return static_cast<SwCharFormat*>(CopyFormat( rFormat, *GetCharFormats(), + &SwDoc::MakeCharFormat_, + *GetDfltCharFormat() )); +} + +/// copy TextNodes +SwTextFormatColl* SwDoc::CopyTextColl( const SwTextFormatColl& rColl ) +{ + SwTextFormatColl* pNewColl = FindTextFormatCollByName( rColl.GetName() ); + if( pNewColl ) + return pNewColl; + + // search for the "parent" first + SwTextFormatColl* pParent = mpDfltTextFormatColl.get(); + if( pParent != rColl.DerivedFrom() ) + pParent = CopyTextColl( *static_cast<SwTextFormatColl*>(rColl.DerivedFrom()) ); + +//FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == rColl.Which() ) + { + pNewColl = new SwConditionTextFormatColl( GetAttrPool(), rColl.GetName(), + pParent); + mpTextFormatCollTable->push_back( pNewColl ); + pNewColl->SetAuto(false); + getIDocumentState().SetModified(); + + // copy the conditions + static_cast<SwConditionTextFormatColl*>(pNewColl)->SetConditions( + static_cast<const SwConditionTextFormatColl&>(rColl).GetCondColls() ); + } + else +//FEATURE::CONDCOLL + pNewColl = MakeTextFormatColl( rColl.GetName(), pParent ); + + // copy the auto formats or the attributes + pNewColl->CopyAttrs( rColl ); + + if(rColl.IsAssignedToListLevelOfOutlineStyle()) + pNewColl->AssignToListLevelOfOutlineStyle(rColl.GetAssignedOutlineStyleLevel()); + pNewColl->SetPoolFormatId( rColl.GetPoolFormatId() ); + pNewColl->SetPoolHelpId( rColl.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewColl->SetPoolHlpFileId( UCHAR_MAX ); + + if( &rColl.GetNextTextFormatColl() != &rColl ) + pNewColl->SetNextTextFormatColl( *CopyTextColl( rColl.GetNextTextFormatColl() )); + + // create the NumRule if necessary + if( this != rColl.GetDoc() ) + { + const SfxPoolItem* pItem; + if( SfxItemState::SET == pNewColl->GetItemState( RES_PARATR_NUMRULE, + false, &pItem )) + { + const OUString& rName = static_cast<const SwNumRuleItem*>(pItem)->GetValue(); + if( !rName.isEmpty() ) + { + const SwNumRule* pRule = rColl.GetDoc()->FindNumRulePtr( rName ); + if( pRule && !pRule->IsAutoRule() ) + { + SwNumRule* pDestRule = FindNumRulePtr( rName ); + if( pDestRule ) + pDestRule->SetInvalidRule( true ); + else + MakeNumRule( rName, pRule ); + } + } + } + } + return pNewColl; +} + +/// copy the graphic nodes +SwGrfFormatColl* SwDoc::CopyGrfColl( const SwGrfFormatColl& rColl ) +{ + SwGrfFormatColl* pNewColl = static_cast<SwGrfFormatColl*>(FindFormatByName( static_cast<SwFormatsBase const &>(*mpGrfFormatCollTable), rColl.GetName() )); + if( pNewColl ) + return pNewColl; + + // Search for the "parent" first + SwGrfFormatColl* pParent = mpDfltGrfFormatColl.get(); + if( pParent != rColl.DerivedFrom() ) + pParent = CopyGrfColl( *static_cast<SwGrfFormatColl*>(rColl.DerivedFrom()) ); + + // if not, copy them + pNewColl = MakeGrfFormatColl( rColl.GetName(), pParent ); + + // copy the attributes + pNewColl->CopyAttrs( rColl ); + + pNewColl->SetPoolFormatId( rColl.GetPoolFormatId() ); + pNewColl->SetPoolHelpId( rColl.GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pNewColl->SetPoolHlpFileId( UCHAR_MAX ); + + return pNewColl; +} + +void SwDoc::CopyFormatArr( const SwFormatsBase& rSourceArr, + SwFormatsBase const & rDestArr, + FNCopyFormat fnCopyFormat, + SwFormat& rDfltFormat ) +{ + SwFormat* pSrc, *pDest; + + // 1st step: Create all formats (skip the 0th - it's the default one) + for( size_t nSrc = rSourceArr.GetFormatCount(); nSrc > 1; ) + { + pSrc = rSourceArr.GetFormat( --nSrc ); + if( pSrc->IsDefault() || pSrc->IsAuto() ) + continue; + + if( nullptr == FindFormatByName( rDestArr, pSrc->GetName() ) ) + { + if( RES_CONDTXTFMTCOLL == pSrc->Which() ) + MakeCondTextFormatColl( pSrc->GetName(), static_cast<SwTextFormatColl*>(&rDfltFormat) ); + else + // #i40550# + (this->*fnCopyFormat)( pSrc->GetName(), &rDfltFormat, false, true ); + } + } + + // 2nd step: Copy all attributes, set the right parents + for( size_t nSrc = rSourceArr.GetFormatCount(); nSrc > 1; ) + { + pSrc = rSourceArr.GetFormat( --nSrc ); + if( pSrc->IsDefault() || pSrc->IsAuto() ) + continue; + + pDest = FindFormatByName( rDestArr, pSrc->GetName() ); + pDest->SetAuto(false); + pDest->DelDiffs( *pSrc ); + + // #i94285#: existing <SwFormatPageDesc> instance, before copying attributes + const SfxPoolItem* pItem; + if( &GetAttrPool() != pSrc->GetAttrSet().GetPool() && + SfxItemState::SET == pSrc->GetAttrSet().GetItemState( + RES_PAGEDESC, false, &pItem ) && + static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc() ) + { + SwFormatPageDesc aPageDesc( *static_cast<const SwFormatPageDesc*>(pItem) ); + const OUString& rNm = aPageDesc.GetPageDesc()->GetName(); + SwPageDesc* pPageDesc = FindPageDesc( rNm ); + if( !pPageDesc ) + { + pPageDesc = MakePageDesc(rNm); + } + aPageDesc.RegisterToPageDesc( *pPageDesc ); + SwAttrSet aTmpAttrSet( pSrc->GetAttrSet() ); + aTmpAttrSet.Put( aPageDesc ); + pDest->SetFormatAttr( aTmpAttrSet ); + } + else + { + pDest->SetFormatAttr( pSrc->GetAttrSet() ); + } + + pDest->SetPoolFormatId( pSrc->GetPoolFormatId() ); + pDest->SetPoolHelpId( pSrc->GetPoolHelpId() ); + + // Always set the HelpFile Id to default! + pDest->SetPoolHlpFileId( UCHAR_MAX ); + + if( pSrc->DerivedFrom() ) + pDest->SetDerivedFrom( FindFormatByName( rDestArr, + pSrc->DerivedFrom()->GetName() ) ); + if( RES_TXTFMTCOLL == pSrc->Which() || + RES_CONDTXTFMTCOLL == pSrc->Which() ) + { + SwTextFormatColl* pSrcColl = static_cast<SwTextFormatColl*>(pSrc), + * pDstColl = static_cast<SwTextFormatColl*>(pDest); + if( &pSrcColl->GetNextTextFormatColl() != pSrcColl ) + pDstColl->SetNextTextFormatColl( *static_cast<SwTextFormatColl*>(FindFormatByName( + rDestArr, pSrcColl->GetNextTextFormatColl().GetName() ) ) ); + + if(pSrcColl->IsAssignedToListLevelOfOutlineStyle()) + pDstColl->AssignToListLevelOfOutlineStyle(pSrcColl->GetAssignedOutlineStyleLevel()); + +//FEATURE::CONDCOLL + if( RES_CONDTXTFMTCOLL == pSrc->Which() ) + // Copy the conditions, but delete the old ones first! + static_cast<SwConditionTextFormatColl*>(pDstColl)->SetConditions( + static_cast<SwConditionTextFormatColl*>(pSrc)->GetCondColls() ); +//FEATURE::CONDCOLL + } + } +} + +void SwDoc::CopyPageDescHeaderFooterImpl( bool bCpyHeader, + const SwFrameFormat& rSrcFormat, SwFrameFormat& rDestFormat ) +{ + // Treat the header and footer attributes in the right way: + // Copy content nodes across documents! + sal_uInt16 nAttr = bCpyHeader ? sal_uInt16(RES_HEADER) : sal_uInt16(RES_FOOTER); + const SfxPoolItem* pItem; + if( SfxItemState::SET != rSrcFormat.GetAttrSet().GetItemState( nAttr, false, &pItem )) + return ; + + // The header only contains the reference to the format from the other document! + std::unique_ptr<SfxPoolItem> pNewItem(pItem->Clone()); + + SwFrameFormat* pOldFormat; + if( bCpyHeader ) + pOldFormat = static_cast<SwFormatHeader*>(pNewItem.get())->GetHeaderFormat(); + else + pOldFormat = static_cast<SwFormatFooter*>(pNewItem.get())->GetFooterFormat(); + + if( pOldFormat ) + { + SwFrameFormat* pNewFormat = new SwFrameFormat( GetAttrPool(), "CpyDesc", + GetDfltFrameFormat() ); + pNewFormat->CopyAttrs( *pOldFormat ); + + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState( + RES_CNTNT, false, &pItem )) + { + const SwFormatContent* pContent = static_cast<const SwFormatContent*>(pItem); + if( pContent->GetContentIdx() ) + { + SwNodeIndex aTmpIdx( GetNodes().GetEndOfAutotext() ); + const SwNodes& rSrcNds = rSrcFormat.GetDoc()->GetNodes(); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aTmpIdx, + bCpyHeader + ? SwHeaderStartNode + : SwFooterStartNode ); + const SwNode& rCSttNd = pContent->GetContentIdx()->GetNode(); + SwNodeRange aRg( rCSttNd, 0, *rCSttNd.EndOfSectionNode() ); + aTmpIdx = *pSttNd->EndOfSectionNode(); + rSrcNds.Copy_( aRg, aTmpIdx ); + aTmpIdx = *pSttNd; + rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aTmpIdx); + // TODO: investigate calling CopyWithFlyInFly? + SwPaM const source(aRg.aStart, aRg.aEnd); + SwPosition dest(aTmpIdx); + sw::CopyBookmarks(source, dest); + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + } + else + pNewFormat->ResetFormatAttr( RES_CNTNT ); + } + if( bCpyHeader ) + static_cast<SwFormatHeader*>(pNewItem.get())->RegisterToFormat(*pNewFormat); + else + static_cast<SwFormatFooter*>(pNewItem.get())->RegisterToFormat(*pNewFormat); + rDestFormat.SetFormatAttr( *pNewItem ); + } +} + +void SwDoc::CopyPageDesc( const SwPageDesc& rSrcDesc, SwPageDesc& rDstDesc, + bool bCopyPoolIds ) +{ + bool bNotifyLayout = false; + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + rDstDesc.SetLandscape( rSrcDesc.GetLandscape() ); + rDstDesc.SetNumType( rSrcDesc.GetNumType() ); + if( rDstDesc.ReadUseOn() != rSrcDesc.ReadUseOn() ) + { + rDstDesc.WriteUseOn( rSrcDesc.ReadUseOn() ); + bNotifyLayout = true; + } + + if( bCopyPoolIds ) + { + rDstDesc.SetPoolFormatId( rSrcDesc.GetPoolFormatId() ); + rDstDesc.SetPoolHelpId( rSrcDesc.GetPoolHelpId() ); + // Always set the HelpFile Id to default! + rDstDesc.SetPoolHlpFileId( UCHAR_MAX ); + } + + if( rSrcDesc.GetFollow() != &rSrcDesc ) + { + const SwPageDesc* pSrcFollow = rSrcDesc.GetFollow(); + SwPageDesc* pFollow = FindPageDesc( pSrcFollow->GetName() ); + if( !pFollow ) + { + // copy + pFollow = MakePageDesc( pSrcFollow->GetName() ); + CopyPageDesc( *pSrcFollow, *pFollow ); + } + rDstDesc.SetFollow( pFollow ); + bNotifyLayout = true; + } + + // the header and footer attributes are copied separately + // the content sections have to be copied in their entirety + { + SfxItemSet aAttrSet( rSrcDesc.GetMaster().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetMaster().DelDiffs( aAttrSet ); + rDstDesc.GetMaster().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetLeft().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetLeft().DelDiffs( aAttrSet ); + rDstDesc.GetLeft().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetFirstMaster().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetFirstMaster().DelDiffs( aAttrSet ); + rDstDesc.GetFirstMaster().SetFormatAttr( aAttrSet ); + + aAttrSet.ClearItem(); + aAttrSet.Put( rSrcDesc.GetFirstLeft().GetAttrSet() ); + aAttrSet.ClearItem( RES_HEADER ); + aAttrSet.ClearItem( RES_FOOTER ); + + rDstDesc.GetFirstLeft().DelDiffs( aAttrSet ); + rDstDesc.GetFirstLeft().SetFormatAttr( aAttrSet ); + } + + CopyHeader( rSrcDesc.GetMaster(), rDstDesc.GetMaster() ); + CopyFooter( rSrcDesc.GetMaster(), rDstDesc.GetMaster() ); + if( !rDstDesc.IsHeaderShared() ) + CopyHeader( rSrcDesc.GetLeft(), rDstDesc.GetLeft() ); + else + rDstDesc.GetLeft().SetFormatAttr( rDstDesc.GetMaster().GetHeader() ); + if( !rDstDesc.IsFirstShared() ) + { + CopyHeader( rSrcDesc.GetFirstMaster(), rDstDesc.GetFirstMaster() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetFirstMaster().GetHeader()); + } + else + { + rDstDesc.GetFirstMaster().SetFormatAttr( rDstDesc.GetMaster().GetHeader() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetLeft().GetHeader()); + } + + if( !rDstDesc.IsFooterShared() ) + CopyFooter( rSrcDesc.GetLeft(), rDstDesc.GetLeft() ); + else + rDstDesc.GetLeft().SetFormatAttr( rDstDesc.GetMaster().GetFooter() ); + if( !rDstDesc.IsFirstShared() ) + { + CopyFooter( rSrcDesc.GetFirstMaster(), rDstDesc.GetFirstMaster() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetFirstMaster().GetFooter()); + } + else + { + rDstDesc.GetFirstMaster().SetFormatAttr( rDstDesc.GetMaster().GetFooter() ); + rDstDesc.GetFirstLeft().SetFormatAttr(rDstDesc.GetLeft().GetFooter()); + } + + if( bNotifyLayout && pTmpRoot ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->AllCheckPageDescs(); + } + + // If foot notes change the pages have to be triggered + if( !(rDstDesc.GetFootnoteInfo() == rSrcDesc.GetFootnoteInfo()) ) + { + sw::PageFootnoteHint aHint; + rDstDesc.SetFootnoteInfo( rSrcDesc.GetFootnoteInfo() ); + rDstDesc.GetMaster().CallSwClientNotify(aHint); + rDstDesc.GetLeft().CallSwClientNotify(aHint); + rDstDesc.GetFirstMaster().CallSwClientNotify(aHint); + rDstDesc.GetFirstLeft().CallSwClientNotify(aHint); + } +} + +void SwDoc::ReplaceStyles( const SwDoc& rSource, bool bIncludePageStyles ) +{ + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + CopyFormatArr( *rSource.mpCharFormatTable, *mpCharFormatTable, + &SwDoc::MakeCharFormat_, *mpDfltCharFormat ); + CopyFormatArr( *rSource.mpFrameFormatTable, *mpFrameFormatTable, + &SwDoc::MakeFrameFormat_, *mpDfltFrameFormat ); + CopyFormatArr( *rSource.mpTextFormatCollTable, *mpTextFormatCollTable, + &SwDoc::MakeTextFormatColl_, *mpDfltTextFormatColl ); + + //To-Do: + // a) in rtf export don't export our hideous pgdsctbl + // extension to rtf anymore + // b) in sd rtf import (View::InsertData) don't use + // a super-fragile test for mere presence of \trowd to + // indicate import of rtf into a table + // c) then drop use of bIncludePageStyles + if (bIncludePageStyles) + { + // and now the page templates + SwPageDescs::size_type nCnt = rSource.m_PageDescs.size(); + if( nCnt ) + { + // a different Doc -> Number formatter needs to be merged + SwTableNumFormatMerge aTNFM( rSource, *this ); + + // 1st step: Create all formats (skip the 0th - it's the default!) + while( nCnt ) + { + const SwPageDesc &rSrc = *rSource.m_PageDescs[ --nCnt ]; + if( nullptr == FindPageDesc( rSrc.GetName() ) ) + MakePageDesc( rSrc.GetName() ); + } + + // 2nd step: Copy all attributes, set the right parents + for (SwPageDescs::size_type i = rSource.m_PageDescs.size(); i; ) + { + const SwPageDesc &rSrc = *rSource.m_PageDescs[ --i ]; + SwPageDesc* pDesc = FindPageDesc( rSrc.GetName() ); + CopyPageDesc( rSrc, *pDesc); + } + } + } + + // then there are the numbering templates + const SwNumRuleTable::size_type nCnt = rSource.GetNumRuleTable().size(); + if( nCnt ) + { + const SwNumRuleTable& rArr = rSource.GetNumRuleTable(); + for( SwNumRuleTable::size_type n = 0; n < nCnt; ++n ) + { + const SwNumRule& rR = *rArr[ n ]; + SwNumRule* pNew = FindNumRulePtr( rR.GetName()); + if( pNew ) + pNew->CopyNumRule( this, rR ); + else + { + if( !rR.IsAutoRule() ) + MakeNumRule( rR.GetName(), &rR ); + else + { + // as we reset all styles, there shouldn't be any unknown + // automatic SwNumRules, because all should have been + // created by the style copying! + // So just warn and ignore. + SAL_WARN( "sw.core", "Found unknown auto SwNumRule during reset!" ); + } + } + } + } + + if (undoGuard.UndoWasEnabled()) + { + // nodes array was modified! + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + getIDocumentState().SetModified(); +} + +SwFormat* SwDoc::FindFormatByName( const SwFormatsBase& rFormatArr, + const OUString& rName ) +{ + SwFormat* pFnd = nullptr; + for( size_t n = 0; n < rFormatArr.GetFormatCount(); ++n ) + { + // Does the Doc already contain the template? + if( rFormatArr.GetFormat(n)->HasName( rName ) ) + { + pFnd = rFormatArr.GetFormat(n); + break; + } + } + return pFnd; +} + +void SwDoc::MoveLeftMargin(const SwPaM& rPam, bool bRight, bool bModulus, + SwRootFrame const*const pLayout) +{ + SwHistory* pHistory = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoMoveLeftMargin> pUndo(new SwUndoMoveLeftMargin( rPam, bRight, + bModulus )); + pHistory = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + + const SvxTabStopItem& rTabItem = GetDefault( RES_PARATR_TABSTOP ); + const sal_Int32 nDefDist = rTabItem.Count() ? rTabItem[0].GetTabPos() : 1134; + const SwPosition &rStt = *rPam.Start(), &rEnd = *rPam.End(); + SwNodeIndex aIdx( rStt.nNode ); + while( aIdx <= rEnd.nNode ) + { + SwTextNode* pTNd = aIdx.GetNode().GetTextNode(); + if( pTNd ) + { + pTNd = sw::GetParaPropsNode(*pLayout, aIdx); + SvxLRSpaceItem aLS( static_cast<const SvxLRSpaceItem&>(pTNd->SwContentNode::GetAttr( RES_LR_SPACE )) ); + + // #i93873# See also lcl_MergeListLevelIndentAsLRSpaceItem in thints.cxx + if ( pTNd->AreListLevelIndentsApplicable() ) + { + const SwNumRule* pRule = pTNd->GetNumRule(); + if ( pRule ) + { + const int nListLevel = pTNd->GetActualListLevel(); + if ( nListLevel >= 0 ) + { + const SwNumFormat& rFormat = pRule->Get(static_cast<sal_uInt16>(nListLevel)); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aLS.SetTextLeft( rFormat.GetIndentAt() ); + aLS.SetTextFirstLineOffset( static_cast<short>(rFormat.GetFirstLineIndent()) ); + } + } + } + } + + long nNext = aLS.GetTextLeft(); + if( bModulus ) + nNext = ( nNext / nDefDist ) * nDefDist; + + if( bRight ) + nNext += nDefDist; + else + if(nNext >0) // fdo#75936 set limit for decreasing indent + nNext -= nDefDist; + + aLS.SetTextLeft( nNext ); + + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + pTNd->SetAttr( aLS ); + aIdx = *sw::GetFirstAndLastNode(*pLayout, aIdx).second; + } + ++aIdx; + } + getIDocumentState().SetModified(); +} + +bool SwDoc::DontExpandFormat( const SwPosition& rPos, bool bFlag ) +{ + bool bRet = false; + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( pTextNd ) + { + bRet = pTextNd->DontExpandFormat( rPos.nContent, bFlag ); + if( bRet && GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDontExpandFormat>(rPos) ); + } + } + return bRet; +} + +SwTableBoxFormat* SwDoc::MakeTableBoxFormat() +{ + SwTableBoxFormat* pFormat = new SwTableBoxFormat( GetAttrPool(), mpDfltFrameFormat.get() ); + pFormat->SetName("TableBox" + OUString::number(reinterpret_cast<sal_IntPtr>(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +SwTableLineFormat* SwDoc::MakeTableLineFormat() +{ + SwTableLineFormat* pFormat = new SwTableLineFormat( GetAttrPool(), mpDfltFrameFormat.get() ); + pFormat->SetName("TableLine" + OUString::number(reinterpret_cast<sal_IntPtr>(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +void SwDoc::EnsureNumberFormatter() +{ + comphelper::doubleCheckedInit(mpNumberFormatter, []() + { + LanguageType eLang = LANGUAGE_SYSTEM; + SvNumberFormatter* pRet = new SvNumberFormatter(comphelper::getProcessComponentContext(), eLang); + pRet->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL ); + if (!utl::ConfigManager::IsFuzzing()) + pRet->SetYear2000(static_cast<sal_uInt16>(::utl::MiscCfg().GetYear2000())); + return pRet; + }); +} + +SwTableNumFormatMerge::SwTableNumFormatMerge( const SwDoc& rSrc, SwDoc& rDest ) + : pNFormat( nullptr ) +{ + // a different Doc -> Number formatter needs to be merged + if( &rSrc != &rDest ) + { + SvNumberFormatter* pN = const_cast<SwDoc&>(rSrc).GetNumberFormatter( false ); + if( pN ) + { + pNFormat = rDest.GetNumberFormatter(); + pNFormat->MergeFormatter( *pN ); + } + } + + if( &rSrc != &rDest ) + static_cast<SwGetRefFieldType*>(rSrc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef ))-> + MergeWithOtherDoc( rDest ); +} + +SwTableNumFormatMerge::~SwTableNumFormatMerge() +{ + if( pNFormat ) + pNFormat->ClearMergeTable(); +} + +void SwDoc::SetTextFormatCollByAutoFormat( const SwPosition& rPos, sal_uInt16 nPoolId, + const SfxItemSet* pSet ) +{ + SwPaM aPam( rPos ); + SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + assert(pTNd); + + if (mbIsAutoFormatRedline) + { + // create the redline object + const SwTextFormatColl& rColl = *pTNd->GetTextColl(); + SwRangeRedline* pRedl = new SwRangeRedline( RedlineType::FmtColl, aPam ); + pRedl->SetMark(); + + // Only those items that are not set by the Set again in the Node + // are of interest. Thus, we take the difference. + SwRedlineExtraData_FormatColl aExtraData( rColl.GetName(), + rColl.GetPoolFormatId() ); + if( pSet && pTNd->HasSwAttrSet() ) + { + SfxItemSet aTmp( *pTNd->GetpSwAttrSet() ); + aTmp.Differentiate( *pSet ); + // we handle the adjust item separately + const SfxPoolItem* pItem; + if( SfxItemState::SET == pTNd->GetpSwAttrSet()->GetItemState( + RES_PARATR_ADJUST, false, &pItem )) + aTmp.Put( *pItem ); + aExtraData.SetItemSet( aTmp ); + } + pRedl->SetExtraData( &aExtraData ); + + //TODO: Undo is still missing! + getIDocumentRedlineAccess().AppendRedline( pRedl, true ); + } + + SetTextFormatColl( aPam, getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId ) ); + + if (pSet && pSet->Count()) + { + aPam.SetMark(); + aPam.GetMark()->nContent.Assign(pTNd, pTNd->GetText().getLength()); + // sw_redlinehide: don't need layout currently because the only caller + // passes in the properties node + assert(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(nullptr))->GetTextNodeForParaProps() == pTNd); + getIDocumentContentOperations().InsertItemSet( aPam, *pSet ); + } +} + +void SwDoc::SetFormatItemByAutoFormat( const SwPaM& rPam, const SfxItemSet& rSet ) +{ + SwTextNode* pTNd = rPam.GetPoint()->nNode.GetNode().GetTextNode(); + assert(pTNd); + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + + if (mbIsAutoFormatRedline) + { + // create the redline object + SwRangeRedline* pRedl = new SwRangeRedline( RedlineType::Format, rPam ); + if( !pRedl->HasMark() ) + pRedl->SetMark(); + + // Only those items that are not set by the Set again in the Node + // are of interest. Thus, we take the difference. + SwRedlineExtraData_Format aExtraData( rSet ); + + pRedl->SetExtraData( &aExtraData ); + + //TODO: Undo is still missing! + getIDocumentRedlineAccess().AppendRedline( pRedl, true ); + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + } + + const sal_Int32 nEnd(rPam.End()->nContent.GetIndex()); + std::vector<sal_uInt16> whichIds; + SfxItemIter iter(rSet); + for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem()) + { + whichIds.push_back(pItem->Which()); + whichIds.push_back(pItem->Which()); + } + whichIds.push_back(0); + SfxItemSet currentSet(GetAttrPool(), whichIds.data()); + pTNd->GetParaAttr(currentSet, nEnd, nEnd); + for (size_t i = 0; whichIds[i]; i += 2) + { // yuk - want to explicitly set the pool defaults too :-/ + currentSet.Put(currentSet.Get(whichIds[i])); + } + + getIDocumentContentOperations().InsertItemSet( rPam, rSet, SetAttrMode::DONTEXPAND ); + + // fdo#62536: DONTEXPAND does not work when there is already an AUTOFMT + // here, so insert the old attributes as an empty hint to stop expand + SwPaM endPam(*pTNd, nEnd); + endPam.SetMark(); + getIDocumentContentOperations().InsertItemSet(endPam, currentSet); + + getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwDoc::ChgFormat(SwFormat & rFormat, const SfxItemSet & rSet) +{ + if (GetIDocumentUndoRedo().DoesUndo()) + { + // copying <rSet> to <aSet> + SfxItemSet aSet(rSet); + // remove from <aSet> all items, which are already set at the format + aSet.Differentiate(rFormat.GetAttrSet()); + // <aSet> contains now all *new* items for the format + + // copying current format item set to <aOldSet> + SfxItemSet aOldSet(rFormat.GetAttrSet()); + // insert new items into <aOldSet> + aOldSet.Put(aSet); + // invalidate all new items in <aOldSet> in order to clear these items, + // if the undo action is triggered. + { + SfxItemIter aIter(aSet); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + aOldSet.InvalidateItem(pItem->Which()); + } + } + + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFormatAttr>(aOldSet, rFormat, /*bSaveDrawPt*/true)); + } + + rFormat.SetFormatAttr(rSet); +} + +void SwDoc::RenameFormat(SwFormat & rFormat, const OUString & sNewName, + bool bBroadcast) +{ + SfxStyleFamily eFamily = SfxStyleFamily::All; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndo> pUndo; + + switch (rFormat.Which()) + { + case RES_CHRFMT: + pUndo.reset(new SwUndoRenameCharFormat(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Char; + break; + case RES_TXTFMTCOLL: + pUndo.reset(new SwUndoRenameFormatColl(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Para; + break; + case RES_FRMFMT: + pUndo.reset(new SwUndoRenameFrameFormat(rFormat.GetName(), sNewName, this)); + eFamily = SfxStyleFamily::Frame; + break; + + default: + break; + } + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + } + + rFormat.SetName(sNewName); + + if (bBroadcast) + BroadcastStyleOperation(sNewName, eFamily, SfxHintId::StyleSheetModified); +} + +void SwDoc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("nodes.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + xmlTextWriterStartElement(pWriter, BAD_CAST("SwDoc")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + m_pNodes->dumpAsXml(pWriter); + m_PageDescs.dumpAsXml(pWriter); + maDBData.dumpAsXml(pWriter); + mpMarkManager->dumpAsXml(pWriter); + m_pUndoManager->dumpAsXml(pWriter); + m_pDocumentSettingManager->dumpAsXml(pWriter); + getIDocumentFieldsAccess().GetFieldTypes()->dumpAsXml(pWriter); + mpTextFormatCollTable->dumpAsXml(pWriter); + mpCharFormatTable->dumpAsXml(pWriter); + mpFrameFormatTable->dumpAsXml(pWriter, "frmFormatTable"); + mpSpzFrameFormatTable->dumpAsXml(pWriter, "spzFrameFormatTable"); + mpSectionFormatTable->dumpAsXml(pWriter); + mpTableFrameFormatTable->dumpAsXml(pWriter, "tableFrameFormatTable"); + mpNumRuleTable->dumpAsXml(pWriter); + getIDocumentRedlineAccess().GetRedlineTable().dumpAsXml(pWriter); + getIDocumentRedlineAccess().GetExtraRedlineTable().dumpAsXml(pWriter); + if (const SdrModel* pModel = getIDocumentDrawModelAccess().GetDrawModel()) + pModel->dumpAsXml(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("mbModified")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(getIDocumentState().IsModified()).getStr())); + xmlTextWriterEndElement(pWriter); + + xmlTextWriterEndElement(pWriter); + if (bOwns) + { + xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +void SwDBData::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwDBData")); + + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sDataSource"), BAD_CAST(sDataSource.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sCommand"), BAD_CAST(sCommand.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nCommandType"), BAD_CAST(OString::number(nCommandType).getStr())); + + xmlTextWriterEndElement(pWriter); +} + +std::set<Color> SwDoc::GetDocColors() +{ + std::set<Color> aDocColors; + SwAttrPool& rPool = GetAttrPool(); + const sal_uInt16 pAttribs[] = {RES_CHRATR_COLOR, RES_CHRATR_HIGHLIGHT, RES_BACKGROUND}; + for (sal_uInt16 nAttrib : pAttribs) + { + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(nAttrib)) + { + auto pColorItem = static_cast<const SvxColorItem*>(pItem); + Color aColor( pColorItem->GetValue() ); + if (COL_AUTO != aColor) + aDocColors.insert(aColor); + } + } + return aDocColors; +} + +// #i69627# +namespace docfunc +{ + bool HasOutlineStyleToBeWrittenAsNormalListStyle( SwDoc& rDoc ) + { + // If a parent paragraph style of one of the paragraph styles, which + // are assigned to the list levels of the outline style, has a list style + // set or inherits a list style from its parent style, the outline style + // has to be written as a normal list style to the OpenDocument file + // format or the OpenOffice.org file format. + bool bRet( false ); + + const SwTextFormatColls* pTextFormatColls( rDoc.GetTextFormatColls() ); + if ( pTextFormatColls ) + { + for ( auto pTextFormatColl : *pTextFormatColls ) + { + if ( pTextFormatColl->IsDefault() || + ! pTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + continue; + } + + const SwTextFormatColl* pParentTextFormatColl = + dynamic_cast<const SwTextFormatColl*>( pTextFormatColl->DerivedFrom()); + if ( !pParentTextFormatColl ) + continue; + + if ( SfxItemState::SET == pParentTextFormatColl->GetItemState( RES_PARATR_NUMRULE ) ) + { + // #i106218# consider that the outline style is set + const SwNumRuleItem& rDirectItem = pParentTextFormatColl->GetNumRule(); + if ( rDirectItem.GetValue() != rDoc.GetOutlineNumRule()->GetName() ) + { + bRet = true; + break; + } + } + } + + } + return bRet; + } +} + +SwFrameFormats::SwFrameFormats() + : m_PosIndex( m_Array.get<0>() ) + , m_TypeAndNameIndex( m_Array.get<1>() ) +{ +} + +SwFrameFormats::~SwFrameFormats() +{ + DeleteAndDestroyAll(); +} + +SwFrameFormats::const_iterator SwFrameFormats::find( const value_type& x ) const +{ + ByTypeAndName::iterator it = m_TypeAndNameIndex.find( + boost::make_tuple(x->Which(), x->GetName(), x) ); + return m_Array.project<0>( it ); +} + +std::pair<SwFrameFormats::const_range_iterator,SwFrameFormats::const_range_iterator> +SwFrameFormats::rangeFind( sal_uInt16 type, const OUString& name ) const +{ + return m_TypeAndNameIndex.equal_range( boost::make_tuple(type, name) ); +} + +std::pair<SwFrameFormats::const_range_iterator,SwFrameFormats::const_range_iterator> +SwFrameFormats::rangeFind( const value_type& x ) const +{ + return rangeFind( x->Which(), x->GetName() ); +} + +void SwFrameFormats::DeleteAndDestroyAll( bool keepDefault ) +{ + if ( empty() ) + return; + const int _offset = keepDefault ? 1 : 0; + for( const_iterator it = begin() + _offset; it != end(); ++it ) + delete *it; + if ( _offset ) + m_PosIndex.erase( begin() + _offset, end() ); + else + m_Array.clear(); +} + +std::pair<SwFrameFormats::const_iterator,bool> SwFrameFormats::push_back( const value_type& x ) +{ + SAL_WARN_IF(x->m_ffList != nullptr, "sw.core", "Inserting already assigned item"); + assert(x->m_ffList == nullptr); + x->m_ffList = this; + return m_PosIndex.push_back( x ); +} + +bool SwFrameFormats::erase( const value_type& x ) +{ + const_iterator const ret = find( x ); + SAL_WARN_IF(x->m_ffList != this, "sw.core", "Removing invalid / unassigned item"); + if (ret != end()) { + assert( x == *ret ); + m_PosIndex.erase( ret ); + x->m_ffList = nullptr; + return true; + } + return false; +} + +void SwFrameFormats::erase( size_type index_ ) +{ + erase( begin() + index_ ); +} + +void SwFrameFormats::erase( const_iterator const& position ) +{ + (*position)->m_ffList = nullptr; + m_PosIndex.erase( begin() + (position - begin()) ); +} + +bool SwFrameFormats::ContainsFormat(const SwFrameFormat& x) const +{ + return (x.m_ffList == this); +} + +bool SwFrameFormats::IsAlive(SwFrameFormat const*const p) const +{ + return find(const_cast<SwFrameFormat*>(p)) != end(); +} + +bool SwFrameFormats::newDefault( const value_type& x ) +{ + std::pair<iterator,bool> res = m_PosIndex.push_front( x ); + if( ! res.second ) + newDefault( res.first ); + return res.second; +} + +void SwFrameFormats::newDefault( const_iterator const& position ) +{ + if (position == begin()) + return; + m_PosIndex.relocate( begin(), position ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docftn.cxx b/sw/source/core/doc/docftn.cxx new file mode 100644 index 000000000..90899b91a --- /dev/null +++ b/sw/source/core/doc/docftn.cxx @@ -0,0 +1,547 @@ +/* -*- 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 <ftnidx.hxx> +#include <rootfrm.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <pam.hxx> +#include <pagedesc.hxx> +#include <charfmt.hxx> +#include <UndoAttribute.hxx> +#include <hints.hxx> +#include <rolbck.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <ftninfo.hxx> + +SwEndNoteInfo& SwEndNoteInfo::operator=(const SwEndNoteInfo& rInfo) +{ + m_pTextFormatColl = rInfo.m_pTextFormatColl; + m_pPageDesc = rInfo.m_pPageDesc; + m_pCharFormat = rInfo.m_pCharFormat; + m_pAnchorFormat = rInfo.m_pAnchorFormat; + m_aDepends.EndListeningAll(); + m_aDepends.StartListening(m_pTextFormatColl); + m_aDepends.StartListening(m_pPageDesc); + m_aDepends.StartListening(m_pCharFormat); + m_aDepends.StartListening(m_pAnchorFormat); + + m_aFormat = rInfo.m_aFormat; + m_nFootnoteOffset = rInfo.m_nFootnoteOffset; + m_bEndNote = rInfo.m_bEndNote; + m_sPrefix = rInfo.m_sPrefix; + m_sSuffix = rInfo.m_sSuffix; + return *this; +} + +bool SwEndNoteInfo::operator==( const SwEndNoteInfo& rInfo ) const +{ + return + m_pTextFormatColl == rInfo.m_pTextFormatColl && + m_pPageDesc == rInfo.m_pPageDesc && + m_pCharFormat == rInfo.m_pCharFormat && + m_pAnchorFormat == rInfo.m_pAnchorFormat && + m_aFormat.GetNumberingType() == rInfo.m_aFormat.GetNumberingType() && + m_nFootnoteOffset == rInfo.m_nFootnoteOffset && + m_bEndNote == rInfo.m_bEndNote && + m_sPrefix == rInfo.m_sPrefix && + m_sSuffix == rInfo.m_sSuffix; +} + +SwEndNoteInfo::SwEndNoteInfo(const SwEndNoteInfo& rInfo) : + SwClient(nullptr), + m_aDepends(*this), + m_pTextFormatColl(rInfo.m_pTextFormatColl), + m_pPageDesc(rInfo.m_pPageDesc), + m_pCharFormat(rInfo.m_pCharFormat), + m_pAnchorFormat(rInfo.m_pAnchorFormat), + m_sPrefix( rInfo.m_sPrefix ), + m_sSuffix( rInfo.m_sSuffix ), + m_bEndNote( true ), + m_aFormat( rInfo.m_aFormat ), + m_nFootnoteOffset( rInfo.m_nFootnoteOffset ) +{ + m_aDepends.StartListening(m_pTextFormatColl); + m_aDepends.StartListening(m_pPageDesc); + m_aDepends.StartListening(m_pCharFormat); + m_aDepends.StartListening(m_pAnchorFormat); +} + +SwEndNoteInfo::SwEndNoteInfo() : + SwClient(nullptr), + m_aDepends(*this), + m_pTextFormatColl(nullptr), + m_pPageDesc(nullptr), + m_pCharFormat(nullptr), + m_pAnchorFormat(nullptr), + m_bEndNote( true ), + m_nFootnoteOffset( 0 ) +{ + m_aFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); +} + +SwPageDesc* SwEndNoteInfo::GetPageDesc(SwDoc& rDoc) const +{ + if(!m_pPageDesc) + { + m_pPageDesc = rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( static_cast<sal_uInt16>( + m_bEndNote ? RES_POOLPAGE_ENDNOTE : RES_POOLPAGE_FOOTNOTE ) ); + m_aDepends.StartListening(m_pPageDesc); + } + return m_pPageDesc; +} + +bool SwEndNoteInfo::KnowsPageDesc() const +{ + return m_pPageDesc != nullptr; +} + +bool SwEndNoteInfo::DependsOn(const SwPageDesc* pDesc) const +{ + return m_pPageDesc == pDesc; +} + +void SwEndNoteInfo::ChgPageDesc(SwPageDesc* pDesc) +{ + m_aDepends.EndListening(m_pPageDesc); + m_pPageDesc = pDesc; + m_aDepends.StartListening(m_pPageDesc); +} + +void SwEndNoteInfo::SetFootnoteTextColl(SwTextFormatColl& rFormat) +{ + m_aDepends.EndListening(m_pTextFormatColl); + m_pTextFormatColl = &rFormat; + m_aDepends.StartListening(m_pTextFormatColl); +} + +SwCharFormat* SwEndNoteInfo::GetCharFormat(SwDoc& rDoc) const +{ + auto pCharFormatFromDoc = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( static_cast<sal_uInt16>( + m_bEndNote ? RES_POOLCHR_ENDNOTE : RES_POOLCHR_FOOTNOTE ) ); + if (m_pCharFormat != pCharFormatFromDoc) + { + m_aDepends.EndListening(m_pCharFormat); + m_aDepends.StartListening(pCharFormatFromDoc); + m_pCharFormat = pCharFormatFromDoc; + } + return m_pCharFormat; +} + +namespace +{ + void lcl_ResetPoolIdForDocAndSync(const sal_uInt16 nId, SwCharFormat* pFormat, const SwEndNoteInfo& rInfo) + { + auto pDoc = pFormat->GetDoc(); + if(!pDoc) + return; + for(auto pDocFormat : *pDoc->GetCharFormats()) + { + if(pDocFormat == pFormat) + pDocFormat->SetPoolFormatId(nId); + else if(pDocFormat->GetPoolFormatId() == nId) + pDocFormat->SetPoolFormatId(0); + } + rInfo.GetCharFormat(*pDoc); + rInfo.GetAnchorCharFormat(*pDoc); + } +} + +void SwEndNoteInfo::SetCharFormat(SwCharFormat* pFormat) +{ + lcl_ResetPoolIdForDocAndSync( + static_cast<sal_uInt16>(m_bEndNote + ? RES_POOLCHR_ENDNOTE + : RES_POOLCHR_FOOTNOTE), + pFormat, + *this); +} + +SwCharFormat* SwEndNoteInfo::GetAnchorCharFormat(SwDoc& rDoc) const +{ + auto pAnchorFormatFromDoc = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( static_cast<sal_uInt16>( + m_bEndNote ? RES_POOLCHR_ENDNOTE_ANCHOR : RES_POOLCHR_FOOTNOTE_ANCHOR ) ); + if(m_pAnchorFormat != pAnchorFormatFromDoc) + { + m_aDepends.EndListening(m_pAnchorFormat); + m_aDepends.StartListening(pAnchorFormatFromDoc); + m_pAnchorFormat = pAnchorFormatFromDoc; + } + return m_pAnchorFormat; +} + +void SwEndNoteInfo::SetAnchorCharFormat(SwCharFormat* pFormat) +{ + lcl_ResetPoolIdForDocAndSync( + static_cast<sal_uInt16>(m_bEndNote + ? RES_POOLCHR_ENDNOTE_ANCHOR + : RES_POOLCHR_FOOTNOTE_ANCHOR), + pFormat, + *this); +} + +SwCharFormat* SwEndNoteInfo::GetCurrentCharFormat(const bool bAnchor) const +{ + return bAnchor + ? m_pAnchorFormat + : m_pCharFormat; +} + +void SwEndNoteInfo::SwClientNotify( const SwModify& rModify, const SfxHint& rHint) +{ + if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint)) + { + const sal_uInt16 nWhich = pLegacyHint->m_pOld ? pLegacyHint->m_pOld->Which() : pLegacyHint->m_pNew ? pLegacyHint->m_pNew->Which() : 0 ; + if (RES_ATTRSET_CHG == nWhich || RES_FMT_CHG == nWhich) + { + auto pFormat = GetCurrentCharFormat(m_pCharFormat == nullptr); + if (!pFormat || !m_aDepends.IsListeningTo(pFormat) || pFormat->IsFormatInDTOR()) + return; + SwDoc* pDoc = pFormat->GetDoc(); + SwFootnoteIdxs& rFootnoteIdxs = pDoc->GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() == m_bEndNote ) + { + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + else + CheckRegistration( pLegacyHint->m_pOld ); + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + auto pNew = const_cast<SwModify*>(pModifyChangedHint->m_pNew); + if(m_pAnchorFormat == &rModify) + m_pAnchorFormat = static_cast<SwCharFormat*>(pNew); + else if(m_pCharFormat == &rModify) + m_pCharFormat = static_cast<SwCharFormat*>(pNew); + else if(m_pPageDesc == &rModify) + m_pPageDesc = static_cast<SwPageDesc*>(pNew); + else if(m_pTextFormatColl == &rModify) + m_pTextFormatColl = static_cast<SwTextFormatColl*>(pNew); + } +} + +SwFootnoteInfo& SwFootnoteInfo::operator=(const SwFootnoteInfo& rInfo) +{ + SwEndNoteInfo::operator=(rInfo); + m_aQuoVadis = rInfo.m_aQuoVadis; + m_aErgoSum = rInfo.m_aErgoSum; + m_ePos = rInfo.m_ePos; + m_eNum = rInfo.m_eNum; + return *this; +} + +bool SwFootnoteInfo::operator==( const SwFootnoteInfo& rInfo ) const +{ + return m_ePos == rInfo.m_ePos && + m_eNum == rInfo.m_eNum && + SwEndNoteInfo::operator==(rInfo) && + m_aQuoVadis == rInfo.m_aQuoVadis && + m_aErgoSum == rInfo.m_aErgoSum; +} + +SwFootnoteInfo::SwFootnoteInfo(const SwFootnoteInfo& rInfo) : + SwEndNoteInfo( rInfo ), + m_aQuoVadis( rInfo.m_aQuoVadis ), + m_aErgoSum( rInfo.m_aErgoSum ), + m_ePos( rInfo.m_ePos ), + m_eNum( rInfo.m_eNum ) +{ + m_bEndNote = false; +} + +SwFootnoteInfo::SwFootnoteInfo() : + SwEndNoteInfo(), + m_ePos( FTNPOS_PAGE ), + m_eNum( FTNNUM_DOC ) +{ + m_aFormat.SetNumberingType(SVX_NUM_ARABIC); + m_bEndNote = false; +} + +void SwDoc::SetFootnoteInfo(const SwFootnoteInfo& rInfo) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( GetFootnoteInfo() == rInfo ) + return; + + const SwFootnoteInfo &rOld = GetFootnoteInfo(); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoFootNoteInfo>(rOld, this) ); + } + + bool bFootnotePos = rInfo.m_ePos != rOld.m_ePos; + bool bFootnoteDesc = rOld.m_ePos == FTNPOS_CHAPTER && + rInfo.GetPageDesc( *this ) != rOld.GetPageDesc( *this ); + bool bExtra = rInfo.m_aQuoVadis != rOld.m_aQuoVadis || + rInfo.m_aErgoSum != rOld.m_aErgoSum || + rInfo.m_aFormat.GetNumberingType() != rOld.m_aFormat.GetNumberingType() || + rInfo.GetPrefix() != rOld.GetPrefix() || + rInfo.GetSuffix() != rOld.GetSuffix(); + SwCharFormat *pOldChrFormat = rOld.GetCharFormat( *this ), + *pNewChrFormat = rInfo.GetCharFormat( *this ); + bool bFootnoteChrFormats = pOldChrFormat != pNewChrFormat; + + *mpFootnoteInfo = rInfo; + + if (pTmpRoot) + { + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = GetAllLayouts(); + if ( bFootnotePos ) + for( auto aLayout : aAllLayouts ) + aLayout->AllRemoveFootnotes(); + else + { + for( auto aLayout : aAllLayouts ) + aLayout->UpdateFootnoteNums(); + if ( bFootnoteDesc ) + for( auto aLayout : aAllLayouts ) + aLayout->CheckFootnotePageDescs(false); + if ( bExtra ) + { + // For messages regarding ErgoSum etc. we save the extra code and use the + // available methods. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( !rFootnote.IsEndNote() ) + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + } + if( FTNNUM_PAGE != rInfo.m_eNum ) + GetFootnoteIdxs().UpdateAllFootnote(); + else if( bFootnoteChrFormats ) + { + SwFormatChg aOld( pOldChrFormat ); + SwFormatChg aNew( pNewChrFormat ); + mpFootnoteInfo->ModifyNotification( &aOld, &aNew ); + } + + // #i81002# no update during loading + if ( !IsInReading() ) + { + getIDocumentFieldsAccess().UpdateRefFields(); + } + getIDocumentState().SetModified(); + +} + +void SwDoc::SetEndNoteInfo(const SwEndNoteInfo& rInfo) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if( GetEndNoteInfo() == rInfo ) + return; + + if(GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoEndNoteInfo>( GetEndNoteInfo(), this ) ); + } + + bool bNumChg = rInfo.m_nFootnoteOffset != GetEndNoteInfo().m_nFootnoteOffset; + // this seems to be an optimization: UpdateAllFootnote() is only called + // if the offset changes; if the offset is the same, + // but type/prefix/suffix changes, just set new numbers. + bool const bExtra = !bNumChg && + ( (rInfo.m_aFormat.GetNumberingType() != + GetEndNoteInfo().m_aFormat.GetNumberingType()) + || (rInfo.GetPrefix() != GetEndNoteInfo().GetPrefix()) + || (rInfo.GetSuffix() != GetEndNoteInfo().GetSuffix()) + ); + bool bFootnoteDesc = rInfo.GetPageDesc( *this ) != + GetEndNoteInfo().GetPageDesc( *this ); + SwCharFormat *pOldChrFormat = GetEndNoteInfo().GetCharFormat( *this ), + *pNewChrFormat = rInfo.GetCharFormat( *this ); + bool bFootnoteChrFormats = pOldChrFormat != pNewChrFormat; + + *mpEndNoteInfo = rInfo; + + if ( pTmpRoot ) + { + if ( bFootnoteDesc ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->CheckFootnotePageDescs(true); + } + if ( bExtra ) + { + // For messages regarding ErgoSum etc. we save the extra code and use the + // available methods. + SwFootnoteIdxs& rFootnoteIdxs = GetFootnoteIdxs(); + for( size_t nPos = 0; nPos < rFootnoteIdxs.size(); ++nPos ) + { + SwTextFootnote *pTextFootnote = rFootnoteIdxs[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() ) + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } + } + } + if( bNumChg ) + GetFootnoteIdxs().UpdateAllFootnote(); + else if( bFootnoteChrFormats ) + { + SwFormatChg aOld( pOldChrFormat ); + SwFormatChg aNew( pNewChrFormat ); + mpEndNoteInfo->ModifyNotification( &aOld, &aNew ); + } + + // #i81002# no update during loading + if ( !IsInReading() ) + { + getIDocumentFieldsAccess().UpdateRefFields(); + } + getIDocumentState().SetModified(); + +} + +bool SwDoc::SetCurFootnote( const SwPaM& rPam, const OUString& rNumStr, + bool bIsEndNote) +{ + SwFootnoteIdxs& rFootnoteArr = GetFootnoteIdxs(); + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + + const SwPosition* pStt = rPam.Start(), *pEnd = rPam.End(); + const sal_uLong nSttNd = pStt->nNode.GetIndex(); + const sal_Int32 nSttCnt = pStt->nContent.GetIndex(); + const sal_uLong nEndNd = pEnd->nNode.GetIndex(); + const sal_Int32 nEndCnt = pEnd->nContent.GetIndex(); + + size_t nPos = 0; + rFootnoteArr.SeekEntry( pStt->nNode, &nPos ); + + std::unique_ptr<SwUndoChangeFootNote> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it + pUndo.reset(new SwUndoChangeFootNote( rPam, rNumStr, bIsEndNote )); + } + + bool bChg = false; + bool bTypeChgd = false; + const size_t nPosSave = nPos; + while( nPos < rFootnoteArr.size() ) + { + SwTextFootnote* pTextFootnote = rFootnoteArr[ nPos++ ]; + sal_uLong nIdx = SwTextFootnote_GetIndex(pTextFootnote); + if( nIdx >= nEndNd && + ( nIdx != nEndNd || nEndCnt < pTextFootnote->GetStart() ) ) + continue; + if( nIdx > nSttNd || ( nIdx == nSttNd && + nSttCnt <= pTextFootnote->GetStart() ) ) + { + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr() != rNumStr || + rFootnote.IsEndNote() != bIsEndNote ) + { + bChg = true; + if ( pUndo ) + { + pUndo->GetHistory().Add( *pTextFootnote ); + } + + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rNumStr); + if( rFootnote.IsEndNote() != bIsEndNote ) + { + const_cast<SwFormatFootnote&>(rFootnote).SetEndNote( bIsEndNote ); + bTypeChgd = true; + pTextFootnote->CheckCondColl(); + //#i11339# dispose UNO wrapper when a footnote is changed to an endnote or vice versa + const_cast<SwFormatFootnote&>(rFootnote).InvalidateFootnote(); + } + } + } + } + + nPos = nPosSave; // There are more in the front! + while( nPos ) + { + SwTextFootnote* pTextFootnote = rFootnoteArr[ --nPos ]; + sal_uLong nIdx = SwTextFootnote_GetIndex(pTextFootnote); + if( nIdx <= nSttNd && + ( nIdx != nSttNd || nSttCnt > pTextFootnote->GetStart() ) ) + continue; + if( nIdx < nEndNd || ( nIdx == nEndNd && + nEndCnt >= pTextFootnote->GetStart() ) ) + { + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr() != rNumStr || + rFootnote.IsEndNote() != bIsEndNote ) + { + bChg = true; + if ( pUndo ) + { + pUndo->GetHistory().Add( *pTextFootnote ); + } + + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rNumStr); + if( rFootnote.IsEndNote() != bIsEndNote ) + { + const_cast<SwFormatFootnote&>(rFootnote).SetEndNote( bIsEndNote ); + bTypeChgd = true; + pTextFootnote->CheckCondColl(); + } + } + } + } + + // Who needs to be triggered? + if( bChg ) + { + if( pUndo ) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + if ( bTypeChgd ) + rFootnoteArr.UpdateAllFootnote(); + if( FTNNUM_PAGE != GetFootnoteInfo().m_eNum ) + { + if ( !bTypeChgd ) + rFootnoteArr.UpdateAllFootnote(); + } + else if( pTmpRoot ) + { + for( auto aLayout : GetAllLayouts() ) + aLayout->UpdateFootnoteNums(); + } + getIDocumentState().SetModified(); + } + return bChg; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docglbl.cxx b/sw/source/core/doc/docglbl.cxx new file mode 100644 index 000000000..b933843ff --- /dev/null +++ b/sw/source/core/doc/docglbl.cxx @@ -0,0 +1,523 @@ +/* -*- 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 <osl/diagnose.h> +#include <unotools/tempfile.hxx> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <tools/datetime.hxx> +#include <fmtinfmt.hxx> +#include <fmtanchr.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <DocumentSettingManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <docsh.hxx> +#include <section.hxx> +#include <calbck.hxx> +#include <iodetect.hxx> +#include <frameformats.hxx> +#include <memory> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/frame/XModel.hpp> + +using namespace ::com::sun::star; + +namespace { + +enum SwSplitDocType +{ + SPLITDOC_TO_GLOBALDOC, + SPLITDOC_TO_HTML +}; + +} + +bool SwDoc::GenerateGlobalDoc( const OUString& rPath, + const SwTextFormatColl* pSplitColl ) +{ + return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, false, pSplitColl ); +} + +bool SwDoc::GenerateGlobalDoc( const OUString& rPath, int nOutlineLevel ) +{ + return SplitDoc( SPLITDOC_TO_GLOBALDOC, rPath, true, nullptr, nOutlineLevel ); +} + +bool SwDoc::GenerateHTMLDoc( const OUString& rPath, int nOutlineLevel ) +{ + return SplitDoc( SPLITDOC_TO_HTML, rPath, true, nullptr, nOutlineLevel ); +} + +bool SwDoc::GenerateHTMLDoc( const OUString& rPath, + const SwTextFormatColl* pSplitColl ) +{ + return SplitDoc( SPLITDOC_TO_HTML, rPath, false, pSplitColl ); +} + +// two helpers for outline mode +static SwNodePtr GetStartNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNodePtr pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel && !pNd->FindTableNode() ) + { + return pNd; + } + } + + return nullptr; +} + +static SwNodePtr GetEndNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + SwNodePtr pNd; + + for( ++(*nOutl); (*nOutl) < pOutlNds->size(); ++(*nOutl) ) + { + pNd = (*pOutlNds)[ *nOutl ]; + + const int nLevel = pNd->GetTextNode()->GetAttrOutlineLevel(); + + if( ( 0 < nLevel && nLevel <= nOutlineLevel ) && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +// two helpers for collection mode +static SwNodePtr GetStartNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNodePtr pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetTextColl() == pSplitColl && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +static SwNodePtr GetEndNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + SwNodePtr pNd; + + for( ++(*nOutl); *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + pNd = (*pOutlNds)[ *nOutl ]; + SwTextFormatColl* pTColl = pNd->GetTextNode()->GetTextColl(); + + if( ( pTColl == pSplitColl || + ( pSplitColl->GetAttrOutlineLevel() > 0 && + pTColl->GetAttrOutlineLevel() > 0 && + pTColl->GetAttrOutlineLevel() < + pSplitColl->GetAttrOutlineLevel() )) && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +bool SwDoc::SplitDoc( sal_uInt16 eDocType, const OUString& rPath, bool bOutline, const SwTextFormatColl* pSplitColl, int nOutlineLevel ) +{ + // Iterate over all the template's Nodes, creating an own + // document for every single one and replace linked sections (GlobalDoc) for links (HTML). + // Finally, we save this document as a GlobalDoc/HTMLDoc. + if( !mpDocShell || !mpDocShell->GetMedium() || + ( SPLITDOC_TO_GLOBALDOC == eDocType && GetDocumentSettingManager().get(DocumentSettingId::GLOBAL_DOCUMENT) ) ) + return false; + + SwOutlineNodes::size_type nOutl = 0; + SwOutlineNodes* pOutlNds = const_cast<SwOutlineNodes*>(&GetNodes().GetOutLineNds()); + std::unique_ptr<SwOutlineNodes> xTmpOutlNds; + SwNodePtr pStartNd; + + if ( !bOutline) { + if( pSplitColl ) + { + // If it isn't an OutlineNumbering, then use an own array and collect the Nodes. + if( pSplitColl->GetAttrOutlineLevel() == 0 ) + { + xTmpOutlNds.reset(new SwOutlineNodes); + pOutlNds = xTmpOutlNds.get(); + SwIterator<SwTextNode,SwFormatColl> aIter( *pSplitColl ); + for( SwTextNode* pTNd = aIter.First(); pTNd; pTNd = aIter.Next() ) + if( pTNd->GetNodes().IsDocNodes() ) + pOutlNds->insert( pTNd ); + + if( pOutlNds->empty() ) + return false; + } + } + else + { + // Look for the 1st level OutlineTemplate + const SwTextFormatColls& rFormatColls =*GetTextFormatColls(); + for( SwTextFormatColls::size_type n = rFormatColls.size(); n; ) + if ( rFormatColls[ --n ]->GetAttrOutlineLevel() == 1 ) + { + pSplitColl = rFormatColls[ n ]; + break; + } + + if( !pSplitColl ) + return false; + } + } + + std::shared_ptr<const SfxFilter> pFilter; + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + pFilter = SwIoSystem::GetFilterOfFormat("HTML"); + break; + + default: + pFilter = SwIoSystem::GetFilterOfFormat(FILTER_XML); + eDocType = SPLITDOC_TO_GLOBALDOC; + break; + } + + if( !pFilter ) + return false; + + // Deactivate Undo/Redline in any case + GetIDocumentUndoRedo().DoUndo(false); + getIDocumentRedlineAccess().SetRedlineFlags_intern( getIDocumentRedlineAccess().GetRedlineFlags() & ~RedlineFlags::On ); + + OUString sExt = pFilter->GetSuffixes().getToken(0, ','); + if( sExt.isEmpty() ) + { + sExt = ".sxw"; + } + else + { + if( '.' != sExt[ 0 ] ) + { + sExt = "." + sExt; + } + } + + INetURLObject aEntry(rPath); + OUString sLeading(aEntry.GetBase()); + aEntry.removeSegment(); + OUString sPath = aEntry.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + utl::TempFile aTemp(sLeading, true, &sExt, &sPath); + aTemp.EnableKillingFile(); + + DateTime aTmplDate( DateTime::SYSTEM ); + { + tools::Time a2Min( 0 ); a2Min.SetMin( 2 ); + aTmplDate += a2Min; + } + + // Skip all invalid ones + while( nOutl < pOutlNds->size() && + (*pOutlNds)[ nOutl ]->GetIndex() < GetNodes().GetEndOfExtras().GetIndex() ) + ++nOutl; + + do { + if( bOutline ) + pStartNd = GetStartNode( pOutlNds, nOutlineLevel, &nOutl ); + else + pStartNd = GetStartNode( pOutlNds, pSplitColl, &nOutl ); + + if( pStartNd ) + { + SwNodePtr pEndNd; + if( bOutline ) + pEndNd = GetEndNode( pOutlNds, nOutlineLevel, &nOutl ); + else + pEndNd = GetEndNode( pOutlNds, pSplitColl, &nOutl ); + SwNodeIndex aEndIdx( pEndNd ? *pEndNd + : GetNodes().GetEndOfContent() ); + + // Write out the Nodes completely + OUString sFileName; + if( pStartNd->GetIndex() + 1 < aEndIdx.GetIndex() ) + { + SfxObjectShellLock xDocSh( new SwDocShell( SfxObjectCreateMode::INTERNAL )); + if( xDocSh->DoInitNew() ) + { + SwDoc* pDoc = static_cast<SwDocShell*>(&xDocSh)->GetDoc(); + + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + static_cast<SwDocShell*>(&xDocSh)->GetModel(), + uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + // the GlobalDoc is the template + xDocProps->setTemplateName(OUString()); + ::util::DateTime uDT = aTmplDate.GetUNODateTime(); + xDocProps->setTemplateDate(uDT); + xDocProps->setTemplateURL(rPath); + // Set the new doc's title to the text of the "split para". + // If the current doc has a title, insert it at the begin. + OUString sTitle( xDocProps->getTitle() ); + if (!sTitle.isEmpty()) + sTitle += ": "; + sTitle += pStartNd->GetTextNode()->GetExpandText(nullptr); + xDocProps->setTitle( sTitle ); + + // Replace template + pDoc->ReplaceStyles( *this ); + + // Take over chapter numbering + if( mpOutlineRule ) + pDoc->SetOutlineNumRule( *mpOutlineRule ); + + SwNodeRange aRg( *pStartNd, 0, aEndIdx.GetNode() ); + SwNodeIndex aTmpIdx( pDoc->GetNodes().GetEndOfContent() ); + GetNodes().Copy_( aRg, aTmpIdx, false ); + + // Delete the initial TextNode + SwNodeIndex aIdx( pDoc->GetNodes().GetEndOfExtras(), 2 ); + if( aIdx.GetIndex() + 1 != + pDoc->GetNodes().GetEndOfContent().GetIndex() ) + pDoc->GetNodes().Delete( aIdx ); + + // All Flys in the section + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, aIdx); + + // And what's with all the Bookmarks? + // ????? + + utl::TempFile aTempFile2(sLeading, true, &sExt, &sPath); + sFileName = aTempFile2.GetURL(); + SfxMedium* pTmpMed = new SfxMedium( sFileName, + StreamMode::STD_READWRITE ); + pTmpMed->SetFilter( pFilter ); + + // We need to have a Layout for the HTMLFilter, so that + // TextFrames/Controls/OLE objects can be exported correctly as graphics. + if( SPLITDOC_TO_HTML == eDocType && + !pDoc->GetSpzFrameFormats()->empty() ) + { + SfxViewFrame::LoadHiddenDocument( *xDocSh, SFX_INTERFACE_NONE ); + } + xDocSh->DoSaveAs( *pTmpMed ); + xDocSh->DoSaveCompleted( pTmpMed ); + + // do not insert a FileLinkSection in case of error + if( xDocSh->GetError() ) + sFileName.clear(); + } + xDocSh->DoClose(); + } + + // We can now insert the section + if( !sFileName.isEmpty() ) + { + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + { + // Delete all nodes in the section and, in the "start node", + // set the Link to the saved document. + sal_uLong nNodeDiff = aEndIdx.GetIndex() - + pStartNd->GetIndex() - 1; + if( nNodeDiff ) + { + SwPaM aTmp( *pStartNd, aEndIdx.GetNode(), 1, -1 ); + aTmp.GetPoint()->nContent.Assign( nullptr, 0 ); + aTmp.GetMark()->nContent.Assign( nullptr, 0 ); + SwNodeIndex aSIdx( aTmp.GetMark()->nNode ); + SwNodeIndex aEIdx( aTmp.GetPoint()->nNode ); + + // Try to move past the end + if( !aTmp.Move( fnMoveForward, GoInNode ) ) + { + // well then, back to the beginning + aTmp.Exchange(); + if( !aTmp.Move( fnMoveBackward, GoInNode )) + { + OSL_FAIL( "no more Nodes!" ); + } + } + // Move Bookmarks and so forth + CorrAbs( aSIdx, aEIdx, *aTmp.GetPoint(), true); + + // If FlyFrames are still around, delete these too + for( SwFrameFormats::size_type n = 0; n < GetSpzFrameFormats()->size(); ++n ) + { + SwFrameFormat* pFly = (*GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = + pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aSIdx <= pAPos->nNode && + pAPos->nNode < aEIdx ) + { + getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + + GetNodes().Delete( aSIdx, nNodeDiff ); + } + + // set the link in the StartNode + SwFormatINetFormat aINet( sFileName , OUString() ); + SwTextNode* pTNd = pStartNd->GetTextNode(); + pTNd->InsertItem(aINet, 0, pTNd->GetText().getLength()); + + // If the link cannot be found anymore, + // it has to be a bug! + if( !pOutlNds->Seek_Entry( pStartNd, &nOutl )) + pStartNd = nullptr; + ++nOutl ; + } + break; + + default: + { + const OUString sNm(INetURLObject(sFileName).GetLastName()); + SwSectionData aSectData( SectionType::FileLink, + GetUniqueSectionName( &sNm )); + SwSectionFormat* pFormat = MakeSectionFormat(); + aSectData.SetLinkFileName(sFileName); + aSectData.SetProtectFlag(true); + + --aEndIdx; // in the InsertSection the end is inclusive + while( aEndIdx.GetNode().IsStartNode() ) + --aEndIdx; + + // If any Section ends or starts in the new sectionrange, + // they must end or start before or after the range! + SwSectionNode* pSectNd = pStartNd->FindSectionNode(); + while( pSectNd && pSectNd->EndOfSectionIndex() + <= aEndIdx.GetIndex() ) + { + const SwNode* pSectEnd = pSectNd->EndOfSectionNode(); + if( pSectNd->GetIndex() + 1 == + pStartNd->GetIndex() ) + { + bool bMvIdx = aEndIdx == *pSectEnd; + DelSectionFormat( pSectNd->GetSection().GetFormat() ); + if( bMvIdx ) + --aEndIdx; + } + else + { + SwNodeRange aRg( *pStartNd, *pSectEnd ); + SwNodeIndex aIdx( *pSectEnd, 1 ); + GetNodes().MoveNodes( aRg, GetNodes(), aIdx ); + } + pSectNd = pStartNd->FindSectionNode(); + } + + pSectNd = aEndIdx.GetNode().FindSectionNode(); + while( pSectNd && pSectNd->GetIndex() > + pStartNd->GetIndex() ) + { + // #i15712# don't attempt to split sections if + // they are fully enclosed in [pSectNd,aEndIdx]. + if( aEndIdx < pSectNd->EndOfSectionIndex() ) + { + SwNodeRange aRg( *pSectNd, 1, aEndIdx, 1 ); + SwNodeIndex aIdx( *pSectNd ); + GetNodes().MoveNodes( aRg, GetNodes(), aIdx ); + } + + pSectNd = pStartNd->FindSectionNode(); + } + + // -> #i26762# + // Ensure order of start and end of section is sane. + SwNodeIndex aStartIdx(*pStartNd); + + if (aEndIdx >= aStartIdx) + { + pSectNd = GetNodes().InsertTextSection(aStartIdx, + *pFormat, aSectData, nullptr, &aEndIdx, false); + } + else + { + pSectNd = GetNodes().InsertTextSection(aEndIdx, + *pFormat, aSectData, nullptr, &aStartIdx, false); + } + // <- #i26762# + + pSectNd->GetSection().CreateLink( LinkCreateType::Connect ); + } + break; + } + } + } + } while( pStartNd ); + + xTmpOutlNds.reset(); + + switch( eDocType ) + { + case SPLITDOC_TO_HTML: + if( GetDocumentSettingManager().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + { + // save all remaining sections + while( !GetSections().empty() ) + DelSectionFormat( GetSections().front() ); + + SfxFilterContainer* pFCntnr = mpDocShell->GetFactory().GetFilterContainer(); + pFilter = pFCntnr->GetFilter4EA( pFilter->GetTypeName(), SfxFilterFlags::EXPORT ); + } + break; + + default: + // save the Globaldoc + GetDocumentSettingManager().set(DocumentSettingId::GLOBAL_DOCUMENT, true); + GetDocumentSettingManager().set(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS, false); + } + + // The medium isn't locked after reopening the document. + SfxRequest aReq( SID_SAVEASDOC, SfxCallMode::SYNCHRON, GetAttrPool() ); + aReq.AppendItem( SfxStringItem( SID_FILE_NAME, rPath ) ); + aReq.AppendItem( SfxBoolItem( SID_SAVETO, true ) ); + if(pFilter) + aReq.AppendItem( SfxStringItem( SID_FILTER_NAME, pFilter->GetName() ) ); + const SfxBoolItem *pRet = static_cast<const SfxBoolItem*>(mpDocShell->ExecuteSlot( aReq )); + + return pRet && pRet->GetValue(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docglos.cxx b/sw/source/core/doc/docglos.cxx new file mode 100644 index 000000000..269d00ad8 --- /dev/null +++ b/sw/source/core/doc/docglos.cxx @@ -0,0 +1,212 @@ +/* -*- 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 <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <osl/diagnose.h> + +#include <doc.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <shellio.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <acorrect.hxx> +#include <crsrsh.hxx> +#include <docsh.hxx> + +using namespace ::com::sun::star; + +void SwDoc::ReplaceUserDefinedDocumentProperties( + const uno::Reference<document::XDocumentProperties>& xSourceDocProps) +{ + OSL_ENSURE(xSourceDocProps.is(), "null reference"); + + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties() ); + OSL_ENSURE(xDocProps.is(), "null reference"); + + uno::Reference<beans::XPropertySet> xSourceUDSet( + xSourceDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertyContainer> xTargetUD( + xDocProps->getUserDefinedProperties()); + uno::Reference<beans::XPropertySet> xTargetUDSet(xTargetUD, + uno::UNO_QUERY_THROW); + const uno::Sequence<beans::Property> tgtprops + = xTargetUDSet->getPropertySetInfo()->getProperties(); + + for (const auto& rTgtProp : tgtprops) { + try { + xTargetUD->removeProperty(rTgtProp.Name); + } catch (uno::Exception &) { + // ignore + } + } + + uno::Reference<beans::XPropertySetInfo> xSetInfo + = xSourceUDSet->getPropertySetInfo(); + const uno::Sequence<beans::Property> srcprops = xSetInfo->getProperties(); + + for (const auto& rSrcProp : srcprops) { + try { + OUString name = rSrcProp.Name; + xTargetUD->addProperty(name, rSrcProp.Attributes, + xSourceUDSet->getPropertyValue(name)); + } catch (uno::Exception &) { + // ignore + } + } +} + +void SwDoc::ReplaceDocumentProperties(const SwDoc& rSource, bool mailMerge) +{ + uno::Reference<document::XDocumentPropertiesSupplier> xSourceDPS( + rSource.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xSourceDocProps( + xSourceDPS->getDocumentProperties() ); + OSL_ENSURE(xSourceDocProps.is(), "null reference"); + + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetDocShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties() ); + OSL_ENSURE(xDocProps.is(), "null reference"); + + xDocProps->setAuthor(xSourceDocProps->getAuthor()); + xDocProps->setGenerator(xSourceDocProps->getGenerator()); + xDocProps->setCreationDate(xSourceDocProps->getCreationDate()); + xDocProps->setTitle(xSourceDocProps->getTitle()); + xDocProps->setSubject(xSourceDocProps->getSubject()); + xDocProps->setDescription(xSourceDocProps->getDescription()); + xDocProps->setKeywords(xSourceDocProps->getKeywords()); + xDocProps->setLanguage(xSourceDocProps->getLanguage()); + // Note: These below originally weren't copied for mailmerge, but I don't see why not. + xDocProps->setModifiedBy(xSourceDocProps->getModifiedBy()); + xDocProps->setModificationDate(xSourceDocProps->getModificationDate()); + xDocProps->setPrintedBy(xSourceDocProps->getPrintedBy()); + xDocProps->setPrintDate(xSourceDocProps->getPrintDate()); + xDocProps->setTemplateName(xSourceDocProps->getTemplateName()); + xDocProps->setTemplateURL(xSourceDocProps->getTemplateURL()); + xDocProps->setTemplateDate(xSourceDocProps->getTemplateDate()); + xDocProps->setAutoloadURL(xSourceDocProps->getAutoloadURL()); + xDocProps->setAutoloadSecs(xSourceDocProps->getAutoloadSecs()); + xDocProps->setDefaultTarget(xSourceDocProps->getDefaultTarget()); + xDocProps->setDocumentStatistics(xSourceDocProps->getDocumentStatistics()); + xDocProps->setEditingCycles(xSourceDocProps->getEditingCycles()); + xDocProps->setEditingDuration(xSourceDocProps->getEditingDuration()); + + if( mailMerge ) // Note: Not sure this is needed. + { + // Manually set the creation date, otherwise author field isn't filled + // during MM, as it's set when saving the document the first time. + xDocProps->setCreationDate( xSourceDocProps->getModificationDate() ); + } + + ReplaceUserDefinedDocumentProperties( xSourceDocProps ); +} + +/// inserts an AutoText block +bool SwDoc::InsertGlossary( SwTextBlocks& rBlock, const OUString& rEntry, + SwPaM& rPaM, SwCursorShell* pShell ) +{ + bool bRet = false; + const sal_uInt16 nIdx = rBlock.GetIndex( rEntry ); + if( USHRT_MAX != nIdx ) + { + bool bSav_IsInsGlossary = mbInsOnlyTextGlssry; + mbInsOnlyTextGlssry = rBlock.IsOnlyTextBlock( nIdx ); + + if( rBlock.BeginGetDoc( nIdx ) ) + { + SwDoc* pGDoc = rBlock.GetDoc(); + + // Update all fixed fields, with the right DocInfo. + // FIXME: UGLY: Because we cannot limit the range in which to do + // field updates, we must update the fixed fields at the glossary + // entry document. + // To be able to do this, we copy the document properties of the + // target document to the glossary document + // OSL_ENSURE(GetDocShell(), "no SwDocShell"); // may be clipboard! + OSL_ENSURE(pGDoc->GetDocShell(), "no SwDocShell at glossary"); + if (GetDocShell() && pGDoc->GetDocShell()) + pGDoc->ReplaceDocumentProperties( *this ); + pGDoc->getIDocumentFieldsAccess().SetFixFields(nullptr); + + // StartAllAction(); + getIDocumentFieldsAccess().LockExpFields(); + + SwNodeIndex aStt( pGDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pGDoc->GetNodes().GoNext( &aStt ); + const SwTableNode* pTableNd = pContentNd->FindTableNode(); + SwPaM aCpyPam( pTableNd ? *const_cast<SwNode*>(static_cast<SwNode const *>(pTableNd)) : *static_cast<SwNode*>(pContentNd) ); + aCpyPam.SetMark(); + + // till the nodes array's end + aCpyPam.GetPoint()->nNode = pGDoc->GetNodes().GetEndOfContent().GetIndex()-1; + pContentNd = aCpyPam.GetContentNode(); + aCpyPam.GetPoint()->nContent.Assign( + pContentNd, pContentNd ? pContentNd->Len() : 0 ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + SwPaM *_pStartCursor = &rPaM, *_pStartCursor2 = _pStartCursor; + do { + + SwPosition& rInsPos = *_pStartCursor->GetPoint(); + SwStartNode* pBoxSttNd = const_cast<SwStartNode*>(rInsPos.nNode.GetNode(). + FindTableBoxStartNode()); + + if( pBoxSttNd && 2 == pBoxSttNd->EndOfSectionIndex() - + pBoxSttNd->GetIndex() && + aCpyPam.GetPoint()->nNode != aCpyPam.GetMark()->nNode ) + { + // We copy more than one Node to the current Box. + // However, we have to remove the BoxAttributes then. + ClearBoxNumAttrs( rInsPos.nNode ); + } + + SwDontExpandItem aACD; + aACD.SaveDontExpandItems( rInsPos ); + + pGDoc->getIDocumentContentOperations().CopyRange(aCpyPam, rInsPos, SwCopyFlags::CheckPosInFly); + + aACD.RestoreDontExpandItems( rInsPos ); + if( pShell ) + pShell->SaveTableBoxContent( &rInsPos ); + } while( (_pStartCursor = _pStartCursor->GetNext()) != + _pStartCursor2 ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSGLOSSARY, nullptr ); + + getIDocumentFieldsAccess().UnlockExpFields(); + if( !getIDocumentFieldsAccess().IsExpFieldsLocked() ) + getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + bRet = true; + } + mbInsOnlyTextGlssry = bSav_IsInsGlossary; + } + rBlock.EndGetDoc(); + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doclay.cxx b/sw/source/core/doc/doclay.cxx new file mode 100644 index 000000000..ce33b8c21 --- /dev/null +++ b/sw/source/core/doc/doclay.cxx @@ -0,0 +1,1682 @@ +/* -*- 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 <sot/exchange.hxx> +#include <svx/svdpage.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/opaqitem.hxx> +#include <svx/svdouno.hxx> +#include <editeng/frmdiritem.hxx> +#include <swmodule.hxx> +#include <modcfg.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <SwStyleNameMapper.hxx> +#include <drawdoc.hxx> +#include <fchrfmt.hxx> +#include <frmatr.hxx> +#include <txatbase.hxx> +#include <fmtfld.hxx> +#include <fmtornt.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <fmtflcnt.hxx> +#include <frmfmt.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <ndnotxt.hxx> +#include <ndole.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrm.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <swundo.hxx> +#include <flypos.hxx> +#include <UndoInsert.hxx> +#include <expfld.hxx> +#include <poolfmt.hxx> +#include <docary.hxx> +#include <swtable.hxx> +#include <tblsel.hxx> +#include <txtftn.hxx> +#include <ftnidx.hxx> +#include <ftninfo.hxx> +#include <pagedesc.hxx> +#include <strings.hrc> +#include <frameformats.hxx> +#include <tools/datetimeutils.hxx> + +#include <sortedobjs.hxx> + +#include <vector> + +using namespace ::com::sun::star; + +#define DEF_FLY_WIDTH 2268 // Default width for FlyFrames (2268 == 4cm) + +static bool lcl_IsItemSet(const SwContentNode & rNode, sal_uInt16 which) +{ + bool bResult = false; + + if (SfxItemState::SET == rNode.GetSwAttrSet().GetItemState(which)) + bResult = true; + + return bResult; +} + +SdrObject* SwDoc::CloneSdrObj( const SdrObject& rObj, bool bMoveWithinDoc, + bool bInsInPage ) +{ + // #i52858# - method name changed + SdrPage *pPg = getIDocumentDrawModelAccess().GetOrCreateDrawModel()->GetPage( 0 ); + if( !pPg ) + { + pPg = getIDocumentDrawModelAccess().GetDrawModel()->AllocPage( false ); + getIDocumentDrawModelAccess().GetDrawModel()->InsertPage( pPg ); + } + + // TTTT Clone directly to target SdrModel + SdrObject *pObj(rObj.CloneSdrObject(*getIDocumentDrawModelAccess().GetDrawModel())); + + if( bMoveWithinDoc && SdrInventor::FmForm == pObj->GetObjInventor() ) + { + // We need to preserve the Name for Controls + uno::Reference< awt::XControlModel > xModel = static_cast<SdrUnoObj*>(pObj)->GetUnoControlModel(); + uno::Any aVal; + uno::Reference< beans::XPropertySet > xSet(xModel, uno::UNO_QUERY); + const OUString sName("Name"); + if( xSet.is() ) + aVal = xSet->getPropertyValue( sName ); + if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj ); + if( xSet.is() ) + xSet->setPropertyValue( sName, aVal ); + } + else if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj ); + + // For drawing objects: set layer of cloned object to invisible layer + SdrLayerID nLayerIdForClone = rObj.GetLayer(); + if ( dynamic_cast<const SwFlyDrawObj*>( pObj) == nullptr && + dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr && + typeid(SdrObject) != typeid(pObj) ) + { + if ( getIDocumentDrawModelAccess().IsVisibleLayerId( nLayerIdForClone ) ) + { + nLayerIdForClone = getIDocumentDrawModelAccess().GetInvisibleLayerIdByVisibleOne( nLayerIdForClone ); + } + } + pObj->SetLayer( nLayerIdForClone ); + + return pObj; +} + +SwFlyFrameFormat* SwDoc::MakeFlySection_( const SwPosition& rAnchPos, + const SwContentNode& rNode, + RndStdIds eRequestId, + const SfxItemSet* pFlySet, + SwFrameFormat* pFrameFormat ) +{ + if( !pFrameFormat ) + pFrameFormat = getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ); + + OUString sName; + if( !mbInReading ) + switch( rNode.GetNodeType() ) + { + case SwNodeType::Grf: sName = GetUniqueGrfName(); break; + case SwNodeType::Ole: sName = GetUniqueOLEName(); break; + default: sName = GetUniqueFrameName(); break; + } + SwFlyFrameFormat* pFormat = MakeFlyFrameFormat( sName, pFrameFormat ); + + // Create content and connect to the format. + // Create ContentNode and put it into the autotext selection. + SwNodeRange aRange( GetNodes().GetEndOfAutotext(), -1, + GetNodes().GetEndOfAutotext() ); + GetNodes().SectionDown( &aRange, SwFlyStartNode ); + + pFormat->SetFormatAttr( SwFormatContent( rNode.StartOfSectionNode() )); + + const SwFormatAnchor* pAnchor = nullptr; + if( pFlySet ) + { + pFlySet->GetItemState( RES_ANCHOR, false, + reinterpret_cast<const SfxPoolItem**>(&pAnchor) ); + if( SfxItemState::SET == pFlySet->GetItemState( RES_CNTNT, false )) + { + SfxItemSet aTmpSet( *pFlySet ); + aTmpSet.ClearItem( RES_CNTNT ); + pFormat->SetFormatAttr( aTmpSet ); + } + else + pFormat->SetFormatAttr( *pFlySet ); + } + + // Anchor not yet set? + RndStdIds eAnchorId; + // #i107811# Assure that at-page anchored fly frames have a page num or a + // content anchor set. + if ( !pAnchor || + ( RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId() && + !pAnchor->GetContentAnchor() ) || + ( RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId() && + !pAnchor->GetContentAnchor() && + pAnchor->GetPageNum() == 0 ) ) + { + // set it again, needed for Undo + SwFormatAnchor aAnch( pFormat->GetAnchor() ); + if (pAnchor && (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())) + { + SwPosition aPos( *rAnchPos.nNode.GetNode().FindFlyStartNode() ); + aAnch.SetAnchor( &aPos ); + eAnchorId = RndStdIds::FLY_AT_FLY; + } + else + { + if( eRequestId != aAnch.GetAnchorId() && + SfxItemState::SET != pFormat->GetItemState( RES_ANCHOR ) ) + { + aAnch.SetType( eRequestId ); + } + + eAnchorId = aAnch.GetAnchorId(); + if ( RndStdIds::FLY_AT_PAGE != eAnchorId || !pAnchor || aAnch.GetPageNum() == 0) + { + aAnch.SetAnchor( &rAnchPos ); + } + } + pFormat->SetFormatAttr( aAnch ); + } + else + eAnchorId = pFormat->GetAnchor().GetAnchorId(); + + if ( RndStdIds::FLY_AS_CHAR == eAnchorId ) + { + const sal_Int32 nStt = rAnchPos.nContent.GetIndex(); + SwTextNode * pTextNode = rAnchPos.nNode.GetNode().GetTextNode(); + + OSL_ENSURE(pTextNode!= nullptr, "There should be a SwTextNode!"); + + if (pTextNode != nullptr) + { + SwFormatFlyCnt aFormat( pFormat ); + // may fail if there's no space left or header/ftr + if (!pTextNode->InsertItem(aFormat, nStt, nStt)) + { // pFormat is dead now + return nullptr; + } + } + } + + if( SfxItemState::SET != pFormat->GetAttrSet().GetItemState( RES_FRM_SIZE )) + { + SwFormatFrameSize aFormatSize( SwFrameSize::Variable, 0, DEF_FLY_WIDTH ); + const SwNoTextNode* pNoTextNode = rNode.GetNoTextNode(); + if( pNoTextNode ) + { + // Set size + Size aSize( pNoTextNode->GetTwipSize() ); + if( MINFLY > aSize.Width() ) + aSize.setWidth( DEF_FLY_WIDTH ); + aFormatSize.SetWidth( aSize.Width() ); + if( aSize.Height() ) + { + aFormatSize.SetHeight( aSize.Height() ); + aFormatSize.SetHeightSizeType( SwFrameSize::Fixed ); + } + } + pFormat->SetFormatAttr( aFormatSize ); + } + + // Set up frames + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + pFormat->MakeFrames(); // ??? + + if (GetIDocumentUndoRedo().DoesUndo()) + { + sal_uLong nNodeIdx = rAnchPos.nNode.GetIndex(); + const sal_Int32 nCntIdx = rAnchPos.nContent.GetIndex(); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsLayFormat>( pFormat, nNodeIdx, nCntIdx )); + } + + getIDocumentState().SetModified(); + return pFormat; +} + +SwFlyFrameFormat* SwDoc::MakeFlySection( RndStdIds eAnchorType, + const SwPosition* pAnchorPos, + const SfxItemSet* pFlySet, + SwFrameFormat* pFrameFormat, bool bCalledFromShell ) +{ + SwFlyFrameFormat* pFormat = nullptr; + if ( !pAnchorPos && (RndStdIds::FLY_AT_PAGE != eAnchorType) ) + { + const SwFormatAnchor* pAnch; + if( (pFlySet && SfxItemState::SET == pFlySet->GetItemState( + RES_ANCHOR, false, reinterpret_cast<const SfxPoolItem**>(&pAnch) )) || + ( pFrameFormat && SfxItemState::SET == pFrameFormat->GetItemState( + RES_ANCHOR, true, reinterpret_cast<const SfxPoolItem**>(&pAnch) )) ) + { + if ( RndStdIds::FLY_AT_PAGE != pAnch->GetAnchorId() ) + { + pAnchorPos = pAnch->GetContentAnchor(); + } + } + } + + if (pAnchorPos) + { + if( !pFrameFormat ) + pFrameFormat = getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ); + + sal_uInt16 nCollId = static_cast<sal_uInt16>( + GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE) ? RES_POOLCOLL_TEXT : RES_POOLCOLL_FRAME ); + + /* If there is no adjust item in the paragraph style for the content node of the new fly section + propagate an existing adjust item at the anchor to the new content node. */ + SwContentNode * pNewTextNd = GetNodes().MakeTextNode + (SwNodeIndex( GetNodes().GetEndOfAutotext()), + getIDocumentStylePoolAccess().GetTextCollFromPool( nCollId )); + SwContentNode * pAnchorNode = pAnchorPos->nNode.GetNode().GetContentNode(); + // pAnchorNode from cursor must be valid, unless a whole table is selected (in which + // case the node is not a content node, and pAnchorNode is nullptr). In the latter case, + // bCalledFromShell is false. + assert(!bCalledFromShell || pAnchorNode); + + const SfxPoolItem * pItem = nullptr; + + if (bCalledFromShell && !lcl_IsItemSet(*pNewTextNd, RES_PARATR_ADJUST) && + SfxItemState::SET == pAnchorNode->GetSwAttrSet(). + GetItemState(RES_PARATR_ADJUST, true, &pItem)) + { + pNewTextNd->SetAttr(*pItem); + } + + pFormat = MakeFlySection_( *pAnchorPos, *pNewTextNd, + eAnchorType, pFlySet, pFrameFormat ); + } + return pFormat; +} + +SwFlyFrameFormat* SwDoc::MakeFlyAndMove( const SwPaM& rPam, const SfxItemSet& rSet, + const SwSelBoxes* pSelBoxes, + SwFrameFormat *pParent ) +{ + const SwFormatAnchor& rAnch = rSet.Get( RES_ANCHOR ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSLAYFMT, nullptr ); + + SwFlyFrameFormat* pFormat = MakeFlySection( rAnch.GetAnchorId(), rPam.GetPoint(), + &rSet, pParent ); + + // If content is selected, it becomes the new frame's content. + // Namely, it is moved into the NodeArray's appropriate section. + + if( pFormat ) + { + do { // middle check loop + const SwFormatContent &rContent = pFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "No content prepared." ); + SwNodeIndex aIndex( *(rContent.GetContentIdx()), 1 ); + SwContentNode *pNode = aIndex.GetNode().GetContentNode(); + + // Attention: Do not create an index on the stack, or we + // cannot delete ContentNode in the end! + SwPosition aPos( aIndex ); + aPos.nContent.Assign( pNode, 0 ); + + if( pSelBoxes && !pSelBoxes->empty() ) + { + // Table selection + // Copy parts of a table: create a table with the same width as the + // original one and move (copy and delete) the selected boxes. + // The size is corrected on a percentage basis. + + SwTableNode* pTableNd = const_cast<SwTableNode*>((*pSelBoxes)[0]-> + GetSttNd()->FindTableNode()); + if( !pTableNd ) + break; + + SwTable& rTable = pTableNd->GetTable(); + + // Did we select the whole table? + if( pSelBoxes->size() == rTable.GetTabSortBoxes().size() ) + { + // move the whole table + SwNodeRange aRg( *pTableNd, 0, *pTableNd->EndOfSectionNode(), 1 ); + + // If we move the whole table and it is located within a + // FlyFrame, the we create a TextNode after it. + // So that this FlyFrame is preserved. + if( aRg.aEnd.GetNode().IsEndNode() ) + GetNodes().MakeTextNode( aRg.aStart, + GetDfltTextFormatColl() ); + + getIDocumentContentOperations().MoveNodeRange( aRg, aPos.nNode, SwMoveFlags::DEFAULT ); + } + else + { + rTable.MakeCopy( this, aPos, *pSelBoxes ); + // Don't delete a part of a table with row span!! + // You could delete the content instead -> ToDo + //rTable.DeleteSel( this, *pSelBoxes, 0, 0, true, true ); + } + + // If the table is within the frame, then copy without the following TextNode + aIndex = rContent.GetContentIdx()->GetNode().EndOfSectionIndex() - 1; + OSL_ENSURE( aIndex.GetNode().GetTextNode(), + "a TextNode should be here" ); + aPos.nContent.Assign( nullptr, 0 ); // Deregister index! + GetNodes().Delete( aIndex ); + + // This is a hack: whilst FlyFrames/Headers/Footers are not undoable we delete all Undo objects + if( GetIDocumentUndoRedo().DoesUndo() ) + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + } + else + { + // copy all Pams and then delete all + bool bOldFlag = mbCopyIsMove; + bool const bOldUndo = GetIDocumentUndoRedo().DoesUndo(); + bool const bOldRedlineMove(getIDocumentRedlineAccess().IsRedlineMove()); + mbCopyIsMove = true; + GetIDocumentUndoRedo().DoUndo(false); + getIDocumentRedlineAccess().SetRedlineMove(true); + for(const SwPaM& rTmp : rPam.GetRingContainer()) + { + if( rTmp.HasMark() && + *rTmp.GetPoint() != *rTmp.GetMark() ) + { + // aPos is the newly created fly section, so definitely outside rPam, it's pointless to check that again. + getIDocumentContentOperations().CopyRange(*const_cast<SwPaM*>(&rTmp), aPos, SwCopyFlags::IsMoveToFly); + } + } + getIDocumentRedlineAccess().SetRedlineMove(bOldRedlineMove); + mbCopyIsMove = bOldFlag; + GetIDocumentUndoRedo().DoUndo(bOldUndo); + + for(const SwPaM& rTmp : rPam.GetRingContainer()) + { + if( rTmp.HasMark() && + *rTmp.GetPoint() != *rTmp.GetMark() ) + { + getIDocumentContentOperations().DeleteAndJoin( *const_cast<SwPaM*>(&rTmp) ); + } + } + } + } while( false ); + } + + getIDocumentState().SetModified(); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSLAYFMT, nullptr ); + + return pFormat; +} + + +/* + * paragraph frames - o.k. if the PaM includes the paragraph from the beginning + * to the beginning of the next paragraph at least + * frames at character - o.k. if the PaM starts at least at the same position + * as the frame + */ +static bool lcl_TstFlyRange( const SwPaM* pPam, const SwPosition* pFlyPos, + RndStdIds nAnchorId ) +{ + bool bOk = false; + const SwPaM* pTmp = pPam; + do { + const sal_uInt32 nFlyIndex = pFlyPos->nNode.GetIndex(); + const SwPosition* pPaMStart = pTmp->Start(); + const SwPosition* pPaMEnd = pTmp->End(); + const sal_uInt32 nPamStartIndex = pPaMStart->nNode.GetIndex(); + const sal_uInt32 nPamEndIndex = pPaMEnd->nNode.GetIndex(); + if (RndStdIds::FLY_AT_PARA == nAnchorId) + bOk = (nPamStartIndex < nFlyIndex && nPamEndIndex > nFlyIndex) || + (((nPamStartIndex == nFlyIndex) && (pPaMStart->nContent.GetIndex() == 0)) && + (nPamEndIndex > nFlyIndex)); + else + { + const sal_Int32 nFlyContentIndex = pFlyPos->nContent.GetIndex(); + const sal_Int32 nPamEndContentIndex = pPaMEnd->nContent.GetIndex(); + bOk = (nPamStartIndex < nFlyIndex && + (( nPamEndIndex > nFlyIndex )|| + ((nPamEndIndex == nFlyIndex) && + (nPamEndContentIndex > nFlyContentIndex))) ) + || + (((nPamStartIndex == nFlyIndex) && + (pPaMStart->nContent.GetIndex() <= nFlyContentIndex)) && + ((nPamEndIndex > nFlyIndex) || + (nPamEndContentIndex > nFlyContentIndex ))); + } + + if( bOk ) + break; + pTmp = pTmp->GetNext(); + } while( pPam != pTmp ); + return bOk; +} + +SwPosFlyFrames SwDoc::GetAllFlyFormats( const SwPaM* pCmpRange, bool bDrawAlso, + bool bAsCharAlso ) const +{ + SwPosFlyFrames aRetval; + + // collect all anchored somehow to paragraphs + for( auto pFly : *GetSpzFrameFormats() ) + { + bool bDrawFormat = bDrawAlso && RES_DRAWFRMFMT == pFly->Which(); + bool bFlyFormat = RES_FLYFRMFMT == pFly->Which(); + if( bFlyFormat || bDrawFormat ) + { + const SwFormatAnchor& rAnchor = pFly->GetAnchor(); + SwPosition const*const pAPos = rAnchor.GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) && bAsCharAlso))) + { + if( pCmpRange && + !lcl_TstFlyRange( pCmpRange, pAPos, rAnchor.GetAnchorId() )) + continue; // not a valid FlyFrame + aRetval.insert(std::make_shared<SwPosFlyFrame>(pAPos->nNode, pFly, aRetval.size())); + } + } + } + + // If we don't have a layout we can't get page anchored FlyFrames. + // Also, page anchored FlyFrames are only returned if no range is specified. + if( !getIDocumentLayoutAccess().GetCurrentViewShell() || pCmpRange ) + { + return aRetval; + } + + const SwPageFrame *pPage = static_cast<const SwPageFrame*>(getIDocumentLayoutAccess().GetCurrentLayout()->GetLower()); + while( pPage ) + { + if( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for(SwAnchoredObject* pAnchoredObj : rObjs) + { + SwFrameFormat *pFly; + if ( dynamic_cast<const SwFlyFrame*>( pAnchoredObj) != nullptr ) + pFly = &(pAnchoredObj->GetFrameFormat()); + else if ( bDrawAlso ) + pFly = &(pAnchoredObj->GetFrameFormat()); + else + continue; + + const SwFormatAnchor& rAnchor = pFly->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_FLY != rAnchor.GetAnchorId()) && + (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId())) + { + const SwContentFrame * pContentFrame = pPage->FindFirstBodyContent(); + if ( !pContentFrame ) + { + // Oops! An empty page. + // In order not to lose the whole frame (RTF) we + // look for the last Content before the page. + const SwPageFrame *pPrv = static_cast<const SwPageFrame*>(pPage->GetPrev()); + while ( !pContentFrame && pPrv ) + { + pContentFrame = pPrv->FindFirstBodyContent(); + pPrv = static_cast<const SwPageFrame*>(pPrv->GetPrev()); + } + } + if ( pContentFrame ) + { + SwNodeIndex aIdx( pContentFrame->IsTextFrame() + ? *static_cast<SwTextFrame const*>(pContentFrame)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame const*>(pContentFrame)->GetNode() ); + aRetval.insert(std::make_shared<SwPosFlyFrame>(aIdx, pFly, aRetval.size())); + } + } + } + } + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + return aRetval; +} + +/* #i6447# changed behaviour if lcl_CpyAttr: + + If the old item set contains the item to set (no inheritance) copy the item + into the new set. + + If the old item set contains the item by inheritance and the new set + contains the item, too: + If the two items differ copy the item from the old set to the new set. + + Otherwise the new set will not be changed. +*/ +static void lcl_CpyAttr( SfxItemSet &rNewSet, const SfxItemSet &rOldSet, sal_uInt16 nWhich ) +{ + const SfxPoolItem *pOldItem = nullptr; + + rOldSet.GetItemState( nWhich, false, &pOldItem); + if (pOldItem != nullptr) + rNewSet.Put( *pOldItem ); + else + { + pOldItem = rOldSet.GetItem( nWhich ); + if (pOldItem != nullptr) + { + const SfxPoolItem *pNewItem = rNewSet.GetItem( nWhich ); + if (pNewItem != nullptr) + { + if (*pOldItem != *pNewItem) + rNewSet.Put( *pOldItem ); + } + else { + OSL_FAIL("What am I doing here?"); + } + } + else { + OSL_FAIL("What am I doing here?"); + } + } + +} + +static SwFlyFrameFormat * +lcl_InsertLabel(SwDoc & rDoc, SwTextFormatColls *const pTextFormatCollTable, + SwUndoInsertLabel *const pUndo, + SwLabelType const eType, OUString const& rText, OUString const& rSeparator, + const OUString& rNumberingSeparator, + const bool bBefore, const sal_uInt16 nId, const sal_uLong nNdIdx, + const OUString& rCharacterStyle, + const bool bCpyBrd ) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + bool bTable = false; // To save some code. + + // Get the field first, because we retrieve the TextColl via the field's name + OSL_ENSURE( nId == USHRT_MAX || nId < rDoc.getIDocumentFieldsAccess().GetFieldTypes()->size(), + "FieldType index out of bounds." ); + SwFieldType *pType = (nId != USHRT_MAX) ? (*rDoc.getIDocumentFieldsAccess().GetFieldTypes())[nId].get() : nullptr; + OSL_ENSURE(!pType || pType->Which() == SwFieldIds::SetExp, "wrong Id for Label"); + + SwTextFormatColl * pColl = nullptr; + if( pType ) + { + for( auto i = pTextFormatCollTable->size(); i; ) + { + if( (*pTextFormatCollTable)[ --i ]->GetName()==pType->GetName() ) + { + pColl = (*pTextFormatCollTable)[i]; + break; + } + } + OSL_ENSURE( pColl, "no text collection found" ); + } + + if( !pColl ) + { + pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_LABEL ); + } + + SwTextNode *pNew = nullptr; + SwFlyFrameFormat* pNewFormat = nullptr; + + switch ( eType ) + { + case SwLabelType::Table: + bTable = true; + [[fallthrough]]; + case SwLabelType::Fly: + // At the FlySection's Beginning/End insert the corresponding Node with its Field. + // The Frame is created automatically. + { + SwStartNode *pSttNd = rDoc.GetNodes()[nNdIdx]->GetStartNode(); + OSL_ENSURE( pSttNd, "No StartNode in InsertLabel." ); + sal_uLong nNode; + if( bBefore ) + { + nNode = pSttNd->GetIndex(); + if( !bTable ) + ++nNode; + } + else + { + nNode = pSttNd->EndOfSectionIndex(); + if( bTable ) + ++nNode; + } + + if( pUndo ) + pUndo->SetNodePos( nNode ); + + // Create Node for labeling paragraph. + SwNodeIndex aIdx( rDoc.GetNodes(), nNode ); + pNew = rDoc.GetNodes().MakeTextNode( aIdx, pColl ); + } + break; + + case SwLabelType::Object: + { + // Destroy Frame, + // insert new Frame, + // insert the corresponding Node with Field into the new Frame, + // insert the old Frame with the Object (Picture/OLE) paragraph-bound into the new Frame, + // create Frames. + + // Get the FlyFrame's Format and decouple the Layout. + SwFrameFormat *pOldFormat = rDoc.GetNodes()[nNdIdx]->GetFlyFormat(); + OSL_ENSURE( pOldFormat, "Couldn't find the Fly's Format." ); + // #i115719# + // <title> and <description> attributes are lost when calling <DelFrames()>. + // Thus, keep them and restore them after the calling <MakeFrames()> + const bool bIsSwFlyFrameFormatInstance( dynamic_cast<SwFlyFrameFormat*>(pOldFormat) != nullptr ); + const OUString sTitle( bIsSwFlyFrameFormatInstance + ? static_cast<SwFlyFrameFormat*>(pOldFormat)->GetObjTitle() + : OUString() ); + const OUString sDescription( bIsSwFlyFrameFormatInstance + ? static_cast<SwFlyFrameFormat*>(pOldFormat)->GetObjDescription() + : OUString() ); + pOldFormat->DelFrames(); + + pNewFormat = rDoc.MakeFlyFrameFormat( rDoc.GetUniqueFrameName(), + rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool(RES_POOLFRM_FRAME) ); + + /* #i6447#: Only the selected items are copied from the old + format. */ + std::unique_ptr<SfxItemSet> pNewSet = pNewFormat->GetAttrSet().Clone(); + + // Copy only the set attributes. + // The others should apply from the Templates. + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_PRINT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_OPAQUE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_PROTECT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_SURROUND ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_VERT_ORIENT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_HORI_ORIENT ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_LR_SPACE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_UL_SPACE ); + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_BACKGROUND ); + if( bCpyBrd ) + { + // If there's no BoxItem at graphic, but the new Format has one, then set the + // default item in the new Set. Because the graphic's size has never changed! + const SfxPoolItem *pItem; + if( SfxItemState::SET == pOldFormat->GetAttrSet(). + GetItemState( RES_BOX, true, &pItem )) + pNewSet->Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_BOX )) + pNewSet->Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pOldFormat->GetAttrSet(). + GetItemState( RES_SHADOW, true, &pItem )) + pNewSet->Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_SHADOW )) + pNewSet->Put( *GetDfltAttr( RES_SHADOW ) ); + } + else + { + // Hard-set the attributes, because they could come from the Template + // and then size calculations could not be correct anymore. + pNewSet->Put( SvxBoxItem(RES_BOX) ); + pNewSet->Put( SvxShadowItem(RES_SHADOW) ); + } + + // Always transfer the anchor, which is a hard attribute anyways. + pNewSet->Put( pOldFormat->GetAnchor() ); + + // The new one should be changeable in its height. + std::unique_ptr<SwFormatFrameSize> aFrameSize(pOldFormat->GetFrameSize().Clone()); + aFrameSize->SetHeightSizeType( SwFrameSize::Minimum ); + pNewSet->Put( std::move(aFrameSize) ); + + SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( + SwNodeIndex( rDoc.GetNodes().GetEndOfAutotext() ), + SwFlyStartNode, pColl ); + pNewSet->Put( SwFormatContent( pSttNd )); + + pNewFormat->SetFormatAttr( *pNewSet ); + + // InContents need to be treated in a special way: + // The TextAttribute needs to be destroyed. + // Unfortunately, this also destroys the Format next to the Frames. + // To avoid this, we disconnect the attribute from the Format. + + const SwFormatAnchor& rAnchor = pNewFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId() ) + { + const SwPosition *pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt(nIdx, RES_TXTATR_FLYCNT); + + assert(pHint && "Missing Hint."); + + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == pOldFormat, + "Wrong TextFlyCnt-Hint." ); + + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat( + pNewFormat ); + } + + // The old one should not have a flow and it should be adjusted to above and + // middle. + // Also, the width should be 100% and it should also adjust the height, if changed. + pNewSet->ClearItem(); + + pNewSet->Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + pNewSet->Put( SvxOpaqueItem( RES_OPAQUE, true ) ); + + sal_Int16 eVert = bBefore ? text::VertOrientation::BOTTOM : text::VertOrientation::TOP; + pNewSet->Put( SwFormatVertOrient( 0, eVert ) ); + pNewSet->Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER ) ); + + aFrameSize.reset(pOldFormat->GetFrameSize().Clone()); + + SwOLENode* pOleNode = rDoc.GetNodes()[nNdIdx + 1]->GetOLENode(); + bool isMath = false; + if(pOleNode) + { + svt::EmbeddedObjectRef& xRef = pOleNode->GetOLEObj().GetObject(); + if(xRef.is()) + { + SvGlobalName aCLSID( xRef->getClassID() ); + isMath = ( SotExchange::IsMath( aCLSID ) != 0 ); + } + } + aFrameSize->SetWidthPercent(isMath ? 0 : 100); + aFrameSize->SetHeightPercent(SwFormatFrameSize::SYNCED); + pNewSet->Put( std::move(aFrameSize) ); + + // Hard-set the attributes, because they could come from the Template + // and then size calculations could not be correct anymore. + if( bCpyBrd ) + { + pNewSet->Put( SvxBoxItem(RES_BOX) ); + pNewSet->Put( SvxShadowItem(RES_SHADOW) ); + } + pNewSet->Put( SvxLRSpaceItem(RES_LR_SPACE) ); + pNewSet->Put( SvxULSpaceItem(RES_UL_SPACE) ); + + // The old one is paragraph-bound to the paragraph in the new one. + SwFormatAnchor aAnch( RndStdIds::FLY_AT_PARA ); + SwNodeIndex aAnchIdx( *pNewFormat->GetContent().GetContentIdx(), 1 ); + pNew = aAnchIdx.GetNode().GetTextNode(); + SwPosition aPos( aAnchIdx ); + aAnch.SetAnchor( &aPos ); + pNewSet->Put( aAnch ); + + if( pUndo ) + pUndo->SetFlys( *pOldFormat, *pNewSet, *pNewFormat ); + else + pOldFormat->SetFormatAttr( *pNewSet ); + + pNewSet.reset(); + + // Have only the FlyFrames created. + // We leave this to established methods (especially for InCntFlys). + pNewFormat->MakeFrames(); + // #i115719# + if ( bIsSwFlyFrameFormatInstance ) + { + static_cast<SwFlyFrameFormat*>(pOldFormat)->SetObjTitle( sTitle ); + static_cast<SwFlyFrameFormat*>(pOldFormat)->SetObjDescription( sDescription ); + } + } + break; + + default: + OSL_ENSURE(false, "unknown LabelType?"); + } + OSL_ENSURE( pNew, "No Label inserted" ); + if( pNew ) + { + // #i61007# order of captions + bool bOrderNumberingFirst = SW_MOD()->GetModuleConfig()->IsCaptionOrderNumberingFirst(); + // Work up OUString + OUString aText; + if( bOrderNumberingFirst ) + { + aText = rNumberingSeparator; + } + if( pType) + { + aText += pType->GetName(); + if( !bOrderNumberingFirst ) + aText += " "; + } + sal_Int32 nIdx = aText.getLength(); + if( !rText.isEmpty() ) + { + aText += rSeparator; + } + const sal_Int32 nSepIdx = aText.getLength(); + aText += rText; + + // Insert string + SwIndex aIdx( pNew, 0 ); + pNew->InsertText( aText, aIdx ); + + // Insert field + if(pType) + { + SwSetExpField aField( static_cast<SwSetExpFieldType*>(pType), OUString(), SVX_NUM_ARABIC); + if( bOrderNumberingFirst ) + nIdx = 0; + SwFormatField aFormat( aField ); + pNew->InsertItem( aFormat, nIdx, nIdx ); + if(!rCharacterStyle.isEmpty()) + { + SwCharFormat* pCharFormat = rDoc.FindCharFormatByName(rCharacterStyle); + if( !pCharFormat ) + { + const sal_uInt16 nMyId = SwStyleNameMapper::GetPoolIdFromUIName(rCharacterStyle, SwGetPoolIdFromName::ChrFmt); + pCharFormat = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( nMyId ); + } + if (pCharFormat) + { + SwFormatCharFormat aCharFormat( pCharFormat ); + pNew->InsertItem( aCharFormat, 0, + nSepIdx + 1, SetAttrMode::DONTEXPAND ); + } + } + } + + if ( bTable ) + { + if ( bBefore ) + { + if ( !pNew->GetSwAttrSet().GetKeep().GetValue() ) + pNew->SetAttr( SvxFormatKeepItem( true, RES_KEEP ) ); + } + else + { + SwTableNode *const pNd = + rDoc.GetNodes()[nNdIdx]->GetStartNode()->GetTableNode(); + SwTable &rTable = pNd->GetTable(); + if ( !rTable.GetFrameFormat()->GetKeep().GetValue() ) + rTable.GetFrameFormat()->SetFormatAttr( SvxFormatKeepItem( true, RES_KEEP ) ); + if ( pUndo ) + pUndo->SetUndoKeep(); + } + } + rDoc.getIDocumentState().SetModified(); + } + + return pNewFormat; +} + +SwFlyFrameFormat * +SwDoc::InsertLabel( + SwLabelType const eType, OUString const& rText, OUString const& rSeparator, + OUString const& rNumberingSeparator, + bool const bBefore, sal_uInt16 const nId, sal_uLong const nNdIdx, + OUString const& rCharacterStyle, + bool const bCpyBrd ) +{ + std::unique_ptr<SwUndoInsertLabel> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoInsertLabel( + eType, rText, rSeparator, rNumberingSeparator, + bBefore, nId, rCharacterStyle, bCpyBrd, this )); + } + + SwFlyFrameFormat *const pNewFormat = lcl_InsertLabel(*this, mpTextFormatCollTable.get(), pUndo.get(), + eType, rText, rSeparator, rNumberingSeparator, bBefore, + nId, nNdIdx, rCharacterStyle, bCpyBrd); + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + else + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + return pNewFormat; +} + +static SwFlyFrameFormat * +lcl_InsertDrawLabel( SwDoc & rDoc, SwTextFormatColls *const pTextFormatCollTable, + SwUndoInsertLabel *const pUndo, SwDrawFrameFormat *const pOldFormat, + OUString const& rText, + const OUString& rSeparator, + const OUString& rNumberSeparator, + const sal_uInt16 nId, + const OUString& rCharacterStyle, + SdrObject& rSdrObj ) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + ::sw::DrawUndoGuard const drawUndoGuard(rDoc.GetIDocumentUndoRedo()); + + // Because we get by the TextColl's name, we need to create the field first. + OSL_ENSURE( nId == USHRT_MAX || nId < rDoc.getIDocumentFieldsAccess().GetFieldTypes()->size(), + "FieldType index out of bounds" ); + SwFieldType *pType = nId != USHRT_MAX ? (*rDoc.getIDocumentFieldsAccess().GetFieldTypes())[nId].get() : nullptr; + OSL_ENSURE( !pType || pType->Which() == SwFieldIds::SetExp, "Wrong label id" ); + + SwTextFormatColl *pColl = nullptr; + if( pType ) + { + for( auto i = pTextFormatCollTable->size(); i; ) + { + if( (*pTextFormatCollTable)[ --i ]->GetName()==pType->GetName() ) + { + pColl = (*pTextFormatCollTable)[i]; + break; + } + } + OSL_ENSURE( pColl, "no text collection found" ); + } + + if( !pColl ) + { + pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_LABEL ); + } + + SwTextNode* pNew = nullptr; + SwFlyFrameFormat* pNewFormat = nullptr; + + // Destroy Frame, + // insert new Frame, + // insert the corresponding Node with Field into the new Frame, + // insert the old Frame with the Object (Picture/OLE) paragraph-bound into the new Frame, + // create Frames. + + // Keep layer ID of drawing object before removing + // its frames. + // Note: The layer ID is passed to the undo and have to be the correct value. + // Removing the frames of the drawing object changes its layer. + const SdrLayerID nLayerId = rSdrObj.GetLayer(); + + pOldFormat->DelFrames(); + + // InContents need to be treated in a special way: + // The TextAttribute needs to be destroyed. + // Unfortunately, this also destroys the Format next to the Frames. + // To avoid this, we disconnect the attribute from the Format. + std::unique_ptr<SfxItemSet> pNewSet = pOldFormat->GetAttrSet().Clone( false ); + + // Protect the Frame's size and position + if ( rSdrObj.IsMoveProtect() || rSdrObj.IsResizeProtect() ) + { + SvxProtectItem aProtect(RES_PROTECT); + aProtect.SetContentProtect( false ); + aProtect.SetPosProtect( rSdrObj.IsMoveProtect() ); + aProtect.SetSizeProtect( rSdrObj.IsResizeProtect() ); + pNewSet->Put( aProtect ); + } + + // Take over the text wrap + lcl_CpyAttr( *pNewSet, pOldFormat->GetAttrSet(), RES_SURROUND ); + + // Send the frame to the back, if needed. + // Consider the 'invisible' hell layer. + if ( rDoc.getIDocumentDrawModelAccess().GetHellId() != nLayerId && + rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId() != nLayerId ) + { + SvxOpaqueItem aOpaque( RES_OPAQUE ); + aOpaque.SetValue( true ); + pNewSet->Put( aOpaque ); + } + + // Take over position + // #i26791# - use directly drawing object's positioning attributes + pNewSet->Put( pOldFormat->GetHoriOrient() ); + pNewSet->Put( pOldFormat->GetVertOrient() ); + + pNewSet->Put( pOldFormat->GetAnchor() ); + + // The new one should be variable in its height! + Size aSz( rSdrObj.GetCurrentBoundRect().GetSize() ); + SwFormatFrameSize aFrameSize( SwFrameSize::Minimum, aSz.Width(), aSz.Height() ); + pNewSet->Put( aFrameSize ); + + // Apply the margin to the new Frame. + // Don't set a border, use the one from the Template. + pNewSet->Put( pOldFormat->GetLRSpace() ); + pNewSet->Put( pOldFormat->GetULSpace() ); + + SwStartNode* pSttNd = + rDoc.GetNodes().MakeTextSection( + SwNodeIndex( rDoc.GetNodes().GetEndOfAutotext() ), + SwFlyStartNode, pColl ); + + pNewFormat = rDoc.MakeFlyFrameFormat( rDoc.GetUniqueFrameName(), + rDoc.getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ) ); + + // Set border and shadow to default if the template contains any. + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState( RES_BOX )) + pNewSet->Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState(RES_SHADOW)) + pNewSet->Put( *GetDfltAttr( RES_SHADOW ) ); + + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + pNewFormat->SetFormatAttr( *pNewSet ); + + const SwFormatAnchor& rAnchor = pNewFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId() ) + { + const SwPosition *pPos = rAnchor.GetContentAnchor(); + SwTextNode *pTextNode = pPos->nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = pPos->nContent.GetIndex(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + + assert(pHint && "Missing Hint."); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt(). + GetFrameFormat() == static_cast<SwFrameFormat*>(pOldFormat), + "Wrong TextFlyCnt-Hint." ); +#endif + const_cast<SwFormatFlyCnt&>(pHint->GetFlyCnt()).SetFlyFormat( pNewFormat ); + } + + // The old one should not have a flow + // and it should be adjusted to above and middle. + pNewSet->ClearItem(); + + pNewSet->Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + if (nLayerId == rDoc.getIDocumentDrawModelAccess().GetHellId()) + { + // Consider drawing objects in the 'invisible' hell layer + rSdrObj.SetLayer( rDoc.getIDocumentDrawModelAccess().GetHeavenId() ); + } + else if (nLayerId == rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId()) + { + rSdrObj.SetLayer( rDoc.getIDocumentDrawModelAccess().GetInvisibleHeavenId() ); + } + pNewSet->Put( SvxLRSpaceItem( RES_LR_SPACE ) ); + pNewSet->Put( SvxULSpaceItem( RES_UL_SPACE ) ); + + // #i26791# - set position of the drawing object, which is labeled. + pNewSet->Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ) ); + pNewSet->Put( SwFormatHoriOrient( 0, text::HoriOrientation::CENTER, text::RelOrientation::FRAME ) ); + + // The old one is paragraph-bound to the new one's paragraph. + SwFormatAnchor aAnch( RndStdIds::FLY_AT_PARA ); + SwNodeIndex aAnchIdx( *pNewFormat->GetContent().GetContentIdx(), 1 ); + pNew = aAnchIdx.GetNode().GetTextNode(); + SwPosition aPos( aAnchIdx ); + aAnch.SetAnchor( &aPos ); + pNewSet->Put( aAnch ); + + if( pUndo ) + { + pUndo->SetFlys( *pOldFormat, *pNewSet, *pNewFormat ); + // #i26791# - position no longer needed + pUndo->SetDrawObj( nLayerId ); + } + else + pOldFormat->SetFormatAttr( *pNewSet ); + + pNewSet.reset(); + + // Have only the FlyFrames created. + // We leave this to established methods (especially for InCntFlys). + pNewFormat->MakeFrames(); + + OSL_ENSURE( pNew, "No Label inserted" ); + + if( pNew ) + { + //#i61007# order of captions + bool bOrderNumberingFirst = SW_MOD()->GetModuleConfig()->IsCaptionOrderNumberingFirst(); + + // prepare string + OUString aText; + if( bOrderNumberingFirst ) + { + aText = rNumberSeparator; + } + if ( pType ) + { + aText += pType->GetName(); + if( !bOrderNumberingFirst ) + aText += " "; + } + sal_Int32 nIdx = aText.getLength(); + aText += rSeparator; + const sal_Int32 nSepIdx = aText.getLength(); + aText += rText; + + // insert text + SwIndex aIdx( pNew, 0 ); + pNew->InsertText( aText, aIdx ); + + // insert field + if ( pType ) + { + SwSetExpField aField( static_cast<SwSetExpFieldType*>(pType), OUString(), SVX_NUM_ARABIC ); + if( bOrderNumberingFirst ) + nIdx = 0; + SwFormatField aFormat( aField ); + pNew->InsertItem( aFormat, nIdx, nIdx ); + if ( !rCharacterStyle.isEmpty() ) + { + SwCharFormat * pCharFormat = rDoc.FindCharFormatByName(rCharacterStyle); + if ( !pCharFormat ) + { + const sal_uInt16 nMyId = SwStyleNameMapper::GetPoolIdFromUIName( rCharacterStyle, SwGetPoolIdFromName::ChrFmt ); + pCharFormat = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( nMyId ); + } + if ( pCharFormat ) + { + SwFormatCharFormat aCharFormat( pCharFormat ); + pNew->InsertItem( aCharFormat, 0, nSepIdx + 1, + SetAttrMode::DONTEXPAND ); + } + } + } + } + + return pNewFormat; +} + +SwFlyFrameFormat* SwDoc::InsertDrawLabel( + OUString const& rText, + OUString const& rSeparator, + OUString const& rNumberSeparator, + sal_uInt16 const nId, + OUString const& rCharacterStyle, + SdrObject& rSdrObj ) +{ + SwDrawContact *const pContact = + static_cast<SwDrawContact*>(GetUserCall( &rSdrObj )); + if (!pContact) + return nullptr; + OSL_ENSURE( RES_DRAWFRMFMT == pContact->GetFormat()->Which(), + "InsertDrawLabel(): not a DrawFrameFormat" ); + + SwDrawFrameFormat* pOldFormat = static_cast<SwDrawFrameFormat *>(pContact->GetFormat()); + if (!pOldFormat) + return nullptr; + + std::unique_ptr<SwUndoInsertLabel> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + pUndo.reset(new SwUndoInsertLabel( + SwLabelType::Draw, rText, rSeparator, rNumberSeparator, false, + nId, rCharacterStyle, false, this )); + } + + SwFlyFrameFormat *const pNewFormat = lcl_InsertDrawLabel( + *this, mpTextFormatCollTable.get(), pUndo.get(), pOldFormat, + rText, rSeparator, rNumberSeparator, nId, rCharacterStyle, rSdrObj); + + if (pUndo) + { + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + GetIDocumentUndoRedo().DelAllUndoObj(); + } + + return pNewFormat; +} + +static void lcl_collectUsedNums(std::vector<unsigned int>& rSetFlags, sal_Int32 nNmLen, const OUString& rName, const OUString& rCmpName) +{ + if (rName.startsWith(rCmpName)) + { + // Only get and set the Flag + const sal_Int32 nNum = rName.copy(nNmLen).toInt32() - 1; + if (nNum >= 0) + rSetFlags.push_back(nNum); + } +} + +static void lcl_collectUsedNums(std::vector<unsigned int>& rSetFlags, sal_Int32 nNmLen, const SdrObject& rObj, const OUString& rCmpName) +{ + OUString sName = rObj.GetName(); + lcl_collectUsedNums(rSetFlags, nNmLen, sName, rCmpName); + // tdf#122487 take groups into account, iterate and recurse through their + // contents for name collision check + if (rObj.IsGroupObject()) + { + const SdrObjList* pSub(rObj.GetSubList()); + assert(pSub && "IsGroupObject is implemented as GetSubList != nullptr"); + const size_t nCount = pSub->GetObjCount(); + for (size_t i = 0; i < nCount; ++i) + { + SdrObject* pObj = pSub->GetObj(i); + if (!pObj) + continue; + lcl_collectUsedNums(rSetFlags, nNmLen, *pObj, rCmpName); + } + } +} + +namespace +{ + int first_available_number(std::vector<unsigned int>& numbers) + { + std::sort(numbers.begin(), numbers.end()); + auto last = std::unique(numbers.begin(), numbers.end()); + numbers.erase(last, numbers.end()); + + for (size_t i = 0; i < numbers.size(); ++i) + { + if (numbers[i] != i) + return i; + } + + return numbers.size(); + } +} + +static OUString lcl_GetUniqueFlyName(const SwDoc* pDoc, const char* pDefStrId, sal_uInt16 eType) +{ + assert(eType >= RES_FMT_BEGIN && eType < RES_FMT_END); + if( pDoc->IsInMailMerge()) + { + OUString newName = "MailMergeFly" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( pDoc->GetSpzFrameFormats()->size() + 1 ); + return newName; + } + + OUString aName(SwResId(pDefStrId)); + sal_Int32 nNmLen = aName.getLength(); + + const SwFrameFormats& rFormats = *pDoc->GetSpzFrameFormats(); + + std::vector<unsigned int> aUsedNums; + aUsedNums.reserve(rFormats.size()); + + for( SwFrameFormats::size_type n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFlyFormat = rFormats[ n ]; + if (eType != pFlyFormat->Which()) + continue; + if (eType == RES_DRAWFRMFMT) + { + const SdrObject *pObj = pFlyFormat->FindSdrObject(); + if (pObj) + lcl_collectUsedNums(aUsedNums, nNmLen, *pObj, aName); + } + + OUString sName = pFlyFormat->GetName(); + lcl_collectUsedNums(aUsedNums, nNmLen, sName, aName); + } + + // All numbers are flagged accordingly, so determine the right one + SwFrameFormats::size_type nNum = first_available_number(aUsedNums) + 1; + return aName + OUString::number(nNum); +} + +OUString SwDoc::GetUniqueGrfName() const +{ + return lcl_GetUniqueFlyName(this, STR_GRAPHIC_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueOLEName() const +{ + return lcl_GetUniqueFlyName(this, STR_OBJECT_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueFrameName() const +{ + return lcl_GetUniqueFlyName(this, STR_FRAME_DEFNAME, RES_FLYFRMFMT); +} + +OUString SwDoc::GetUniqueShapeName() const +{ + return lcl_GetUniqueFlyName(this, STR_SHAPE_DEFNAME, RES_DRAWFRMFMT); +} + +OUString SwDoc::GetUniqueDrawObjectName() const +{ + return lcl_GetUniqueFlyName(this, "DrawObject", RES_DRAWFRMFMT); +} + +const SwFlyFrameFormat* SwDoc::FindFlyByName( const OUString& rName, SwNodeType nNdTyp ) const +{ + auto range = GetSpzFrameFormats()->rangeFind( RES_FLYFRMFMT, rName ); + for( auto it = range.first; it != range.second; it++ ) + { + const SwFrameFormat* pFlyFormat = *it; + if( RES_FLYFRMFMT != pFlyFormat->Which() || pFlyFormat->GetName() != rName ) + continue; + const SwNodeIndex* pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + if( nNdTyp != SwNodeType::NONE ) + { + // query for the right NodeType + const SwNode* pNd = GetNodes()[ pIdx->GetIndex()+1 ]; + if( nNdTyp == SwNodeType::Text + ? !pNd->IsNoTextNode() + : nNdTyp == pNd->GetNodeType() ) + return static_cast<const SwFlyFrameFormat*>(pFlyFormat); + } + else + return static_cast<const SwFlyFrameFormat*>(pFlyFormat); + } + } + return nullptr; +} + +void SwDoc::SetFlyName( SwFlyFrameFormat& rFormat, const OUString& rName ) +{ + OUString sName( rName ); + if( sName.isEmpty() || FindFlyByName( sName ) ) + { + const char* pTyp = STR_FRAME_DEFNAME; + const SwNodeIndex* pIdx = rFormat.GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + switch( GetNodes()[ pIdx->GetIndex() + 1 ]->GetNodeType() ) + { + case SwNodeType::Grf: + pTyp = STR_GRAPHIC_DEFNAME; + break; + case SwNodeType::Ole: + pTyp = STR_OBJECT_DEFNAME; + break; + default: break; + } + } + sName = lcl_GetUniqueFlyName(this, pTyp, RES_FLYFRMFMT); + } + rFormat.SetName( sName, true ); + getIDocumentState().SetModified(); +} + +void SwDoc::SetAllUniqueFlyNames() +{ + sal_Int32 n, nFlyNum = 0, nGrfNum = 0, nOLENum = 0; + + const OUString sFlyNm(SwResId(STR_FRAME_DEFNAME)); + const OUString sGrfNm(SwResId(STR_GRAPHIC_DEFNAME)); + const OUString sOLENm(SwResId(STR_OBJECT_DEFNAME)); + + if( 255 < ( n = GetSpzFrameFormats()->size() )) + n = 255; + SwFrameFormatsV aArr; + aArr.reserve( n ); + SwFrameFormat* pFlyFormat; + bool bContainsAtPageObjWithContentAnchor = false; + + for( n = GetSpzFrameFormats()->size(); n; ) + { + pFlyFormat = (*GetSpzFrameFormats())[ --n ]; + if( RES_FLYFRMFMT == pFlyFormat->Which() ) + { + const OUString& aNm = pFlyFormat->GetName(); + if ( !aNm.isEmpty() ) + { + sal_Int32 *pNum = nullptr; + sal_Int32 nLen = 0; + if ( aNm.startsWith(sGrfNm) ) + { + nLen = sGrfNm.getLength(); + pNum = &nGrfNum; + } + else if( aNm.startsWith(sFlyNm) ) + { + nLen = sFlyNm.getLength(); + pNum = &nFlyNum; + } + else if( aNm.startsWith(sOLENm) ) + { + nLen = sOLENm.getLength(); + pNum = &nOLENum; + } + + if ( pNum ) + { + const sal_Int32 nNewLen = aNm.copy( nLen ).toInt32(); + if (*pNum < nNewLen) + *pNum = nNewLen; + } + } + else + // we want to set that afterwards + aArr.push_back( pFlyFormat ); + + } + if ( !bContainsAtPageObjWithContentAnchor ) + { + const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor(); + if ( (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) && + rAnchor.GetContentAnchor() ) + { + bContainsAtPageObjWithContentAnchor = true; + } + } + } + SetContainsAtPageObjWithContentAnchor( bContainsAtPageObjWithContentAnchor ); + + for( n = aArr.size(); n; ) + { + pFlyFormat = aArr[ --n ]; + const SwNodeIndex* pIdx = pFlyFormat->GetContent().GetContentIdx(); + if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() ) + { + switch( GetNodes()[ pIdx->GetIndex() + 1 ]->GetNodeType() ) + { + case SwNodeType::Grf: + pFlyFormat->SetName( sGrfNm + OUString::number( ++nGrfNum )); + break; + case SwNodeType::Ole: + pFlyFormat->SetName( sOLENm + OUString::number( ++nOLENum )); + break; + default: + pFlyFormat->SetName( sFlyNm + OUString::number( ++nFlyNum )); + break; + } + } + } + aArr.clear(); + + if( !GetFootnoteIdxs().empty() ) + { + SwTextFootnote::SetUniqueSeqRefNo( *this ); + // #i52775# Chapter footnotes did not get updated correctly. + // Calling UpdateAllFootnote() instead of UpdateFootnote() solves this problem, + // but I do not dare to call UpdateAllFootnote() in all cases: Safety first. + if ( FTNNUM_CHAPTER == GetFootnoteInfo().m_eNum ) + { + GetFootnoteIdxs().UpdateAllFootnote(); + } + else + { + SwNodeIndex aTmp( GetNodes() ); + GetFootnoteIdxs().UpdateFootnote( aTmp ); + } + } +} + +bool SwDoc::IsInHeaderFooter( const SwNodeIndex& rIdx ) const +{ + // That can also be a Fly in a Fly in the Header. + // Is also used by sw3io, to determine if a Redline object is + // in the Header or Footer. + // Because Redlines are also attached to Start and EndNode, + // the Index must not necessarily be from a ContentNode. + SwNode* pNd = &rIdx.GetNode(); + const SwNode* pFlyNd = pNd->FindFlyStartNode(); + while( pFlyNd ) + { + // get up by using the Anchor +#if OSL_DEBUG_LEVEL > 0 + std::vector<const SwFrameFormat*> checkFormats; + for( auto pFormat : *GetSpzFrameFormats() ) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + checkFormats.push_back( pFormat ); + } +#endif + std::vector<SwFrameFormat*> const*const pFlys(pFlyNd->GetAnchoredFlys()); + bool bFound(false); + for (size_t i = 0; pFlys && i < pFlys->size(); ++i) + { + const SwFrameFormat *const pFormat = (*pFlys)[i]; + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { +#if OSL_DEBUG_LEVEL > 0 + auto checkPos = std::find( + checkFormats.begin(), checkFormats.end(), pFormat ); + assert( checkPos != checkFormats.end()); + checkFormats.erase( checkPos ); +#endif + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) || + !rAnchor.GetContentAnchor() ) + { + return false; + } + + pNd = &rAnchor.GetContentAnchor()->nNode.GetNode(); + pFlyNd = pNd->FindFlyStartNode(); + bFound = true; + break; + } + } + if (!bFound) + { + OSL_ENSURE(mbInReading, "Found a FlySection but not a Format!"); + return false; + } + } + + return nullptr != pNd->FindHeaderStartNode() || + nullptr != pNd->FindFooterStartNode(); +} + +SvxFrameDirection SwDoc::GetTextDirection( const SwPosition& rPos, + const Point* pPt ) const +{ + SvxFrameDirection nRet = SvxFrameDirection::Unknown; + + SwContentNode *pNd = rPos.nNode.GetNode().GetContentNode(); + + // #i42921# - use new method <SwContentNode::GetTextDirection(..)> + if ( pNd ) + { + nRet = pNd->GetTextDirection( rPos, pPt ); + } + if ( nRet == SvxFrameDirection::Unknown ) + { + const SvxFrameDirectionItem* pItem = nullptr; + if( pNd ) + { + // Are we in a FlyFrame? Then look at that for the correct attribute + const SwFrameFormat* pFlyFormat = pNd->GetFlyFormat(); + while( pFlyFormat ) + { + pItem = &pFlyFormat->GetFrameDir(); + if( SvxFrameDirection::Environment == pItem->GetValue() ) + { + pItem = nullptr; + const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && + pAnchor->GetContentAnchor()) + { + pFlyFormat = pAnchor->GetContentAnchor()->nNode. + GetNode().GetFlyFormat(); + } + else + pFlyFormat = nullptr; + } + else + pFlyFormat = nullptr; + } + + if( !pItem ) + { + const SwPageDesc* pPgDsc = pNd->FindPageDesc(); + if( pPgDsc ) + pItem = &pPgDsc->GetMaster().GetFrameDir(); + } + } + if( !pItem ) + pItem = &GetAttrPool().GetDefaultItem( RES_FRAMEDIR ); + nRet = pItem->GetValue(); + } + return nRet; +} + +bool SwDoc::IsInVerticalText( const SwPosition& rPos ) const +{ + const SvxFrameDirection nDir = GetTextDirection( rPos ); + return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir; +} + +o3tl::sorted_vector<SwRootFrame*> SwDoc::GetAllLayouts() +{ + o3tl::sorted_vector<SwRootFrame*> aAllLayouts; + SwViewShell *pStart = getIDocumentLayoutAccess().GetCurrentViewShell(); + if(pStart) + { + for(const SwViewShell& rShell : pStart->GetRingContainer()) + { + if(rShell.GetLayout()) + aAllLayouts.insert(rShell.GetLayout()); + } + } + return aAllLayouts; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docnew.cxx b/sw/source/core/doc/docnew.cxx new file mode 100644 index 000000000..358887b21 --- /dev/null +++ b/sw/source/core/doc/docnew.cxx @@ -0,0 +1,1282 @@ +/* -*- 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_features.h> + +#include <o3tl/sorted_vector.hxx> + +#include <doc.hxx> +#include <proofreadingiterator.hxx> +#include <com/sun/star/text/XFlatParagraphIteratorProvider.hpp> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/random.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/XmlIdRegistry.hxx> +#include <sal/log.hxx> + +#include <sfx2/linkmgr.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <svl/zforlist.hxx> +#include <unotools/lingucfg.hxx> +#include <svx/svdpage.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <fmtfordr.hxx> +#include <fmtpdsc.hxx> +#include <pvprtdat.hxx> +#include <rootfrm.hxx> +#include <pagedesc.hxx> +#include <ndtxt.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <charfmt.hxx> +#include <frmfmt.hxx> +#include <poolfmt.hxx> +#include <dbmgr.hxx> +#include <docsh.hxx> +#include <acorrect.hxx> +#include <visiturl.hxx> +#include <docary.hxx> +#include <lineinfo.hxx> +#include <drawdoc.hxx> +#include <extinput.hxx> +#include <viewsh.hxx> +#include <doctxm.hxx> +#include <shellres.hxx> +#include <laycache.hxx> +#include <mvsave.hxx> +#include <istyleaccess.hxx> +#include "swstylemanager.hxx" +#include <IGrammarContact.hxx> +#include <tblafmt.hxx> +#include <MarkManager.hxx> +#include <UndoManager.hxx> +#include <DocumentDeviceManager.hxx> +#include <DocumentSettingManager.hxx> +#include <DocumentDrawModelManager.hxx> +#include <DocumentChartDataProviderManager.hxx> +#include <DocumentTimerManager.hxx> +#include <DocumentLinksAdministrationManager.hxx> +#include <DocumentListItemsManager.hxx> +#include <DocumentListsManager.hxx> +#include <DocumentOutlineNodesManager.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <DocumentRedlineManager.hxx> +#include <DocumentFieldsManager.hxx> +#include <DocumentStatisticsManager.hxx> +#include <DocumentStateManager.hxx> +#include <DocumentLayoutManager.hxx> +#include <DocumentStylePoolManager.hxx> +#include <DocumentExternalDataManager.hxx> +#include <wrtsh.hxx> +#include <unocrsr.hxx> +#include <fmthdft.hxx> +#include <frameformats.hxx> + +#include <numrule.hxx> + +#include <sfx2/Metadatable.hxx> +#include <fmtmeta.hxx> + +#include <svx/xfillit0.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::document; + +/* + * global functions... + */ + uno::Reference< linguistic2::XProofreadingIterator > const & SwDoc::GetGCIterator() const +{ + if (!m_xGCIterator.is() && SvtLinguConfig().HasGrammarChecker()) + { + uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); + try + { + m_xGCIterator = sw::proofreadingiterator::get( xContext ); + } + catch (const uno::Exception &) + { + OSL_FAIL( "No GCIterator" ); + } + } + + return m_xGCIterator; +} + +bool SwDoc::StartGrammarChecking( bool bSkipStart ) +{ + // check for a visible view + bool bVisible = false; + bool bStarted = false; + const SwDocShell *pDocShell = GetDocShell(); + SfxViewFrame *pFrame = SfxViewFrame::GetFirst( pDocShell, false ); + while (pFrame && !bVisible) + { + if (pFrame->IsVisible()) + bVisible = true; + pFrame = SfxViewFrame::GetNext( *pFrame, pDocShell, false ); + } + + //!! only documents with visible views need to be checked + //!! (E.g. don't check temporary documents created for printing, see printing of notes and selections. + //!! Those get created on the fly and get hard deleted a bit later as well, and no one should have + //!! a UNO reference to them) + if (bVisible) + { + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( GetGCIterator() ); + if ( xGCIterator.is() ) + { + uno::Reference< lang::XComponent > xDoc = GetDocShell()->GetBaseModel(); + uno::Reference< text::XFlatParagraphIteratorProvider > xFPIP( xDoc, uno::UNO_QUERY ); + + // start automatic background checking if not active already + if ( xFPIP.is() && !xGCIterator->isProofreading( xDoc ) ) + { + bStarted = true; + if ( !bSkipStart ) + { + for (auto pLayout : GetAllLayouts()) + { // we're starting it now, don't start grammar checker + // again until the user modifies the document + pLayout->SetNeedGrammarCheck(false); + } + xGCIterator->startProofreading( xDoc, xFPIP ); + } + } + } + } + + return bStarted; +} + +/* + * internal functions + */ +static void lcl_DelFormatIndices( SwFormat const * pFormat ) +{ + SwFormatContent &rFormatContent = const_cast<SwFormatContent&>(pFormat->GetContent()); + if ( rFormatContent.GetContentIdx() ) + rFormatContent.SetNewContentIdx( nullptr ); + SwFormatAnchor &rFormatAnchor = const_cast<SwFormatAnchor&>(pFormat->GetAnchor()); + if ( rFormatAnchor.GetContentAnchor() ) + rFormatAnchor.SetAnchor( nullptr ); +} + +/* + * exported methods + */ +SwDoc::SwDoc() + : m_pNodes( new SwNodes(this) ), + mpAttrPool(new SwAttrPool(this)), + mpMarkManager(new ::sw::mark::MarkManager(*this)), + m_pMetaFieldManager(new ::sw::MetaFieldManager()), + m_pDocumentDrawModelManager( new ::sw::DocumentDrawModelManager( *this ) ), + m_pDocumentRedlineManager( new ::sw::DocumentRedlineManager( *this ) ), + m_pDocumentStateManager( new ::sw::DocumentStateManager( *this ) ), + m_pUndoManager(new ::sw::UndoManager( + std::shared_ptr<SwNodes>(new SwNodes(this)), *m_pDocumentDrawModelManager, *m_pDocumentRedlineManager, *m_pDocumentStateManager)), + m_pDocumentSettingManager(new ::sw::DocumentSettingManager(*this)), + m_pDocumentChartDataProviderManager( new sw::DocumentChartDataProviderManager( *this ) ), + m_pDeviceAccess( new ::sw::DocumentDeviceManager( *this ) ), + m_pDocumentTimerManager( new ::sw::DocumentTimerManager( *this ) ), + m_pDocumentLinksAdministrationManager( new ::sw::DocumentLinksAdministrationManager( *this ) ), + m_pDocumentListItemsManager( new ::sw::DocumentListItemsManager() ), + m_pDocumentListsManager( new ::sw::DocumentListsManager( *this ) ), + m_pDocumentOutlineNodesManager( new ::sw::DocumentOutlineNodesManager( *this ) ), + m_pDocumentContentOperationsManager( new ::sw::DocumentContentOperationsManager( *this ) ), + m_pDocumentFieldsManager( new ::sw::DocumentFieldsManager( *this ) ), + m_pDocumentStatisticsManager( new ::sw::DocumentStatisticsManager( *this ) ), + m_pDocumentLayoutManager( new ::sw::DocumentLayoutManager( *this ) ), + m_pDocumentStylePoolManager( new ::sw::DocumentStylePoolManager( *this ) ), + m_pDocumentExternalDataManager( new ::sw::DocumentExternalDataManager ), + mpDfltFrameFormat( new SwFrameFormat( GetAttrPool(), "Frameformat", nullptr ) ), + mpEmptyPageFormat( new SwFrameFormat( GetAttrPool(), "Empty Page", mpDfltFrameFormat.get() ) ), + mpColumnContFormat( new SwFrameFormat( GetAttrPool(), "Columncontainer", mpDfltFrameFormat.get() ) ), + mpDfltCharFormat( new SwCharFormat( GetAttrPool(), "Character style", nullptr ) ), + mpDfltTextFormatColl( new SwTextFormatColl( GetAttrPool(), "Paragraph style" ) ), + mpDfltGrfFormatColl( new SwGrfFormatColl( GetAttrPool(), "Graphikformatvorlage" ) ), + mpFrameFormatTable( new SwFrameFormats() ), + mpCharFormatTable( new SwCharFormats ), + mpSpzFrameFormatTable( new SwFrameFormats() ), + mpSectionFormatTable( new SwSectionFormats ), + mpTableFrameFormatTable( new SwFrameFormats() ), + mpTextFormatCollTable( new SwTextFormatColls() ), + mpGrfFormatCollTable( new SwGrfFormatColls() ), + mpTOXTypes( new SwTOXTypes ), + mpDefTOXBases( new SwDefTOXBase_Impl() ), + mpOutlineRule( nullptr ), + mpFootnoteInfo( new SwFootnoteInfo ), + mpEndNoteInfo( new SwEndNoteInfo ), + mpLineNumberInfo( new SwLineNumberInfo ), + mpFootnoteIdxs( new SwFootnoteIdxs ), + mpDocShell( nullptr ), + mpNumberFormatter( nullptr ), + mpNumRuleTable( new SwNumRuleTable ), + mpExtInputRing( nullptr ), + mpGrammarContact(createGrammarContact()), + mpCellStyles(new SwCellStyleTable), + m_pXmlIdRegistry(), + mReferenceCount(0), + mbDtor(false), + mbCopyIsMove(false), + mbInReading(false), + mbInWriting(false), + mbInMailMerge(false), + mbInXMLImport(false), + mbInWriterfilterImport(false), + mbUpdateTOX(false), + mbInLoadAsynchron(false), + mbIsAutoFormatRedline(false), + mbOLEPrtNotifyPending(false), + mbAllOLENotify(false), + mbInsOnlyTextGlssry(false), + mbContains_MSVBasic(false), + mbClipBoard( false ), + mbColumnSelection( false ), + mbIsPrepareSelAll(false), + meDictionaryMissing( MissingDictionary::Undefined ), + mbContainsAtPageObjWithContentAnchor(false), //#i119292#, fdo#37024 + + meDocType(DOCTYPE_NATIVE) +{ + // The DrawingLayer ItemPool which is used as 2nd pool for Writer documents' pool + // has a default for the XFillStyleItem of XFILL_SOLID and the color for it is the default + // fill color (blue7 or similar). This is a problem, in Writer we want the default fill + // style to be drawing::FillStyle_NONE. This cannot simply be done by changing it in the 2nd pool at the + // pool defaults when the DrawingLayer ItemPool is used for Writer, that would lead to + // countless problems like DrawObjects initial fill and others. + // It is also hard to find all places where the initial ItemSets for Writer (including + // style hierarchies) are created and to always set (but only at the root) the FillStyle + // to NONE fixed; that will add that attribute to the file format. It will be hard to reset + // attribute sets (which is done at import and using UI). Also not a good solution. + // Luckily Writer uses pDfltTextFormatColl as default parent for all paragraphs and similar, thus + // it is possible to set this attribute here. It will be not reset when importing. + mpDfltTextFormatColl->SetFormatAttr(XFillStyleItem(drawing::FillStyle_NONE)); + mpDfltFrameFormat->SetFormatAttr(XFillStyleItem(drawing::FillStyle_NONE)); + // prevent paragraph default margins being applied to everything + mpDfltFrameFormat->SetFormatAttr(SvxULSpaceItem(RES_UL_SPACE)); + mpDfltFrameFormat->SetFormatAttr(SvxLRSpaceItem(RES_LR_SPACE)); + + /* + * DefaultFormats and DefaultFormatCollections (FormatColl) + * are inserted at position 0 at the respective array. + * The formats in the FormatColls are derived from the + * DefaultFormats and are also in the list. + */ + /* Formats */ + mpFrameFormatTable->push_back(mpDfltFrameFormat.get()); + mpCharFormatTable->push_back(mpDfltCharFormat.get()); + + /* FormatColls */ + // TXT + mpTextFormatCollTable->push_back(mpDfltTextFormatColl.get()); + // GRF + mpGrfFormatCollTable->push_back(mpDfltGrfFormatColl.get()); + + // Create PageDesc, EmptyPageFormat and ColumnFormat + if (m_PageDescs.empty()) + getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + + // Set to "Empty Page" + mpEmptyPageFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Fixed ) ); + // Set BodyFormat for columns + mpColumnContFormat->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ) ); + + GetDocumentFieldsManager().InitFieldTypes(); + + // Create a default OutlineNumRule (for Filters) + mpOutlineRule = new SwNumRule( SwNumRule::GetOutlineRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode(), + OUTLINE_RULE ); + AddNumRule(mpOutlineRule); + // Counting of phantoms depends on <IsOldNumbering()> + mpOutlineRule->SetCountPhantoms( !GetDocumentSettingManager().get(DocumentSettingId::OLD_NUMBERING) ); + + new SwTextNode( + SwNodeIndex(GetUndoManager().GetUndoNodes().GetEndOfContent()), + mpDfltTextFormatColl.get() ); + new SwTextNode( SwNodeIndex( GetNodes().GetEndOfContent() ), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + + maOLEModifiedIdle.SetPriority( TaskPriority::LOWEST ); + maOLEModifiedIdle.SetInvokeHandler( LINK( this, SwDoc, DoUpdateModifiedOLE )); + maOLEModifiedIdle.SetDebugName( "sw::SwDoc maOLEModifiedIdle" ); + +#if HAVE_FEATURE_DBCONNECTIVITY + // Create DBManager + m_pOwnDBManager.reset(new SwDBManager(this)); + m_pDBManager = m_pOwnDBManager.get(); +#else + m_pDBManager = nullptr; +#endif + + // create TOXTypes + InitTOXTypes(); + + // pass empty item set containing the paragraph's list attributes + // as ignorable items to the stype manager. + { + SfxItemSet aIgnorableParagraphItems( GetAttrPool(), svl::Items<RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1>{}); + mpStyleAccess = createStyleManager( &aIgnorableParagraphItems ); + } + + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + if (bHack) + { + mnRsid = 0; + } + else + { + // Initialize the session id of the current document to a random number + // smaller than 2^21. + mnRsid = comphelper::rng::uniform_uint_distribution(1, (1 << 21) - 1); + } + mnRsidRoot = mnRsid; + + getIDocumentState().ResetModified(); +} + +/** + * Speciality: a member of the class SwDoc is located at + * position 0 in the array of the Format and GDI objects. + * This MUST not be destroyed using 'delete' in any case! + */ +SwDoc::~SwDoc() +{ + // nothing here should create Undo actions! + GetIDocumentUndoRedo().DoUndo(false); + + if (mpDocShell) + { + mpDocShell->SetUndoManager(nullptr); + } + + mpGrammarContact.reset(); + + getIDocumentTimerAccess().StopIdling(); // stop idle timer + + mpURLStateChgd.reset(); + + // Deactivate Undo notification from Draw + if( GetDocumentDrawModelManager().GetDrawModel() ) + { + GetDocumentDrawModelManager().DrawNotifyUndoHdl(); + ClrContourCache(); + } + + m_pPgPViewPrtData.reset(); + + mbDtor = true; + + //Clear the redline table before the nodes array is destroyed + getIDocumentRedlineAccess().GetRedlineTable().DeleteAndDestroyAll(); + getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAndDestroyAll(); + + const sw::UnoCursorHint aHint; + cleanupUnoCursorTable(); + for(const auto& pWeakCursor : mvUnoCursorTable) + { + auto pCursor(pWeakCursor.lock()); + if(pCursor) + pCursor->m_aNotifier.Broadcast(aHint); + } + mpACEWord.reset(); + + // Release the BaseLinks + { + ::sfx2::SvLinkSources aTemp(getIDocumentLinksAdministration().GetLinkManager().GetServers()); + for( const auto& rpLinkSrc : aTemp ) + rpLinkSrc->Closed(); + + if( !getIDocumentLinksAdministration().GetLinkManager().GetLinks().empty() ) + getIDocumentLinksAdministration().GetLinkManager().Remove( 0, getIDocumentLinksAdministration().GetLinkManager().GetLinks().size() ); + } + + // The ChapterNumbers/Numbers need to be deleted before the styles + // or we update all the time! + m_pNodes->m_pOutlineNodes->clear(); + SwNodes & rUndoNodes( GetUndoManager().GetUndoNodes() ); + rUndoNodes.m_pOutlineNodes->clear(); + + mpFootnoteIdxs->clear(); + + // indices could be registered in attributes + m_pUndoManager->DelAllUndoObj(); + + // The BookMarks contain indices to the Content. These must be deleted + // before deleting the Nodes. + mpMarkManager->clearAllMarks(); + + if( mpExtInputRing ) + { + SwPaM* pTmp = mpExtInputRing; + mpExtInputRing = nullptr; + while( pTmp->GetNext() != pTmp ) + delete pTmp->GetNext(); + delete pTmp; + } + + // Old - deletion without a Flag is expensive, because we send a Modify + // aTOXTypes.DeleteAndDestroy( 0, aTOXTypes.Count() ); + { + for( auto n = mpTOXTypes->size(); n; ) + { + (*mpTOXTypes)[ --n ]->SetInDocDTOR(); + (*mpTOXTypes)[ n ].reset(); + } + mpTOXTypes->clear(); + } + mpDefTOXBases.reset(); + + // Any of the FrameFormats can still have indices registered. + // These need to be destroyed now at the latest. + for( SwFrameFormat* pFormat : *mpFrameFormatTable ) + lcl_DelFormatIndices( pFormat ); + for( SwFrameFormat* pFormat : *mpSpzFrameFormatTable ) + lcl_DelFormatIndices( pFormat ); + for( SwSectionFormat* pFormat : *mpSectionFormatTable ) + lcl_DelFormatIndices( pFormat ); + + // The formats/styles that follow depend on the default formats. + // Destroy these only after destroying the FormatIndices, because the content + // of headers/footers has to be deleted as well. If in the headers/footers + // there are still Flys registered at that point, we have a problem. + for( SwPageDesc *pPageDesc : m_PageDescs ) + delete pPageDesc; + m_PageDescs.clear(); + + // Delete content selections. + // Don't wait for the SwNodes dtor to destroy them; so that Formats + // do not have any dependencies anymore. + m_pNodes->DelNodes( SwNodeIndex(*m_pNodes), m_pNodes->Count() ); + rUndoNodes.DelNodes( SwNodeIndex( rUndoNodes ), rUndoNodes.Count() ); + + // Delete Formats, make it permanent some time in the future + + // Delete for Collections + // So that we get rid of the dependencies + mpFootnoteInfo->EndListeningAll(); + mpEndNoteInfo->EndListeningAll(); + + assert(mpDfltTextFormatColl.get() == (*mpTextFormatCollTable)[0] + && "Default-Text-Collection must always be at the start"); + + // Optimization: Based on the fact that Standard is always 2nd in the + // array, we should delete it as the last. With this we avoid + // reparenting the Formats all the time! + if( 2 < mpTextFormatCollTable->size() ) + mpTextFormatCollTable->DeleteAndDestroy(2, mpTextFormatCollTable->size()); + mpTextFormatCollTable->DeleteAndDestroy(1, mpTextFormatCollTable->size()); + mpTextFormatCollTable.reset(); + + assert(mpDfltGrfFormatColl.get() == (*mpGrfFormatCollTable)[0] + && "DefaultGrfCollection must always be at the start"); + + mpGrfFormatCollTable->DeleteAndDestroy(1, mpGrfFormatCollTable->size()); + mpGrfFormatCollTable.reset(); + + // Without explicitly freeing the DocumentDeviceManager + // and relying on the implicit freeing there would be a crash + // due to it happening after SwAttrPool is freed. + m_pDeviceAccess.reset(); + + /* + * DefaultFormats and DefaultFormatCollections (FormatColl) + * are at position 0 of their respective arrays. + * In order to not be deleted by the array's dtor, we remove them + * now. + */ + mpFrameFormatTable->erase( mpFrameFormatTable->begin() ); + mpCharFormatTable->erase( mpCharFormatTable->begin() ); + +#if HAVE_FEATURE_DBCONNECTIVITY + // On load, SwDBManager::setEmbeddedName() may register a data source. + // If we have an embedded one, then sDataSource points to the registered name, so revoke it here. + if (!m_pOwnDBManager->getEmbeddedName().isEmpty() && !maDBData.sDataSource.isEmpty()) + { + // Remove the revoke listener here first, so that we don't remove the data source from the document. + m_pOwnDBManager->releaseRevokeListener(); + SwDBManager::RevokeDataSource(maDBData.sDataSource); + SwDBManager::RevokeDataSource(m_pOwnDBManager->getEmbeddedName()); + } + else if (!m_pOwnDBManager->getEmbeddedName().isEmpty()) + { + // Remove the revoke listener here first, so that we don't remove the data source from the document. + m_pOwnDBManager->releaseRevokeListener(); + // Remove connections which was committed but not used. + m_pOwnDBManager->RevokeNotUsedConnections(); + } + + m_pOwnDBManager.reset(); +#endif + + // All Flys need to be destroyed before the Drawing Model, + // because Flys can still contain DrawContacts, when no + // Layout could be constructed due to a read error. + mpSpzFrameFormatTable->DeleteAndDestroyAll(); + + // Only now destroy the Model, the drawing objects - which are also + // contained in the Undo - need to remove their attributes from the + // Model. Also, DrawContacts could exist before this. + GetDocumentDrawModelManager().ReleaseDrawModel(); + // Destroy DrawModel before the LinkManager, because it's always set + // in the DrawModel. + //The LinkManager gets destroyed automatically with m_pLinksAdministrationManager + + // Clear the Tables before deleting the defaults, or we crash due to + // dependencies on defaults. + mpFrameFormatTable.reset(); + mpSpzFrameFormatTable.reset(); + + mpStyleAccess.reset(); + + mpCharFormatTable.reset(); + mpSectionFormatTable.reset(); + mpTableFrameFormatTable.reset(); + mpDfltTextFormatColl.reset(); + mpDfltGrfFormatColl.reset(); + mpNumRuleTable.reset(); + + disposeXForms(); // #i113606#, dispose the XForms objects + + delete mpNumberFormatter.load(); mpNumberFormatter= nullptr; + mpFootnoteInfo.reset(); + mpEndNoteInfo.reset(); + mpLineNumberInfo.reset(); + mpFootnoteIdxs.reset(); + mpTOXTypes.reset(); + mpEmptyPageFormat.reset(); + mpColumnContFormat.reset(); + mpDfltCharFormat.reset(); + mpDfltFrameFormat.reset(); + mpLayoutCache.reset(); + + SfxItemPool::Free(mpAttrPool); +} + +void SwDoc::SetDocShell( SwDocShell* pDSh ) +{ + if( mpDocShell != pDSh ) + { + if (mpDocShell) + { + mpDocShell->SetUndoManager(nullptr); + } + mpDocShell = pDSh; + if (mpDocShell) + { + mpDocShell->SetUndoManager(& GetUndoManager()); + GetUndoManager().SetDocShell(mpDocShell); + } + + getIDocumentLinksAdministration().GetLinkManager().SetPersist( mpDocShell ); + + // set DocShell pointer also on DrawModel + InitDrawModelAndDocShell(mpDocShell, GetDocumentDrawModelManager().GetDrawModel()); + assert(!GetDocumentDrawModelManager().GetDrawModel() || + GetDocumentDrawModelManager().GetDrawModel()->GetPersist() == GetPersist()); + } +} + +// Convenience method; to avoid excessive includes from docsh.hxx +uno::Reference < embed::XStorage > SwDoc::GetDocStorage() +{ + if( mpDocShell ) + return mpDocShell->GetStorage(); + if( getIDocumentLinksAdministration().GetLinkManager().GetPersist() ) + return getIDocumentLinksAdministration().GetLinkManager().GetPersist()->GetStorage(); + return nullptr; +} + +SfxObjectShell* SwDoc::GetPersist() const +{ + return mpDocShell ? mpDocShell : getIDocumentLinksAdministration().GetLinkManager().GetPersist(); +} + +void SwDoc::ClearDoc() +{ + GetIDocumentUndoRedo().DelAllUndoObj(); + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Deactivate Undo notification from Draw + if( GetDocumentDrawModelManager().GetDrawModel() ) + { + GetDocumentDrawModelManager().DrawNotifyUndoHdl(); + ClrContourCache(); + } + + // if there are still FlyFrames dangling around, delete them too + while ( !mpSpzFrameFormatTable->empty() ) + getIDocumentLayoutAccess().DelLayoutFormat((*mpSpzFrameFormatTable)[mpSpzFrameFormatTable->size()-1]); + assert(!GetDocumentDrawModelManager().GetDrawModel() + || !GetDocumentDrawModelManager().GetDrawModel()->GetPage(0)->GetObjCount()); + + getIDocumentRedlineAccess().GetRedlineTable().DeleteAndDestroyAll(); + getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteAndDestroyAll(); + + mpACEWord.reset(); + + // The BookMarks contain indices to the Content. These must be deleted + // before deleting the Nodes. + mpMarkManager->clearAllMarks(); + InitTOXTypes(); + + // create a dummy pagedesc for the layout + SwPageDesc* pDummyPgDsc = MakePageDesc("?DUMMY?"); + + SwNodeIndex aSttIdx( *GetNodes().GetEndOfContent().StartOfSectionNode(), 1 ); + // create the first one over and over again (without attributes/style etc. + SwTextNode* pFirstNd = GetNodes().MakeTextNode( aSttIdx, mpDfltTextFormatColl.get() ); + + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // set the layout to the dummy pagedesc + pFirstNd->SetAttr( SwFormatPageDesc( pDummyPgDsc )); + + SwPosition aPos( *pFirstNd, SwIndex( pFirstNd )); + SwPaM const tmpPaM(aSttIdx, SwNodeIndex(GetNodes().GetEndOfContent())); + ::PaMCorrAbs(tmpPaM, aPos); + } + + GetNodes().Delete( aSttIdx, + GetNodes().GetEndOfContent().GetIndex() - aSttIdx.GetIndex() ); + + // #i62440# + // destruction of numbering rules and creation of new outline rule + // *after* the document nodes are deleted. + mpOutlineRule = nullptr; + for( SwNumRule* pNumRule : *mpNumRuleTable ) + { + getIDocumentListsAccess().deleteListForListStyle(pNumRule->GetName()); + delete pNumRule; + } + mpNumRuleTable->clear(); + maNumRuleMap.clear(); + + // creation of new outline numbering rule + mpOutlineRule = new SwNumRule( SwNumRule::GetOutlineRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode(), + OUTLINE_RULE ); + AddNumRule(mpOutlineRule); + // Counting of phantoms depends on <IsOldNumbering()> + mpOutlineRule->SetCountPhantoms( !GetDocumentSettingManager().get(DocumentSettingId::OLD_NUMBERING) ); + + // remove the dummy pagedesc from the array and delete all the old ones + size_t nDummyPgDsc = 0; + if (FindPageDesc(pDummyPgDsc->GetName(), &nDummyPgDsc)) + m_PageDescs.erase( nDummyPgDsc ); + for( SwPageDesc *pPageDesc : m_PageDescs ) + delete pPageDesc; + m_PageDescs.clear(); + + // Delete for Collections + // So that we get rid of the dependencies + mpFootnoteInfo->EndListeningAll(); + mpEndNoteInfo->EndListeningAll(); + + // Optimization: Based on the fact that Standard is always 2nd in the + // array, we should delete it as the last. With this we avoid + // reparenting the Formats all the time! + if( 2 < mpTextFormatCollTable->size() ) + mpTextFormatCollTable->DeleteAndDestroy(2, mpTextFormatCollTable->size()); + mpTextFormatCollTable->DeleteAndDestroy(1, mpTextFormatCollTable->size()); + mpGrfFormatCollTable->DeleteAndDestroy(1, mpGrfFormatCollTable->size()); + mpCharFormatTable->DeleteAndDestroy(1, mpCharFormatTable->size()); + + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // search the FrameFormat of the root frm. This is not allowed to delete + mpFrameFormatTable->erase( getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout()->GetFormat() ); + mpFrameFormatTable->DeleteAndDestroyAll( true ); + mpFrameFormatTable->push_back( getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout()->GetFormat() ); + } + else + mpFrameFormatTable->DeleteAndDestroyAll( true ); + + GetDocumentFieldsManager().ClearFieldTypes(); + + delete mpNumberFormatter.load(); mpNumberFormatter= nullptr; + + getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_STANDARD ); + pFirstNd->ChgFormatColl( getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + nDummyPgDsc = m_PageDescs.size(); + m_PageDescs.push_back( pDummyPgDsc ); + // set the layout back to the new standard pagedesc + pFirstNd->ResetAllAttr(); + // delete now the dummy pagedesc + DelPageDesc( nDummyPgDsc ); +} + +void SwDoc::SetPreviewPrtData( const SwPagePreviewPrtData* pNew ) +{ + if( pNew ) + { + if (m_pPgPViewPrtData) + { + *m_pPgPViewPrtData = *pNew; + } + else + { + m_pPgPViewPrtData.reset(new SwPagePreviewPrtData(*pNew)); + } + } + else if (m_pPgPViewPrtData) + { + m_pPgPViewPrtData.reset(); + } + getIDocumentState().SetModified(); +} + +void SwDoc::SetOLEObjModified() +{ + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) maOLEModifiedIdle.Start(); +} + +/** SwDoc: Reading and writing of the layout cache. */ +void SwDoc::ReadLayoutCache( SvStream& rStream ) +{ + if( !mpLayoutCache ) + mpLayoutCache.reset( new SwLayoutCache() ); + if( !mpLayoutCache->IsLocked() ) + { + mpLayoutCache->GetLockCount() |= 0x8000; + mpLayoutCache->Read( rStream ); + mpLayoutCache->GetLockCount() &= 0x7fff; + } +} + +void SwDoc::WriteLayoutCache( SvStream& rStream ) +{ + SwLayoutCache::Write( rStream, *this ); +} + +IGrammarContact* getGrammarContact( const SwTextNode& rTextNode ) +{ + const SwDoc* pDoc = rTextNode.GetDoc(); + if( !pDoc || pDoc->IsInDtor() ) + return nullptr; + return pDoc->getGrammarContact(); +} + +::sfx2::IXmlIdRegistry& +SwDoc::GetXmlIdRegistry() +{ + // UGLY: this relies on SetClipBoard being called before GetXmlIdRegistry! + if (!m_pXmlIdRegistry) + { + m_pXmlIdRegistry.reset( ::sfx2::createXmlIdRegistry( IsClipBoard() ) ); + } + return *m_pXmlIdRegistry; +} + +void SwDoc::InitTOXTypes() +{ + ShellResource* pShellRes = SwViewShell::GetShellRes(); + SwTOXType* pNew = new SwTOXType(*this, TOX_CONTENT, pShellRes->aTOXContentName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_INDEX, pShellRes->aTOXIndexName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_USER, pShellRes->aTOXUserName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_ILLUSTRATIONS, pShellRes->aTOXIllustrationsName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_OBJECTS, pShellRes->aTOXObjectsName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_TABLES, pShellRes->aTOXTablesName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_AUTHORITIES, pShellRes->aTOXAuthoritiesName); + mpTOXTypes->emplace_back( pNew ); + pNew = new SwTOXType(*this, TOX_CITATION, pShellRes->aTOXCitationName); + mpTOXTypes->emplace_back( pNew ); +} + +void SwDoc::ReplaceDefaults(const SwDoc& rSource) +{ + // copy property defaults + const sal_uInt16 aRangeOfDefaults[] = + { + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + XATTR_START, XATTR_END-1, + 0 + }; + + SfxItemSet aNewDefaults(GetAttrPool(), aRangeOfDefaults); + + for (auto nRange = 0; aRangeOfDefaults[nRange] != 0; nRange += 2) + { + for (sal_uInt16 nWhich = aRangeOfDefaults[nRange]; + nWhich <= aRangeOfDefaults[nRange + 1]; ++nWhich) + { + const SfxPoolItem& rSourceAttr = + rSource.mpAttrPool->GetDefaultItem(nWhich); + if (rSourceAttr != mpAttrPool->GetDefaultItem(nWhich)) + aNewDefaults.Put(rSourceAttr); + } + } + + if (aNewDefaults.Count()) + SetDefault(aNewDefaults); +} + +void SwDoc::ReplaceCompatibilityOptions(const SwDoc& rSource) +{ + m_pDocumentSettingManager->ReplaceCompatibilityOptions(rSource.GetDocumentSettingManager()); +} + +#ifdef DBG_UTIL +#define CNTNT_DOC( doc ) \ + ((doc)->GetNodes().GetEndOfContent().GetIndex() - (doc)->GetNodes().GetEndOfExtras().GetIndex() - 2) +#define CNTNT_IDX( idx ) \ + ((idx).GetNode().GetIndex() - GetNodes().GetEndOfExtras().GetIndex() - 1) +#endif + +SfxObjectShell* SwDoc::CreateCopy( bool bCallInitNew, bool bEmpty ) const +{ + SAL_INFO( "sw.pageframe", "(SwDoc::CreateCopy in" ); + rtl::Reference<SwDoc> xRet( new SwDoc ); + + // we have to use pointer here, since the callee has to decide whether + // SfxObjectShellLock or SfxObjectShellRef should be used sometimes the + // object will be returned with refcount set to 0 ( if no DoInitNew is done ) + SfxObjectShell* pRetShell = new SwDocShell( xRet.get(), SfxObjectCreateMode::STANDARD ); + if( bCallInitNew ) + { + // it could happen that DoInitNew creates model, + // that increases the refcount of the object + pRetShell->DoInitNew(); + } + + xRet->ReplaceDefaults(*this); + + xRet->ReplaceCompatibilityOptions(*this); + + xRet->ReplaceStyles(*this); + + if( !bEmpty ) + { +#ifdef DBG_UTIL + SAL_INFO( "sw.createcopy", "CC-Nd-Src: " << CNTNT_DOC( this ) ); + SAL_INFO( "sw.createcopy", "CC-Nd: " << CNTNT_DOC( xRet ) ); +#endif + xRet->AppendDoc(*this, 0, bCallInitNew, 0, 0); +#ifdef DBG_UTIL + SAL_INFO( "sw.createcopy", "CC-Nd: " << CNTNT_DOC( xRet ) ); +#endif + } + + // remove the temporary shell if it is there as it was done before + xRet->SetTmpDocShell( nullptr ); + + SAL_INFO( "sw.pageframe", "SwDoc::CreateCopy out)" ); + return pRetShell; +} + +// save bulk letters as single documents +static OUString lcl_FindUniqueName(SwWrtShell* pTargetShell, const OUString& rStartingPageDesc, sal_uLong nDocNo ) +{ + do + { + OUString sTest = rStartingPageDesc + OUString::number( nDocNo ); + if( !pTargetShell->FindPageDescByName( sTest ) ) + return sTest; + ++nDocNo; + } + while( true ); +} + +/** Returns whether the passed SwPageDesc& or any of its (transitive) follows + contains a header or footer. */ +static bool lcl_PageDescOrFollowContainsHeaderFooter(const SwPageDesc& rPageDesc) +{ + // remember already checked page descs to avoid cycle + o3tl::sorted_vector<const SwPageDesc*> aCheckedPageDescs; + const SwPageDesc* pCurPageDesc = &rPageDesc; + while (aCheckedPageDescs.count(pCurPageDesc) == 0) + { + const SwFrameFormat& rMaster = pCurPageDesc->GetMaster(); + if (rMaster.GetHeader().IsActive() || rMaster.GetFooter().IsActive()) + return true; + + aCheckedPageDescs.insert(pCurPageDesc); + pCurPageDesc = pCurPageDesc->GetFollow(); + } + return false; +} + +static void lcl_CopyFollowPageDesc( + SwWrtShell& rTargetShell, + const SwPageDesc& rSourcePageDesc, + const SwPageDesc& rTargetPageDesc, + const sal_uLong nDocNo ) +{ + //now copy the follow page desc, too + // note: these may at any point form a cycle, so a loop is needed and it + // must be detected that the last iteration closes the cycle and doesn't + // copy the first page desc of the cycle again. + std::map<OUString, OUString> followMap{ { rSourcePageDesc.GetName(), rTargetPageDesc.GetName() } }; + SwPageDesc const* pCurSourcePageDesc(&rSourcePageDesc); + SwPageDesc const* pCurTargetPageDesc(&rTargetPageDesc); + do + { + const SwPageDesc* pFollowPageDesc = pCurSourcePageDesc->GetFollow(); + OUString sFollowPageDesc = pFollowPageDesc->GetName(); + if (sFollowPageDesc == pCurSourcePageDesc->GetName()) + { + break; + } + SwDoc* pTargetDoc = rTargetShell.GetDoc(); + SwPageDesc* pTargetFollowPageDesc(nullptr); + auto const itMapped(followMap.find(sFollowPageDesc)); + if (itMapped == followMap.end()) + { + OUString sNewFollowPageDesc = lcl_FindUniqueName(&rTargetShell, sFollowPageDesc, nDocNo); + pTargetFollowPageDesc = pTargetDoc->MakePageDesc(sNewFollowPageDesc); + pTargetDoc->CopyPageDesc(*pFollowPageDesc, *pTargetFollowPageDesc, false); + } + else + { + pTargetFollowPageDesc = pTargetDoc->FindPageDesc(itMapped->second); + } + SwPageDesc aDesc(*pCurTargetPageDesc); + aDesc.SetFollow(pTargetFollowPageDesc); + pTargetDoc->ChgPageDesc(pCurTargetPageDesc->GetName(), aDesc); + if (itMapped != followMap.end()) + { + break; // was already copied + } + pCurSourcePageDesc = pCurSourcePageDesc->GetFollow(); + pCurTargetPageDesc = pTargetFollowPageDesc; + followMap[pCurSourcePageDesc->GetName()] = pCurTargetPageDesc->GetName(); + } + while (true); +} + +// appends all pages of source SwDoc - based on SwFEShell::Paste( SwDoc* ) +SwNodeIndex SwDoc::AppendDoc(const SwDoc& rSource, sal_uInt16 const nStartPageNumber, + bool const bDeletePrevious, int pageOffset, const sal_uLong nDocNo) +{ + SAL_INFO( "sw.pageframe", "(SwDoc::AppendDoc in " << bDeletePrevious ); + + // GetEndOfExtras + 1 = StartOfContent == no content node! + // This ensures it won't be merged in the SwTextNode at the position. + SwNodeIndex aSourceIdx( rSource.GetNodes().GetEndOfExtras(), 1 ); + // CopyRange works on the range a [mark, point[ and considers an + // index < point outside the selection. + // @see IDocumentContentOperations::CopyRange + SwNodeIndex aSourceEndIdx( rSource.GetNodes().GetEndOfContent(), 0 ); + SwPaM aCpyPam( aSourceIdx, aSourceEndIdx ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceIdx.GetNode().GetIndex() ); + aSourceIdx++; + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceIdx.GetNode().GetIndex() ); + if ( aSourceIdx.GetNode().GetNodeType() != SwNodeType::End ) { + aSourceIdx++; + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceIdx.GetNode().GetNodeType()) << std::dec ); + aSourceIdx--; + } + aSourceIdx--; + SAL_INFO( "sw.docappend", ".." ); + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceEndIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceEndIdx.GetNode().GetIndex() ); + SAL_INFO( "sw.docappend", "NodeType 0x" << std::hex << static_cast<int>(aSourceEndIdx.GetNode().GetNodeType()) + << std::dec << " " << aSourceEndIdx.GetNode().GetIndex() ); + SAL_INFO( "sw.docappend", "Src-Nd: " << CNTNT_DOC( &rSource ) ); + SAL_INFO( "sw.docappend", "Nd: " << CNTNT_DOC( this ) ); +#endif + + SwWrtShell* pTargetShell = GetDocShell()->GetWrtShell(); + SwPageDesc* pTargetPageDesc = nullptr; + + if ( pTargetShell ) { +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Has target write shell" ); +#endif + pTargetShell->StartAllAction(); + + if( nDocNo > 0 ) + { + // #i72517# put the styles to the target document + // if the source uses headers or footers the target document + // needs inidividual page styles + const SwWrtShell *pSourceShell = rSource.GetDocShell()->GetWrtShell(); + const SwPageDesc& rSourcePageDesc = pSourceShell->GetPageDesc( + pSourceShell->GetCurPageDesc()); + const OUString sStartingPageDesc = rSourcePageDesc.GetName(); + const bool bPageStylesWithHeaderFooter = lcl_PageDescOrFollowContainsHeaderFooter(rSourcePageDesc); + if( bPageStylesWithHeaderFooter ) + { + // create a new pagestyle + // copy the pagedesc from the current document to the new + // document and change the name of the to-be-applied style + OUString sNewPageDescName = lcl_FindUniqueName(pTargetShell, sStartingPageDesc, nDocNo ); + pTargetPageDesc = MakePageDesc( sNewPageDescName ); + if( pTargetPageDesc ) + { + CopyPageDesc( rSourcePageDesc, *pTargetPageDesc, false ); + lcl_CopyFollowPageDesc( *pTargetShell, rSourcePageDesc, *pTargetPageDesc, nDocNo ); + } + } + else + pTargetPageDesc = pTargetShell->FindPageDescByName( sStartingPageDesc ); + } + + // Otherwise we have to handle SwPlaceholderNodes as first node + if ( pTargetPageDesc ) + { + SwNodeIndex aBreakIdx( GetNodes().GetEndOfContent(), -1 ); + SwPosition aBreakPos( aBreakIdx ); + // InsertPageBreak just works on SwTextNode nodes, so make + // sure the last node is one! + bool bIsTextNode = aBreakIdx.GetNode().IsTextNode(); + if ( !bIsTextNode ) + getIDocumentContentOperations().AppendTextNode( aBreakPos ); + const OUString name = pTargetPageDesc->GetName(); + pTargetShell->InsertPageBreak( &name, nStartPageNumber ); + if ( !bIsTextNode ) + { + pTargetShell->SttEndDoc( false ); + --aBreakIdx; + GetNodes().Delete( aBreakIdx ); + } + + // There is now a new empty text node on the new page. If it has + // any marks, those are from the previous page: move them back + // there, otherwise later we can't delete that empty text node. + SwNodeIndex aNodeIndex(GetNodes().GetEndOfContent(), -1); + if (SwTextNode* pTextNode = aNodeIndex.GetNode().GetTextNode()) + { + // Position of the last paragraph on the previous page. + --aNodeIndex; + SwPaM aPaM(aNodeIndex); + // Collect the marks starting or ending at this text node. + o3tl::sorted_vector<sw::mark::IMark*> aSeenMarks; + IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess(); + for (const SwIndex* pIndex = pTextNode->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + { + sw::mark::IMark* pMark = const_cast<sw::mark::IMark*>(pIndex->GetMark()); + if (!pMark) + continue; + if (!aSeenMarks.insert(pMark).second) + continue; + } + // And move them back. + for (sw::mark::IMark* pMark : aSeenMarks) + pMarkAccess->repositionMark(pMark, aPaM); + } + + // Flush the page break, if we want to keep it + if ( !bDeletePrevious ) + { + SAL_INFO( "sw.pageframe", "(Flush pagebreak AKA EndAllAction" ); + pTargetShell->EndAllAction(); + SAL_INFO( "sw.pageframe", "Flush changes AKA EndAllAction)" ); + pTargetShell->StartAllAction(); + } + } + } +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Nd: " << CNTNT_DOC( this ) ); +#endif + + // -1, otherwise aFixupIdx would move to new EOC + SwNodeIndex aFixupIdx( GetNodes().GetEndOfContent(), -1 ); + + // append at the end of document / content + SwNodeIndex aTargetIdx( GetNodes().GetEndOfContent() ); + SwPaM aInsertPam( aTargetIdx ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Pam-Nd: " << aCpyPam.GetNode().GetIndex() - aCpyPam.GetNode( false ).GetIndex() + 1 + << " (0x" << std::hex << static_cast<int>(aCpyPam.GetNode( false ).GetNodeType()) << std::dec + << " " << aCpyPam.GetNode( false ).GetIndex() + << " - 0x" << std::hex << static_cast<int>(aCpyPam.GetNode().GetNodeType()) << std::dec + << " " << aCpyPam.GetNode().GetIndex() << ")" ); +#endif + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + getIDocumentFieldsAccess().LockExpFields(); + + // Position where the appended doc starts. Will be filled in later. + // Initially uses GetEndOfContent() because SwNodeIndex has no default ctor. + SwNodeIndex aStartAppendIndex( GetNodes().GetEndOfContent() ); + + { + // ** + // ** refer to SwFEShell::Paste, if you change the following code ** + // ** + + SwPosition& rInsPos = *aInsertPam.GetPoint(); + + { + SwNodeIndex aIndexBefore(rInsPos.nNode); + + aIndexBefore--; +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "CopyRange In: " << CNTNT_DOC( this ) ); +#endif + rSource.getIDocumentContentOperations().CopyRange(aCpyPam, rInsPos, SwCopyFlags::CopyAll|SwCopyFlags::CheckPosInFly); + // Note: aCpyPam is invalid now +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "CopyRange Out: " << CNTNT_DOC( this ) ); +#endif + + ++aIndexBefore; + SwPaM aPaM(SwPosition(aIndexBefore), + SwPosition(rInsPos.nNode)); + + aPaM.GetDoc()->MakeUniqueNumRules(aPaM); + + // Update the rsid of each pasted text node + SwNodes &rDestNodes = GetNodes(); + sal_uLong const nEndIdx = aPaM.End()->nNode.GetIndex(); + + for (sal_uLong nIdx = aPaM.Start()->nNode.GetIndex(); + nIdx <= nEndIdx; ++nIdx) + { + SwTextNode *const pTextNode = rDestNodes[nIdx]->GetTextNode(); + if ( pTextNode ) + UpdateParRsid( pTextNode ); + } + } + + { + sal_uLong iDelNodes = 0; + SwNodeIndex aDelIdx( aFixupIdx ); + + // we just need to set the new page description and reset numbering + // this keeps all other settings as in the pasted document + if ( nStartPageNumber || pTargetPageDesc ) { + std::unique_ptr<SfxPoolItem> pNewItem; + SwTextNode *aTextNd = nullptr; + SwFormat *pFormat = nullptr; + + // find the first node allowed to contain a RES_PAGEDESC + while (true) { + aFixupIdx++; + + SwNode &node = aFixupIdx.GetNode(); + if ( node.IsTextNode() ) { + // every document contains at least one text node! + aTextNd = node.GetTextNode(); + pNewItem.reset(aTextNd->GetAttr( RES_PAGEDESC ).Clone()); + break; + } + else if ( node.IsTableNode() ) { + pFormat = node.GetTableNode()->GetTable().GetFrameFormat(); + pNewItem.reset(pFormat->GetFormatAttr( RES_PAGEDESC ).Clone()); + break; + } + } + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Idx Del " << CNTNT_IDX( aDelIdx ) ); + SAL_INFO( "sw.docappend", "Idx Fix " << CNTNT_IDX( aFixupIdx ) ); +#endif + // just update the original instead of overwriting + SwFormatPageDesc *aDesc = static_cast< SwFormatPageDesc* >( pNewItem.get() ); +#ifdef DBG_UTIL + if ( aDesc->GetPageDesc() ) + SAL_INFO( "sw.docappend", "PD Update " << aDesc->GetPageDesc()->GetName() ); + else + SAL_INFO( "sw.docappend", "PD New" ); +#endif + if ( nStartPageNumber ) + aDesc->SetNumOffset( nStartPageNumber ); + if ( pTargetPageDesc ) + aDesc->RegisterToPageDesc( *pTargetPageDesc ); + if ( aTextNd ) + aTextNd->SetAttr( *aDesc ); + else + pFormat->SetFormatAttr( *aDesc ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Idx " << CNTNT_IDX( aDelIdx ) ); +#endif + iDelNodes++; + } + + if ( bDeletePrevious ) + iDelNodes++; + + if ( iDelNodes ) { + // delete leading empty page(s), e.g. from InsertPageBreak or + // new SwDoc. this has to be done before copying the page bound + // frames, otherwise the drawing layer gets confused. + if ( pTargetShell ) + pTargetShell->SttEndDoc( false ); + aDelIdx -= iDelNodes - 1; +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "iDelNodes: " << iDelNodes + << " Idx: " << aDelIdx.GetNode().GetIndex() + << " EOE: " << GetNodes().GetEndOfExtras().GetIndex() ); +#endif + GetNodes().Delete( aDelIdx, iDelNodes ); + aStartAppendIndex = aFixupIdx; + } + else + { + aStartAppendIndex = aFixupIdx; + ++aStartAppendIndex; + } + } + + // finally copy page bound frames + for ( auto pCpyFormat : *rSource.GetSpzFrameFormats() ) + { + const SwFrameFormat& rCpyFormat = *pCpyFormat; + SwFormatAnchor aAnchor( rCpyFormat.GetAnchor() ); + if (RndStdIds::FLY_AT_PAGE != aAnchor.GetAnchorId()) + continue; + SAL_INFO( "sw.docappend", "PaAn: " << aAnchor.GetPageNum() + << " => " << aAnchor.GetPageNum() + pageOffset ); + if ( pageOffset != 0 ) + aAnchor.SetPageNum( aAnchor.GetPageNum() + pageOffset ); + getIDocumentLayoutAccess().CopyLayoutFormat( rCpyFormat, aAnchor, true, true ); + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSGLOSSARY, nullptr ); + + getIDocumentFieldsAccess().UnlockExpFields(); + getIDocumentFieldsAccess().UpdateFields(false); + + if ( pTargetShell ) + pTargetShell->EndAllAction(); + + SAL_INFO( "sw.pageframe", "SwDoc::AppendDoc out)" ); + return aStartAppendIndex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docnum.cxx b/sw/source/core/doc/docnum.cxx new file mode 100644 index 000000000..c16ffcdd0 --- /dev/null +++ b/sw/source/core/doc/docnum.cxx @@ -0,0 +1,2622 @@ +/* -*- 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/lrspitem.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentListsAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <UndoCore.hxx> +#include <UndoRedline.hxx> +#include <UndoNumbering.hxx> +#include <swundo.hxx> +#include <SwUndoFmt.hxx> +#include <rolbck.hxx> +#include <paratr.hxx> +#include <docary.hxx> +#include <mvsave.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <redline.hxx> +#include <strings.hrc> +#include <SwNodeNum.hxx> +#include <list.hxx> +#include <calbck.hxx> +#include <comphelper/string.hxx> +#include <comphelper/random.hxx> +#include <o3tl/safeint.hxx> +#include <tools/datetimeutils.hxx> + +#include <map> +#include <stdlib.h> + + +namespace { + void lcl_ResetIndentAttrs(SwDoc *pDoc, const SwPaM &rPam, sal_uInt16 marker, + SwRootFrame const*const pLayout) + { + std::set<sal_uInt16> aResetAttrsArray; + aResetAttrsArray.insert( marker ); + // #i114929# + // On a selection setup a corresponding Point-and-Mark in order to get + // the indentation attribute reset on all paragraphs touched by the selection + if ( rPam.HasMark() && + rPam.End()->nNode.GetNode().GetTextNode() ) + { + SwPaM aPam( rPam.Start()->nNode, + rPam.End()->nNode ); + aPam.Start()->nContent = 0; + aPam.End()->nContent = rPam.End()->nNode.GetNode().GetTextNode()->Len(); + pDoc->ResetAttrs( aPam, false, aResetAttrsArray, true, pLayout ); + } + else + { + pDoc->ResetAttrs( rPam, false, aResetAttrsArray, true, pLayout ); + } + } + + void ExpandPamForParaPropsNodes(SwPaM& rPam, SwRootFrame const*const pLayout) + { + if (pLayout) + { // ensure that selection from the Shell includes the para-props node + // to which the attributes should be applied + if (rPam.GetPoint()->nNode.GetNode().IsTextNode()) + { + rPam.GetPoint()->nNode = *sw::GetParaPropsNode(*pLayout, rPam.GetPoint()->nNode); + rPam.GetPoint()->nContent.Assign(rPam.GetPoint()->nNode.GetNode().GetContentNode(), 0); + } + if (rPam.GetMark()->nNode.GetNode().IsTextNode()) + { + rPam.GetMark()->nNode = *sw::GetParaPropsNode(*pLayout, rPam.GetMark()->nNode); + rPam.GetMark()->nContent.Assign(rPam.GetMark()->nNode.GetNode().GetContentNode(), 0); + } + } + } +} + +static sal_uInt8 GetUpperLvlChg( sal_uInt8 nCurLvl, sal_uInt8 nLevel, sal_uInt16 nMask ) +{ + if( 1 < nLevel ) + { + if( nCurLvl + 1 >= nLevel ) + nCurLvl -= nLevel - 1; + else + nCurLvl = 0; + } + return static_cast<sal_uInt8>((nMask - 1) & ~(( 1 << nCurLvl ) - 1)); +} + +void SwDoc::SetOutlineNumRule( const SwNumRule& rRule ) +{ + if( mpOutlineRule ) + (*mpOutlineRule) = rRule; + else + { + mpOutlineRule = new SwNumRule( rRule ); + + AddNumRule(mpOutlineRule); // #i36749# + } + + mpOutlineRule->SetRuleType( OUTLINE_RULE ); + mpOutlineRule->SetName(SwNumRule::GetOutlineRuleName(), getIDocumentListsAccess()); + + // assure that the outline numbering rule is an automatic rule + mpOutlineRule->SetAutoRule( true ); + + // test whether the optional CharFormats are defined in this Document + mpOutlineRule->CheckCharFormats( this ); + + // notify text nodes, which are registered at the outline style, about the + // changed outline style + SwNumRule::tTextNodeList aTextNodeList; + mpOutlineRule->GetTextNodeList( aTextNodeList ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + pTextNd->NumRuleChgd(); + + // assure that list level corresponds to outline level + if ( pTextNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle() && + pTextNd->GetAttrListLevel() != pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() ) + { + pTextNd->SetAttrListLevel( pTextNd->GetTextColl()->GetAssignedOutlineStyleLevel() ); + } + } + + PropagateOutlineRule(); + mpOutlineRule->SetInvalidRule(true); + UpdateNumRule(); + + // update if we have foot notes && numbering by chapter + if( !GetFootnoteIdxs().empty() && FTNNUM_CHAPTER == GetFootnoteInfo().m_eNum ) + GetFootnoteIdxs().UpdateAllFootnote(); + + getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + getIDocumentState().SetModified(); +} + +void SwDoc::PropagateOutlineRule() +{ + for (auto pColl : *mpTextFormatCollTable) + { + if(pColl->IsAssignedToListLevelOfOutlineStyle()) + { + // Check only the list style, which is set at the paragraph style + const SwNumRuleItem & rCollRuleItem = pColl->GetNumRule( false ); + + if ( rCollRuleItem.GetValue().isEmpty() ) + { + SwNumRule * pMyOutlineRule = GetOutlineNumRule(); + + if (pMyOutlineRule) + { + SwNumRuleItem aNumItem( pMyOutlineRule->GetName() ); + + pColl->SetFormatAttr(aNumItem); + } + } + } + } +} + +// Increase/Decrease +bool SwDoc::OutlineUpDown(const SwPaM& rPam, short nOffset, + SwRootFrame const*const pLayout) +{ + if( GetNodes().GetOutLineNds().empty() || !nOffset ) + return false; + + // calculate the range + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds(); + const SwNodePtr pSttNd = &aPam.Start()->nNode.GetNode(); + const SwNodePtr pEndNd = &aPam.End()->nNode.GetNode(); + SwOutlineNodes::size_type nSttPos, nEndPos; + + if( !rOutlNds.Seek_Entry( pSttNd, &nSttPos ) && + !nSttPos-- ) + // we're not in an "Outline section" + return false; + + if( rOutlNds.Seek_Entry( pEndNd, &nEndPos ) ) + ++nEndPos; + + // We now have the wanted range in the OutlineNodes array, + // so check now if we're not invalidating sublevels + // (stepping over the limits) + + // Here we go: + // 1. Create the style array: + SwTextFormatColl* aCollArr[ MAXLEVEL ]; + memset( aCollArr, 0, sizeof( SwTextFormatColl* ) * MAXLEVEL ); + + for( auto pTextFormatColl : *mpTextFormatCollTable ) + { + if (pTextFormatColl->IsAssignedToListLevelOfOutlineStyle()) + { + const int nLevel = pTextFormatColl->GetAssignedOutlineStyleLevel(); + aCollArr[ nLevel ] = pTextFormatColl; + } + } + + int n; + + /* Find the last occupied level (backward). */ + for (n = MAXLEVEL - 1; n > 0; n--) + { + if (aCollArr[n] != nullptr) + break; + } + + /* If an occupied level is found, choose next level (which IS + unoccupied) until a valid level is found. If no occupied level + was found n is 0 and aCollArr[0] is 0. In this case no demoting + is possible. */ + if (aCollArr[n] != nullptr) + { + while (n < MAXLEVEL - 1) + { + n++; + + SwTextFormatColl *aTmpColl = + getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n)); + + if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() && + aTmpColl->GetAssignedOutlineStyleLevel() == n ) + { + aCollArr[n] = aTmpColl; + break; + } + } + } + + /* Find the first occupied level (forward). */ + for (n = 0; n < MAXLEVEL - 1; n++) + { + if (aCollArr[n] != nullptr) + break; + } + + /* If an occupied level is found, choose previous level (which IS + unoccupied) until a valid level is found. If no occupied level + was found n is MAXLEVEL - 1 and aCollArr[MAXLEVEL - 1] is 0. In + this case no demoting is possible. */ + if (aCollArr[n] != nullptr) + { + while (n > 0) + { + n--; + + SwTextFormatColl *aTmpColl = + getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + n)); + + if( aTmpColl->IsAssignedToListLevelOfOutlineStyle() && + aTmpColl->GetAssignedOutlineStyleLevel() == n ) + { + aCollArr[n] = aTmpColl; + break; + } + } + } + + /* --> #i13747# + + Build a move table that states from which level to which other level + an outline will be moved. + + the move table: + aMoveArr[n] = m: replace aCollArr[n] with aCollArr[m] + */ + int aMoveArr[MAXLEVEL]; + int nStep; // step size for searching in aCollArr: -1 or 1 + int nNum; // amount of steps for stepping in aCollArr + + if (nOffset < 0) + { + nStep = -1; + nNum = -nOffset; + } + else + { + nStep = 1; + nNum = nOffset; + } + + /* traverse aCollArr */ + for (n = 0; n < MAXLEVEL; n++) + { + /* If outline level n has an assigned paragraph style step + nNum steps forwards (nStep == 1) or backwards (nStep == + -1). One step is to go to the next non-null entry in + aCollArr in the selected direction. If nNum steps were + possible write the index of the entry found to aCollArr[n], + i.e. outline level n will be replaced by outline level + aCollArr[n]. + + If outline level n has no assigned paragraph style + aMoveArr[n] is set to -1. + */ + if (aCollArr[n] != nullptr) + { + int m = n; + int nCount = nNum; + + while (nCount > 0 && m + nStep >= 0 && m + nStep < MAXLEVEL) + { + m += nStep; + + if (aCollArr[m] != nullptr) + nCount--; + } + + if (nCount == 0) + aMoveArr[n] = m; + else + aMoveArr[n] = -1; + } + else + aMoveArr[n] = -1; + } + + /* If moving of the outline levels is applicable, i.e. for all + outline levels occurring in the document there has to be a valid + target outline level implied by aMoveArr. */ + bool bMoveApplicable = true; + for (auto i = nSttPos; i < nEndPos; ++i) + { + SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode(); + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + continue; + } + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + + if( pColl->IsAssignedToListLevelOfOutlineStyle() ) + { + const int nLevel = pColl->GetAssignedOutlineStyleLevel(); + if (aMoveArr[nLevel] == -1) + bMoveApplicable = false; + } + + // Check on outline level attribute of text node, if text node is + // not an outline via a to outline style assigned paragraph style. + else + { + const int nNewOutlineLevel = pTextNd->GetAttrOutlineLevel() + nOffset; + if ( nNewOutlineLevel < 1 || nNewOutlineLevel > MAXLEVEL ) + { + bMoveApplicable = false; + } + } + } + + if (! bMoveApplicable ) + return false; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::OUTLINE_LR, nullptr); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoOutlineLeftRight>(aPam, nOffset) ); + } + + // 2. Apply the new style to all Nodes + for (auto i = nSttPos; i < nEndPos; ++i) + { + SwTextNode* pTextNd = rOutlNds[ i ]->GetTextNode(); + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + continue; + } + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + + if( pColl->IsAssignedToListLevelOfOutlineStyle() ) + { + const int nLevel = pColl->GetAssignedOutlineStyleLevel(); + + OSL_ENSURE(aMoveArr[nLevel] >= 0, + "move table: current TextColl not found when building table!"); + + if (nLevel < MAXLEVEL && aMoveArr[nLevel] >= 0) + { + pColl = aCollArr[ aMoveArr[nLevel] ]; + + if (pColl != nullptr) + pTextNd->ChgFormatColl( pColl ); + } + + } + else if( pTextNd->GetAttrOutlineLevel() > 0) + { + int nLevel = pTextNd->GetAttrOutlineLevel() + nOffset; + if( 0 <= nLevel && nLevel <= MAXLEVEL) + pTextNd->SetAttrOutlineLevel( nLevel ); + + } + // Undo ??? + } + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_LR, nullptr); + } + + ChkCondColls(); + getIDocumentState().SetModified(); + + return true; +} + +// Move up/down +bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type nOffset ) +{ + // Do not move to special sections in the nodes array + const SwPosition& rStt = *rPam.Start(), + & rEnd = &rStt == rPam.GetPoint() ? *rPam.GetMark() + : *rPam.GetPoint(); + if( GetNodes().GetOutLineNds().empty() || !nOffset || + (rStt.nNode.GetIndex() < GetNodes().GetEndOfExtras().GetIndex()) || + (rEnd.nNode.GetIndex() < GetNodes().GetEndOfExtras().GetIndex())) + { + return false; + } + + SwOutlineNodes::size_type nCurrentPos = 0; + SwNodeIndex aSttRg( rStt.nNode ), aEndRg( rEnd.nNode ); + + int nOutLineLevel = MAXLEVEL; + SwNode* pSrch = &aSttRg.GetNode(); + + if( pSrch->IsTextNode()) + nOutLineLevel = static_cast<sal_uInt8>(pSrch->GetTextNode()->GetAttrOutlineLevel()-1); + SwNode* pEndSrch = &aEndRg.GetNode(); + if( !GetNodes().GetOutLineNds().Seek_Entry( pSrch, &nCurrentPos ) ) + { + if( !nCurrentPos ) + return false; // Promoting or demoting before the first outline => no. + if( --nCurrentPos ) + aSttRg = *GetNodes().GetOutLineNds()[ nCurrentPos ]; + else if( 0 > nOffset ) + return false; // Promoting at the top of document?! + else + aSttRg = *GetNodes().GetEndOfContent().StartOfSectionNode(); + } + SwOutlineNodes::size_type nTmpPos = 0; + // If the given range ends at an outlined text node we have to decide if it has to be a part of + // the moving range or not. Normally it will be a sub outline of our chapter + // and has to be moved, too. But if the chapter ends with a table(or a section end), + // the next text node will be chosen and this could be the next outline of the same level. + // The criteria has to be the outline level: sub level => incorporate, same/higher level => no. + if( GetNodes().GetOutLineNds().Seek_Entry( pEndSrch, &nTmpPos ) ) + { + if( !pEndSrch->IsTextNode() || pEndSrch == pSrch || + nOutLineLevel < pEndSrch->GetTextNode()->GetAttrOutlineLevel()-1 ) + ++nTmpPos; // For sub outlines only! + } + + aEndRg = nTmpPos < GetNodes().GetOutLineNds().size() + ? *GetNodes().GetOutLineNds()[ nTmpPos ] + : GetNodes().GetEndOfContent(); + if( nOffset >= 0 ) + nCurrentPos = nTmpPos; + if( aEndRg == aSttRg ) + { + OSL_FAIL( "Moving outlines: Surprising selection" ); + ++aEndRg; + } + + const SwNode* pNd; + // The following code corrects the range to handle sections (start/end nodes) + // The range will be extended if the least node before the range is a start node + // which ends inside the range => The complete section will be moved. + // The range will be shrunk if the last position is a start node. + // The range will be shrunk if the last node is an end node which starts before the range. + --aSttRg; + while( aSttRg.GetNode().IsStartNode() ) + { + pNd = aSttRg.GetNode().EndOfSectionNode(); + if( pNd->GetIndex() >= aEndRg.GetIndex() ) + break; + --aSttRg; + } + ++aSttRg; + + --aEndRg; + while( aEndRg.GetNode().IsStartNode() ) + --aEndRg; + + while( aEndRg.GetNode().IsEndNode() ) + { + pNd = aEndRg.GetNode().StartOfSectionNode(); + if( pNd->GetIndex() >= aSttRg.GetIndex() ) + break; + --aEndRg; + } + ++aEndRg; + + // calculation of the new position + if( nOffset < 0 && nCurrentPos < o3tl::make_unsigned(-nOffset) ) + pNd = GetNodes().GetEndOfContent().StartOfSectionNode(); + else if( nCurrentPos + nOffset >= GetNodes().GetOutLineNds().size() ) + pNd = &GetNodes().GetEndOfContent(); + else + pNd = GetNodes().GetOutLineNds()[ nCurrentPos + nOffset ]; + + sal_uLong nNewPos = pNd->GetIndex(); + + // And now a correction of the insert position if necessary... + SwNodeIndex aInsertPos( *pNd, -1 ); + while( aInsertPos.GetNode().IsStartNode() ) + { + // Just before the insert position starts a section: + // when I'm moving forward I do not want to enter the section, + // when I'm moving backward I want to stay in the section if I'm already a part of, + // I want to stay outside if I was outside before. + if( nOffset < 0 ) + { + pNd = aInsertPos.GetNode().EndOfSectionNode(); + if( pNd->GetIndex() >= aEndRg.GetIndex() ) + break; + } + --aInsertPos; + --nNewPos; + } + + if( nOffset >= 0 ) + { + // When just before the insert position a section ends, it is okay when I'm moving backward + // because I want to stay outside the section. + // When moving forward I've to check if I started inside or outside the section + // because I don't want to enter of leave such a section + while( aInsertPos.GetNode().IsEndNode() ) + { + pNd = aInsertPos.GetNode().StartOfSectionNode(); + if( pNd->GetIndex() >= aSttRg.GetIndex() ) + break; + --aInsertPos; + --nNewPos; + } + } + // We do not want to move into tables (at the moment) + ++aInsertPos; + pNd = &aInsertPos.GetNode(); + if( pNd->IsTableNode() ) + pNd = pNd->StartOfSectionNode(); + if( pNd->FindTableNode() ) + return false; + + OSL_ENSURE( aSttRg.GetIndex() > nNewPos || nNewPos >= aEndRg.GetIndex(), + "Position lies within Move range" ); + + // If a Position inside the special nodes array sections was calculated, + // set it to document start instead. + // Sections or Tables at the document start will be pushed backwards. + nNewPos = std::max( nNewPos, GetNodes().GetEndOfExtras().GetIndex() + 2 ); + + long nOffs = nNewPos - ( 0 < nOffset ? aEndRg.GetIndex() : aSttRg.GetIndex()); + SwPaM aPam( aSttRg, aEndRg, 0, -1 ); + return MoveParagraph( aPam, nOffs, true ); +} + +static SwTextNode* lcl_FindOutlineName(const SwOutlineNodes& rOutlNds, + SwRootFrame const*const pLayout, const OUString& rName, bool const bExact) +{ + SwTextNode * pExactButDeleted(nullptr); + SwTextNode* pSavedNode = nullptr; + for( auto pOutlNd : rOutlNds ) + { + SwTextNode* pTextNd = pOutlNd->GetTextNode(); + const OUString sText( pTextNd->GetExpandText(pLayout) ); + if (sText.startsWith(rName)) + { + if (sText.getLength() == rName.getLength()) + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTextNd)) + { + pExactButDeleted = pTextNd; + } + else + { + // Found "exact", set Pos to the Node + return pTextNd; + } + } + if (!bExact && !pSavedNode + && (!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd))) + { + // maybe we just found the text's first part + pSavedNode = pTextNd; + } + } + } + + return bExact ? pExactButDeleted : pSavedNode; +} + +static SwTextNode* lcl_FindOutlineNum(const SwOutlineNodes& rOutlNds, + OUString& rName, SwRootFrame const*const pLayout) +{ + // Valid numbers are (always just offsets!): + // ([Number]+\.)+ (as a regular expression!) + // (Number followed by a period, with 5 repetitions) + // i.e.: "1.1.", "1.", "1.1.1." + sal_Int32 nPos = 0; + OUString sNum = rName.getToken( 0, '.', nPos ); + if( -1 == nPos ) + return nullptr; // invalid number! + + sal_uInt16 nLevelVal[ MAXLEVEL ]; // numbers of all levels + memset( nLevelVal, 0, MAXLEVEL * sizeof( nLevelVal[0] )); + int nLevel = 0; + OUString sName( rName ); + + while( -1 != nPos ) + { + sal_uInt16 nVal = 0; + for( sal_Int32 n = 0; n < sNum.getLength(); ++n ) + { + const sal_Unicode c {sNum[ n ]}; + if( '0' <= c && c <= '9' ) + { + nVal *= 10; + nVal += c - '0'; + } + else if( nLevel ) + break; // "almost" valid number + else + return nullptr; // invalid number! + } + + if( MAXLEVEL > nLevel ) + nLevelVal[ nLevel++ ] = nVal; + + sName = sName.copy( nPos ); + nPos = 0; + sNum = sName.getToken( 0, '.', nPos ); + // #i4533# without this check all parts delimited by a dot are treated as outline numbers + if(!comphelper::string::isdigitAsciiString(sNum)) + break; + } + rName = sName; // that's the follow-up text + + // read all levels, so search the document for this outline + + // Without OutlineNodes searching doesn't pay off + // and we save a crash + if( rOutlNds.empty() ) + return nullptr; + + // search in the existing outline nodes for the required outline num array + for( auto pOutlNd : rOutlNds ) + { + SwTextNode* pNd = pOutlNd->GetTextNode(); + if ( pNd->GetAttrOutlineLevel() == nLevel ) + { + // #i51089#, #i68289# + // Assure, that text node has the correct numbering level. Otherwise, + // its number vector will not fit to the searched level. + if (pNd->GetNum(pLayout) && pNd->GetActualListLevel() == nLevel - 1) + { + const SwNodeNum & rNdNum = *(pNd->GetNum(pLayout)); + SwNumberTree::tNumberVector aLevelVal = rNdNum.GetNumberVector(); + // now compare with the one searched for + bool bEqual = true; + nLevel = std::min<int>(nLevel, MAXLEVEL); + for( int n = 0; n < nLevel; ++n ) + { + if ( aLevelVal[n] != nLevelVal[n] ) + { + bEqual = false; + break; + } + } + if (bEqual) + return pNd; + } + else + { + // A text node, which has an outline paragraph style applied and + // has as hard attribute 'no numbering' set, has an outline level, + // but no numbering tree node. Thus, consider this situation in + // the assertion condition. + OSL_ENSURE( !pNd->GetNumRule(), + "<lcl_FindOutlineNum(..)> - text node with outline level and numbering rule, but without numbering tree node. This is a serious defect" ); + } + } + } + + return nullptr; +} + +// rName can contain a Number and/or the Text. +// First, we try to find the correct Entry via the Number. +// If it exists, we compare the Text to see if it's the right one. +// If that's not the case, we search again via the Text. If it is +// found, we got the right entry. Or else we use the one found by +// searching for the Number. +// If we don't have a Number, we search via the Text only. +bool SwDoc::GotoOutline(SwPosition& rPos, const OUString& rName, SwRootFrame const*const pLayout) const +{ + if( !rName.isEmpty() ) + { + const SwOutlineNodes& rOutlNds = GetNodes().GetOutLineNds(); + + // 1. step: via the Number: + OUString sName( rName ); + SwTextNode* pNd = ::lcl_FindOutlineNum(rOutlNds, sName, pLayout); + if ( pNd ) + { + OUString sExpandedText = pNd->GetExpandText(pLayout); + //#i4533# leading numbers followed by a dot have been remove while + //searching for the outline position + //to compensate this they must be removed from the paragraphs text content, too + while(!sExpandedText.isEmpty()) + { + sal_Int32 nPos = 0; + OUString sTempNum = sExpandedText.getToken(0, '.', nPos); + if( sTempNum.isEmpty() || -1 == nPos || + !comphelper::string::isdigitAsciiString(sTempNum)) + break; + sExpandedText = sExpandedText.copy(nPos); + } + + if( sExpandedText != sName ) + { + SwTextNode *pTmpNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, true); + if ( pTmpNd ) // found via the Name + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTmpNd)) + { // found the correct node but it's deleted! + return false; // avoid fallback to inexact search + } + pNd = pTmpNd; + } + } + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + + pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, rName, false); + if ( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + + // #i68289# additional search on hyperlink URL without its outline numbering part + if ( sName != rName ) + { + pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, sName, false); + if ( pNd ) + { + rPos.nNode = *pNd; + rPos.nContent.Assign( pNd, 0 ); + return true; + } + } + } + return false; +} + +static void lcl_ChgNumRule( SwDoc& rDoc, const SwNumRule& rRule ) +{ + SwNumRule* pOld = rDoc.FindNumRulePtr( rRule.GetName() ); + if (!pOld) //we cannot proceed without the old NumRule + return; + + sal_uInt16 nChgFormatLevel = 0; + sal_uInt16 nMask = 1; + + for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nMask <<= 1 ) + { + const SwNumFormat& rOldFormat = pOld->Get( n ), &rNewFormat = rRule.Get( n ); + + if ( rOldFormat != rNewFormat ) + { + nChgFormatLevel |= nMask; + } + else if ( SVX_NUM_NUMBER_NONE > rNewFormat.GetNumberingType() + && 1 < rNewFormat.GetIncludeUpperLevels() + && 0 != ( nChgFormatLevel & GetUpperLvlChg( n, rNewFormat.GetIncludeUpperLevels(), nMask ) ) ) + { + nChgFormatLevel |= nMask; + } + } + + if( !nChgFormatLevel ) // Nothing has been changed? + { + const bool bInvalidateNumRule( pOld->IsContinusNum() != rRule.IsContinusNum() ); + pOld->CheckCharFormats( &rDoc ); + pOld->SetContinusNum( rRule.IsContinusNum() ); + + if ( bInvalidateNumRule ) + { + pOld->SetInvalidRule(true); + } + + return ; + } + + SwNumRule::tTextNodeList aTextNodeList; + pOld->GetTextNodeList( aTextNodeList ); + sal_uInt8 nLvl( 0 ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + nLvl = static_cast<sal_uInt8>(pTextNd->GetActualListLevel()); + + if( nLvl < MAXLEVEL ) + { + if( nChgFormatLevel & ( 1 << nLvl )) + { + pTextNd->NumRuleChgd(); + } + } + } + + for ( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + if ( nChgFormatLevel & ( 1 << n ) ) + pOld->Set( n, rRule.GetNumFormat( n ) ); + + pOld->CheckCharFormats( &rDoc ); + pOld->SetInvalidRule( true ); + pOld->SetContinusNum( rRule.IsContinusNum() ); + + rDoc.UpdateNumRule(); +} + +OUString SwDoc::SetNumRule( const SwPaM& rPam, + const SwNumRule& rRule, + const bool bCreateNewList, + SwRootFrame const*const pLayout, + const OUString& sContinuedListId, + bool bSetItem, + const bool bResetIndentAttrs ) +{ + OUString sListId; + + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + + SwUndoInsNum * pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // Start/End for attributes! + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSNUM, nullptr ); + pUndo = new SwUndoInsNum( aPam, rRule ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + SwNumRule* pNewOrChangedNumRule = FindNumRulePtr( rRule.GetName() ); + bool bNewNumRuleCreated = false; + if ( pNewOrChangedNumRule == nullptr ) + { + // create new numbering rule based on given one + pNewOrChangedNumRule = ( *mpNumRuleTable )[MakeNumRule( rRule.GetName(), &rRule )]; + bNewNumRuleCreated = true; + } + else if ( rRule != *pNewOrChangedNumRule ) + { + // change existing numbering rule + if (pUndo) + { + pUndo->SaveOldNumRule( *pNewOrChangedNumRule ); + } + ::lcl_ChgNumRule( *this, rRule ); + if (pUndo) + { + pUndo->SetLRSpaceEndPos(); + } + } + + if ( bSetItem ) + { + if ( bCreateNewList ) + { + if ( bNewNumRuleCreated ) + { + // apply list id of list, which has been created for the new list style + sListId = pNewOrChangedNumRule->GetDefaultListId(); + } + else + { + // create new list and apply its list id + const SwList* pNewList = getIDocumentListsAccess().createList( OUString(), pNewOrChangedNumRule->GetName() ); + OSL_ENSURE( pNewList, + "<SwDoc::SetNumRule(..)> - could not create new list. Serious defect." ); + sListId = pNewList->GetListId(); + } + } + else if ( !sContinuedListId.isEmpty() ) + { + // apply given list id + sListId = sContinuedListId; + } + if (!sListId.isEmpty()) + { + getIDocumentContentOperations().InsertPoolItem(aPam, + SfxStringItem(RES_PARATR_LIST_ID, sListId), + SetAttrMode::DEFAULT, pLayout); + } + } + + if (!aPam.HasMark()) + { + SwTextNode * pTextNd = aPam.GetPoint()->nNode.GetNode().GetTextNode(); + // robust code: consider case that the PaM doesn't denote a text node - e.g. it denotes a graphic node + if ( pTextNd != nullptr ) + { + assert(!pLayout || sw::IsParaPropsNode(*pLayout, *pTextNd)); + SwNumRule * pRule = pTextNd->GetNumRule(); + + if (pRule && pRule->GetName() == pNewOrChangedNumRule->GetName()) + { + bSetItem = false; + if ( !pTextNd->IsInList() ) + { + pTextNd->AddToList(); + } + } + // Only clear numbering attribute at text node, if at paragraph + // style the new numbering rule is found. + else if ( !pRule ) + { + SwTextFormatColl* pColl = pTextNd->GetTextColl(); + if ( pColl ) + { + SwNumRule* pCollRule = FindNumRulePtr(pColl->GetNumRule().GetValue()); + if ( pCollRule && pCollRule->GetName() == pNewOrChangedNumRule->GetName() ) + { + pTextNd->ResetAttr( RES_PARATR_NUMRULE ); + bSetItem = false; + } + } + } + } + } + + if ( bSetItem ) + { + getIDocumentContentOperations().InsertPoolItem(aPam, + SwNumRuleItem(pNewOrChangedNumRule->GetName()), + SetAttrMode::DEFAULT, pLayout); + } + + if ( bResetIndentAttrs + && pNewOrChangedNumRule->Get( 0 ).GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + ::lcl_ResetIndentAttrs(this, aPam, RES_LR_SPACE, pLayout); + } + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSNUM, nullptr ); + } + + getIDocumentState().SetModified(); + + return sListId; +} + +void SwDoc::SetCounted(const SwPaM & rPam, bool bCounted, + SwRootFrame const*const pLayout) +{ + if ( bCounted ) + { + ::lcl_ResetIndentAttrs(this, rPam, RES_PARATR_LIST_ISCOUNTED, pLayout); + } + else + { + getIDocumentContentOperations().InsertPoolItem(rPam, + SfxBoolItem(RES_PARATR_LIST_ISCOUNTED, false), + SetAttrMode::DEFAULT, pLayout); + } +} + +void SwDoc::SetNumRuleStart( const SwPosition& rPos, bool bFlag ) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + + if (pTextNd) + { + const SwNumRule* pRule = pTextNd->GetNumRule(); + if( pRule && !bFlag != !pTextNd->IsListRestart()) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumRuleStart>(rPos, bFlag) ); + } + + pTextNd->SetListRestart(bFlag); + + getIDocumentState().SetModified(); + } + } +} + +void SwDoc::SetNodeNumStart( const SwPosition& rPos, sal_uInt16 nStt ) +{ + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + + if (pTextNd) + { + if ( !pTextNd->HasAttrListRestartValue() || + pTextNd->GetAttrListRestartValue() != nStt ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumRuleStart>(rPos, nStt) ); + } + pTextNd->SetAttrListRestartValue( nStt ); + + getIDocumentState().SetModified(); + } + } +} + +// We can only delete if the Rule is unused! +bool SwDoc::DelNumRule( const OUString& rName, bool bBroadcast ) +{ + sal_uInt16 nPos = FindNumRule( rName ); + + if (nPos == USHRT_MAX) + return false; + + if ( (*mpNumRuleTable)[ nPos ] == GetOutlineNumRule() ) + { + OSL_FAIL( "<SwDoc::DelNumRule(..)> - No deletion of outline list style. This is serious defect" ); + return false; + } + + if( !IsUsed( *(*mpNumRuleTable)[ nPos ] )) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleDelete>(*(*mpNumRuleTable)[nPos], this)); + } + + if (bBroadcast) + BroadcastStyleOperation(rName, SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetErased); + + getIDocumentListsAccess().deleteListForListStyle( rName ); + getIDocumentListsAccess().deleteListsByDefaultListStyle( rName ); + // #i34097# DeleteAndDestroy deletes rName if + // rName is directly taken from the numrule. + const OUString aTmpName( rName ); + delete (*mpNumRuleTable)[ nPos ]; + mpNumRuleTable->erase( mpNumRuleTable->begin() + nPos ); + maNumRuleMap.erase(aTmpName); + + getIDocumentState().SetModified(); + return true; + } + return false; +} + +void SwDoc::ChgNumRuleFormats( const SwNumRule& rRule ) +{ + SwNumRule* pRule = FindNumRulePtr( rRule.GetName() ); + if( pRule ) + { + SwUndoInsNum* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoInsNum( *pRule, rRule, this ); + pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + ::lcl_ChgNumRule( *this, rRule ); + if (pUndo) + { + pUndo->SetLRSpaceEndPos(); + } + + getIDocumentState().SetModified(); + } +} + +bool SwDoc::RenameNumRule(const OUString & rOldName, const OUString & rNewName, + bool bBroadcast) +{ + assert(!FindNumRulePtr(rNewName)); + + bool bResult = false; + SwNumRule * pNumRule = FindNumRulePtr(rOldName); + + if (pNumRule) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleRename>(rOldName, rNewName, this)); + } + + SwNumRule::tTextNodeList aTextNodeList; + pNumRule->GetTextNodeList( aTextNodeList ); + + pNumRule->SetName( rNewName, getIDocumentListsAccess() ); + + SwNumRuleItem aItem(rNewName); + + for ( SwTextNode* pTextNd : aTextNodeList ) + { + pTextNd->SetAttr(aItem); + } + + bResult = true; + + if (bBroadcast) + BroadcastStyleOperation(rOldName, SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetModified); + } + + return bResult; +} + +void SwDoc::StopNumRuleAnimations( OutputDevice* pOut ) +{ + for( sal_uInt16 n = GetNumRuleTable().size(); n; ) + { + SwNumRule::tTextNodeList aTextNodeList; + GetNumRuleTable()[ --n ]->GetTextNodeList( aTextNodeList ); + for ( SwTextNode* pTNd : aTextNodeList ) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTNd); + for(SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + if (pFrame->HasAnimation() && + (!pFrame->GetMergedPara() || pFrame->GetMergedPara()->pParaPropsNode == pTNd)) + { + pFrame->StopAnimation( pOut ); + } + } + } +} + +bool SwDoc::ReplaceNumRule( const SwPosition& rPos, + const OUString& rOldRule, const OUString& rNewRule ) +{ + bool bRet = false; + SwNumRule *pOldRule = FindNumRulePtr( rOldRule ), + *pNewRule = FindNumRulePtr( rNewRule ); + if( pOldRule && pNewRule && pOldRule != pNewRule ) + { + SwUndoInsNum* pUndo = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // Start/End for attributes! + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + pUndo = new SwUndoInsNum( rPos, *pNewRule, rOldRule ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + + SwNumRule::tTextNodeList aTextNodeList; + pOldRule->GetTextNodeList( aTextNodeList ); + if ( !aTextNodeList.empty() ) + { + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + sal_uInt16 nChgFormatLevel = 0; + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + { + const SwNumFormat& rOldFormat = pOldRule->Get( n ), + & rNewFormat = pNewRule->Get( n ); + + if( rOldFormat.GetAbsLSpace() != rNewFormat.GetAbsLSpace() || + rOldFormat.GetFirstLineOffset() != rNewFormat.GetFirstLineOffset() ) + nChgFormatLevel |= ( 1 << n ); + } + + const SwTextNode* pGivenTextNode = rPos.nNode.GetNode().GetTextNode(); + SwNumRuleItem aRule( rNewRule ); + for ( SwTextNode* pTextNd : aTextNodeList ) + { + if ( pGivenTextNode && + pGivenTextNode->GetListId() == pTextNd->GetListId() ) + { + aRegH.RegisterInModify( pTextNd, *pTextNd ); + + pTextNd->SetAttr( aRule ); + pTextNd->NumRuleChgd(); + } + } + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + bRet = true; + } + } + + return bRet; +} + +namespace +{ + struct ListStyleData + { + SwNumRule* pReplaceNumRule; + bool bCreateNewList; + OUString sListId; + + ListStyleData() + : pReplaceNumRule( nullptr ), + bCreateNewList( false ), + sListId() + {} + }; +} + +void SwDoc::MakeUniqueNumRules(const SwPaM & rPaM) +{ + OSL_ENSURE( rPaM.GetDoc() == this, "need same doc" ); + + std::map<SwNumRule *, ListStyleData> aMyNumRuleMap; + + bool bFirst = true; + + const sal_uLong nStt = rPaM.Start()->nNode.GetIndex(); + const sal_uLong nEnd = rPaM.End()->nNode.GetIndex(); + for (sal_uLong n = nStt; n <= nEnd; n++) + { + SwTextNode * pCNd = GetNodes()[n]->GetTextNode(); + + if (pCNd) + { + SwNumRule * pRule = pCNd->GetNumRule(); + + if (pRule && pRule->IsAutoRule() && ! pRule->IsOutlineRule()) + { + ListStyleData aListStyleData = aMyNumRuleMap[pRule]; + + if ( aListStyleData.pReplaceNumRule == nullptr ) + { + if (bFirst) + { + SwPosition aPos(*pCNd); + aListStyleData.pReplaceNumRule = + const_cast<SwNumRule *> + (SearchNumRule( aPos, false, pCNd->HasNumber(), + false, 0, + aListStyleData.sListId, nullptr, true )); + } + + if ( aListStyleData.pReplaceNumRule == nullptr ) + { + aListStyleData.pReplaceNumRule = new SwNumRule(*pRule); + aListStyleData.pReplaceNumRule->SetName( GetUniqueNumRuleName(), getIDocumentListsAccess() ); + aListStyleData.bCreateNewList = true; + } + + aMyNumRuleMap[pRule] = aListStyleData; + } + + SwPaM aPam(*pCNd); + + SetNumRule( aPam, + *aListStyleData.pReplaceNumRule, + aListStyleData.bCreateNewList, + nullptr, + aListStyleData.sListId ); + if ( aListStyleData.bCreateNewList ) + { + aListStyleData.bCreateNewList = false; + aListStyleData.sListId = pCNd->GetListId(); + aMyNumRuleMap[pRule] = aListStyleData; + } + + bFirst = false; + } + } + } +} + +bool SwDoc::NoNum( const SwPaM& rPam ) +{ + + bool bRet = getIDocumentContentOperations().SplitNode( *rPam.GetPoint(), false ); + // Do we actually use Numbering at all? + if( bRet ) + { + // Set NoNum and Update + const SwNodeIndex& rIdx = rPam.GetPoint()->nNode; + SwTextNode* pNd = rIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + if( pRule ) + { + pNd->SetCountedInList(false); + + getIDocumentState().SetModified(); + } + else + bRet = false; // no Numbering or just always true? + } + return bRet; +} + +void SwDoc::DelNumRules(const SwPaM& rPam, SwRootFrame const*const pLayout) +{ + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + sal_uLong nStt = aPam.Start()->nNode.GetIndex(); + sal_uLong const nEnd = aPam.End()->nNode.GetIndex(); + + SwUndoDelNum* pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoDelNum( aPam ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + } + else + pUndo = nullptr; + + SwRegHistory aRegH( pUndo ? pUndo->GetHistory() : nullptr ); + + SwNumRuleItem aEmptyRule; + const SwNode* pOutlNd = nullptr; + for( ; nStt <= nEnd; ++nStt ) + { + SwTextNode* pTNd = GetNodes()[ nStt ]->GetTextNode(); + if (pLayout && pTNd) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + } + SwNumRule* pNumRuleOfTextNode = pTNd ? pTNd->GetNumRule() : nullptr; + if ( pTNd && pNumRuleOfTextNode ) + { + // recognize changes of attribute for undo + aRegH.RegisterInModify( pTNd, *pTNd ); + + if( pUndo ) + pUndo->AddNode( *pTNd ); + + // directly set list style attribute is reset, otherwise empty + // list style is applied + const SfxItemSet* pAttrSet = pTNd->GetpSwAttrSet(); + if ( pAttrSet && + pAttrSet->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + pTNd->ResetAttr( RES_PARATR_NUMRULE ); + else + pTNd->SetAttr( aEmptyRule ); + + pTNd->ResetAttr( RES_PARATR_LIST_ID ); + pTNd->ResetAttr( RES_PARATR_LIST_LEVEL ); + pTNd->ResetAttr( RES_PARATR_LIST_ISRESTART ); + pTNd->ResetAttr( RES_PARATR_LIST_RESTARTVALUE ); + pTNd->ResetAttr( RES_PARATR_LIST_ISCOUNTED ); + + if( RES_CONDTXTFMTCOLL == pTNd->GetFormatColl()->Which() ) + { + pTNd->ChkCondColl(); + } + else if( !pOutlNd && + static_cast<SwTextFormatColl*>(pTNd->GetFormatColl())->IsAssignedToListLevelOfOutlineStyle() ) + { + pOutlNd = pTNd; + } + } + } + + // Finally, update all + UpdateNumRule(); + + if( pOutlNd ) + GetNodes().UpdateOutlineIdx( *pOutlNd ); +} + +void SwDoc::InvalidateNumRules() +{ + for (size_t n = 0; n < mpNumRuleTable->size(); ++n) + (*mpNumRuleTable)[n]->SetInvalidRule(true); +} + +// To the next/preceding Bullet at the same Level +static bool lcl_IsNumOk( sal_uInt8 nSrchNum, sal_uInt8& rLower, sal_uInt8& rUpper, + bool bOverUpper, sal_uInt8 nNumber ) +{ + OSL_ENSURE( nNumber < MAXLEVEL, + "<lcl_IsNumOk(..)> - misusage of method" ); + + bool bRet = false; + { + if( bOverUpper ? nSrchNum == nNumber : nSrchNum >= nNumber ) + bRet = true; + else if( nNumber > rLower ) + rLower = nNumber; + else if( nNumber < rUpper ) + rUpper = nNumber; + } + return bRet; +} + +static bool lcl_IsValidPrevNextNumNode( const SwNodeIndex& rIdx ) +{ + bool bRet = false; + const SwNode& rNd = rIdx.GetNode(); + switch( rNd.GetNodeType() ) + { + case SwNodeType::End: + bRet = SwTableBoxStartNode == rNd.StartOfSectionNode()->GetStartNodeType() || + rNd.StartOfSectionNode()->IsSectionNode(); + break; + + case SwNodeType::Start: + bRet = SwTableBoxStartNode == static_cast<const SwStartNode&>(rNd).GetStartNodeType(); + break; + + case SwNodeType::Section: // that one's valid, so proceed + bRet = true; + break; + + default: break; + } + return bRet; +} + +namespace sw { + +void +GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + rIndex = *static_cast<SwTextFrame*>(rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))->GetMergedPara()->pFirstNode; + } + } + else if (rIndex.GetNode().IsEndNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + rIndex = *rIndex.GetNode().StartOfSectionNode(); + assert(rIndex.GetNode().IsTableNode()); + } + } + } + --rIndex; + if (pLayout && rIndex.GetNode().IsTextNode()) + { + rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode()); + } +} + +void +GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const*const pLayout) +{ + if (pLayout && pLayout->IsHideRedlines()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + rIndex = *static_cast<SwTextFrame*>(rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout))->GetMergedPara()->pLastNode; + } + } + else if (rIndex.GetNode().IsTableNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + rIndex = *rIndex.GetNode().EndOfSectionNode(); + } + } + } + ++rIndex; + if (pLayout && rIndex.GetNode().IsTextNode()) + { + rIndex = *sw::GetParaPropsNode(*pLayout, *rIndex.GetNode().GetTextNode()); + } +} + +} // namespace sw + +static bool lcl_GotoNextPrevNum( SwPosition& rPos, bool bNext, + bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower, + SwRootFrame const*const pLayout) +{ + const SwTextNode* pNd = rPos.nNode.GetNode().GetTextNode(); + if (pNd && pLayout) + { + pNd = sw::GetParaPropsNode(*pLayout, *pNd); + } + if( !pNd || nullptr == pNd->GetNumRule() ) + return false; + + sal_uInt8 nSrchNum = static_cast<sal_uInt8>(pNd->GetActualListLevel()); + + SwNodeIndex aIdx( rPos.nNode ); + if( ! pNd->IsCountedInList() ) + { + // If NO_NUMLEVEL is switched on, we search the preceding Node with Numbering + bool bError = false; + do { + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + if( aIdx.GetNode().IsTextNode() ) + { + pNd = aIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + + sal_uInt8 nTmpNum; + + if( pRule ) + { + nTmpNum = static_cast<sal_uInt8>(pNd->GetActualListLevel()); + if( !( ! pNd->IsCountedInList() && + (nTmpNum >= nSrchNum )) ) + break; // found it! + } + else + bError = true; + } + else + bError = !lcl_IsValidPrevNextNumNode( aIdx ); + + } while( !bError ); + if( bError ) + return false; + } + + sal_uInt8 nLower = nSrchNum, nUpper = nSrchNum; + bool bRet = false; + + const SwTextNode* pLast; + if( bNext ) + { + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + pLast = pNd; + } + else + { + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + pLast = nullptr; + } + + while( bNext ? ( aIdx.GetIndex() < aIdx.GetNodes().Count() - 1 ) + : aIdx.GetIndex() != 0 ) + { + if( aIdx.GetNode().IsTextNode() ) + { + pNd = aIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + if( pRule ) + { + if( ::lcl_IsNumOk( nSrchNum, nLower, nUpper, bOverUpper, + static_cast<sal_uInt8>(pNd->GetActualListLevel()) )) + { + rPos.nNode = aIdx; + rPos.nContent.Assign( const_cast<SwTextNode*>(pNd), 0 ); + bRet = true; + break; + } + else + pLast = pNd; + } + else + break; + } + else if( !lcl_IsValidPrevNextNumNode( aIdx )) + break; + + if( bNext ) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + if( !bRet && !bOverUpper && pLast ) // do not iterate over higher numbers, but still to the end + { + if( bNext ) + { + rPos.nNode = aIdx; + if( aIdx.GetNode().IsContentNode() ) + rPos.nContent.Assign( aIdx.GetNode().GetContentNode(), 0 ); + } + else + { + rPos.nNode.Assign( *pLast ); + rPos.nContent.Assign( const_cast<SwTextNode*>(pLast), 0 ); + } + bRet = true; + } + + if( bRet ) + { + if( pUpper ) + *pUpper = nUpper; + if( pLower ) + *pLower = nLower; + } + return bRet; +} + +bool SwDoc::GotoNextNum(SwPosition& rPos, SwRootFrame const*const pLayout, + bool bOverUpper, sal_uInt8* pUpper, sal_uInt8* pLower) +{ + return ::lcl_GotoNextPrevNum(rPos, true, bOverUpper, pUpper, pLower, pLayout); +} + +const SwNumRule * SwDoc::SearchNumRule(const SwPosition & rPos, + const bool bForward, + const bool bNum, + const bool bOutline, + int nNonEmptyAllowed, + OUString& sListId, + SwRootFrame const* pLayout, + const bool bInvestigateStartNode) +{ + const SwNumRule * pResult = nullptr; + SwTextNode * pTextNd = rPos.nNode.GetNode().GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, rPos.nNode); + } + SwNode * pStartFromNode = pTextNd; + + if (pTextNd) + { + SwNodeIndex aIdx(rPos.nNode); + + // - the start node has also been investigated, if requested. + const SwNode * pNode = nullptr; + do + { + if ( !bInvestigateStartNode ) + { + if (bForward) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + if (aIdx.GetNode().IsTextNode()) + { + pTextNd = aIdx.GetNode().GetTextNode(); + + const SwNumRule * pNumRule = pTextNd->GetNumRule(); + if (pNumRule) + { + if ( ( pNumRule->IsOutlineRule() == bOutline ) && + ( ( bNum && pNumRule->Get(0).IsEnumeration()) || + ( !bNum && pNumRule->Get(0).IsItemize() ) ) ) // #i22362#, #i29560# + { + pResult = pTextNd->GetNumRule(); + // provide also the list id, to which the text node belongs. + sListId = pTextNd->GetListId(); + } + + break; + } + else if (pTextNd->Len() > 0 || nullptr != pTextNd->GetNumRule()) + { + if (nNonEmptyAllowed == 0) + break; + + nNonEmptyAllowed--; + + if (nNonEmptyAllowed < 0) + nNonEmptyAllowed = -1; + } + } + + if ( bInvestigateStartNode ) + { + if (bForward) + sw::GotoNextLayoutTextFrame(aIdx, pLayout); + else + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + } + + pNode = &aIdx.GetNode(); + } + while (!(pNode == GetNodes().DocumentSectionStartNode(pStartFromNode) || + pNode == GetNodes().DocumentSectionEndNode(pStartFromNode))); + } + + return pResult; +} + +bool SwDoc::GotoPrevNum(SwPosition& rPos, SwRootFrame const*const pLayout, + bool bOverUpper) +{ + return ::lcl_GotoNextPrevNum(rPos, false, bOverUpper, nullptr, nullptr, pLayout); +} + +bool SwDoc::NumUpDown(const SwPaM& rPam, bool bDown, SwRootFrame const*const pLayout) +{ + SwPaM aPam(rPam, nullptr); + ExpandPamForParaPropsNodes(aPam, pLayout); + sal_uLong nStt = aPam.Start()->nNode.GetIndex(); + sal_uLong const nEnd = aPam.End()->nNode.GetIndex(); + + // -> outline nodes are promoted or demoted differently + bool bOnlyOutline = true; + bool bOnlyNonOutline = true; + for (sal_uLong n = nStt; n <= nEnd; n++) + { + SwTextNode * pTextNd = GetNodes()[n]->GetTextNode(); + + if (pTextNd) + { + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, *pTextNd); + } + SwNumRule * pRule = pTextNd->GetNumRule(); + + if (pRule) + { + if (pRule->IsOutlineRule()) + bOnlyNonOutline = false; + else + bOnlyOutline = false; + } + } + } + + bool bRet = true; + sal_Int8 nDiff = bDown ? 1 : -1; + + if (bOnlyOutline) + bRet = OutlineUpDown(rPam, nDiff, pLayout); + else if (bOnlyNonOutline) + { + /* #i24560# + Only promote or demote if all selected paragraphs are + promotable resp. demotable. + */ + for (sal_uLong nTmp = nStt; nTmp <= nEnd; ++nTmp) + { + SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode(); + + // Make code robust: consider case that the node doesn't denote a + // text node. + if ( pTNd ) + { + if (pLayout) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + } + + SwNumRule * pRule = pTNd->GetNumRule(); + + if (pRule) + { + sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel()); + if( (-1 == nDiff && 0 >= nLevel) || + (1 == nDiff && MAXLEVEL - 1 <= nLevel)) + bRet = false; + } + } + } + + if( bRet ) + { + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumUpDown>(aPam, nDiff) ); + } + + SwTextNode* pPrev = nullptr; + for(sal_uLong nTmp = nStt; nTmp <= nEnd; ++nTmp ) + { + SwTextNode* pTNd = GetNodes()[ nTmp ]->GetTextNode(); + + if( pTNd) + { + if (pLayout) + { + pTNd = sw::GetParaPropsNode(*pLayout, *pTNd); + if (pTNd == pPrev) + { + continue; + } + pPrev = pTNd; + } + + SwNumRule * pRule = pTNd->GetNumRule(); + + if (pRule) + { + sal_uInt8 nLevel = static_cast<sal_uInt8>(pTNd->GetActualListLevel()); + nLevel = nLevel + nDiff; + + pTNd->SetAttrListLevel(nLevel); + } + } + } + + ChkCondColls(); + getIDocumentState().SetModified(); + } + } + + return bRet; +} + +// this function doesn't contain any numbering-related code, but it is +// primarily called to move numbering-relevant paragraphs around, hence +// it will expand its selection to include full SwTextFrames. +bool SwDoc::MoveParagraph(SwPaM& rPam, long nOffset, bool const bIsOutlMv) +{ + // sw_redlinehide: as long as a layout with Hide mode exists, only + // move nodes that have merged frames *completely* + SwRootFrame const* pLayout(nullptr); + for (SwRootFrame const*const pLay : GetAllLayouts()) + { + if (pLay->IsHideRedlines()) + { + pLayout = pLay; + } + } + if (pLayout) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*pLayout, rPam.Start()->nNode)); + if (nodes.first && nodes.first != &rPam.Start()->nNode.GetNode()) + { + assert(nodes.second); + if (nOffset < 0) + { + nOffset += rPam.Start()->nNode.GetIndex() - nodes.first->GetIndex(); + if (0 <= nOffset) // hack: there are callers that know what + { // node they want; those should never need + nOffset = -1; // this; other callers just pass in -1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(nodes.first->GetIndex() < rPam.Start()->nNode.GetIndex()); + rPam.Start()->nNode = *nodes.first; + rPam.Start()->nContent.Assign(nodes.first, 0); + } + nodes = sw::GetFirstAndLastNode(*pLayout, rPam.End()->nNode); + if (nodes.second && nodes.second != &rPam.End()->nNode.GetNode()) + { + assert(nodes.first); + if (0 < nOffset) + { + nOffset -= nodes.second->GetIndex() - rPam.End()->nNode.GetIndex(); + if (nOffset <= 0) // hack: there are callers that know what + { // node they want; those should never need + nOffset = +1; // this; other callers just pass in +1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(rPam.End()->nNode.GetIndex() < nodes.second->GetIndex()); + rPam.End()->nNode = *nodes.second; + // until end, otherwise Impl will detect overlapping redline + rPam.End()->nContent.Assign(nodes.second, nodes.second->GetTextNode()->Len()); + } + + if (nOffset > 0) + { // sw_redlinehide: avoid moving into delete redline, skip forward + if (GetNodes().GetEndOfContent().GetIndex() <= rPam.End()->nNode.GetIndex() + nOffset) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.End()->nNode.GetIndex() + nOffset + 1]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; ++nOffset) + { + pNode = GetNodes()[rPam.End()->nNode.GetIndex() + nOffset]; + if (pNode->IsTextNode()) + { + nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode()); + assert(nodes.first && nodes.second); + nOffset += nodes.second->GetIndex() - pNode->GetIndex(); + // on last; will be incremented below to behind-last + break; + } + } + } + } + else + { // sw_redlinehide: avoid moving into delete redline, skip backward + if (rPam.Start()->nNode.GetIndex() + nOffset < 1) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.Start()->nNode.GetIndex() + nOffset]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; --nOffset) + { + pNode = GetNodes()[rPam.Start()->nNode.GetIndex() + nOffset]; + if (pNode->IsTextNode()) + { + nodes = GetFirstAndLastNode(*pLayout, *pNode->GetTextNode()); + assert(nodes.first && nodes.second); + nOffset -= pNode->GetIndex() - nodes.first->GetIndex(); + // on first + break; + } + } + } + } + } + return MoveParagraphImpl(rPam, nOffset, bIsOutlMv, pLayout); +} + +bool SwDoc::MoveParagraphImpl(SwPaM& rPam, long const nOffset, + bool const bIsOutlMv, SwRootFrame const*const pLayout) +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + sal_uLong nStIdx = pStt->nNode.GetIndex(); + sal_uLong nEndIdx = pEnd->nNode.GetIndex(); + + // Here are some sophisticated checks whether the wished PaM will be moved or not. + // For moving outlines (bIsOutlMv) I've already done some checks, so here are two different + // checks... + SwNode *pTmp1; + SwNode *pTmp2; + if( bIsOutlMv ) + { + // For moving chapters (outline) the following reason will deny the move: + // if a start node is inside the moved range and its end node outside or vice versa. + // If a start node is the first moved paragraph, its end node has to be within the moved + // range, too (e.g. as last node). + // If an end node is the last node of the moved range, its start node has to be a part of + // the moved section, too. + pTmp1 = GetNodes()[ nStIdx ]; + if( pTmp1->IsStartNode() ) + { // First is a start node + pTmp2 = pTmp1->EndOfSectionNode(); + if( pTmp2->GetIndex() > nEndIdx ) + return false; // Its end node is behind the moved range + } + pTmp1 = pTmp1->StartOfSectionNode()->EndOfSectionNode(); + if( pTmp1->GetIndex() <= nEndIdx ) + return false; // End node inside but start node before moved range => no. + pTmp1 = GetNodes()[ nEndIdx ]; + if( pTmp1->IsEndNode() ) + { // The last one is an end node + pTmp1 = pTmp1->StartOfSectionNode(); + if( pTmp1->GetIndex() < nStIdx ) + return false; // Its start node is before the moved range. + } + pTmp1 = pTmp1->StartOfSectionNode(); + if( pTmp1->GetIndex() >= nStIdx ) + return false; // A start node which ends behind the moved range => no. + } + + sal_uLong nInStIdx, nInEndIdx; + long nOffs = nOffset; + if( nOffset > 0 ) + { + nInEndIdx = nEndIdx; + nEndIdx += nOffset; + ++nOffs; + } + else + { + // Impossible to move to negative index + if( o3tl::make_unsigned(std::abs( nOffset )) > nStIdx) + return false; + + nInEndIdx = nStIdx - 1; + nStIdx += nOffset; + } + nInStIdx = nInEndIdx + 1; + // The following paragraphs shall be swapped: + // Swap [ nStIdx, nInEndIdx ] with [ nInStIdx, nEndIdx ] + + if( nEndIdx >= GetNodes().GetEndOfContent().GetIndex() ) + return false; + + if( !bIsOutlMv ) + { // And here the restrictions for moving paragraphs other than chapters (outlines) + // The plan is to exchange [nStIdx,nInEndIdx] and [nStartIdx,nEndIdx] + // It will checked if the both "start" nodes as well as the both "end" notes belongs to + // the same start-end-section. This is more restrictive than the conditions checked above. + // E.g. a paragraph will not escape from a section or be inserted to another section. + pTmp1 = GetNodes()[ nStIdx ]->StartOfSectionNode(); + pTmp2 = GetNodes()[ nInStIdx ]->StartOfSectionNode(); + if( pTmp1 != pTmp2 ) + return false; // "start" nodes in different sections + pTmp1 = GetNodes()[ nEndIdx ]; + bool bIsEndNode = pTmp1->IsEndNode(); + if( !pTmp1->IsStartNode() ) + { + pTmp1 = pTmp1->StartOfSectionNode(); + if( bIsEndNode ) // For end nodes the first start node is of course inside the range, + pTmp1 = pTmp1->StartOfSectionNode(); // I've to check the start node of the start node. + } + pTmp1 = pTmp1->EndOfSectionNode(); + pTmp2 = GetNodes()[ nInEndIdx ]; + if( !pTmp2->IsStartNode() ) + { + bIsEndNode = pTmp2->IsEndNode(); + pTmp2 = pTmp2->StartOfSectionNode(); + if( bIsEndNode ) + pTmp2 = pTmp2->StartOfSectionNode(); + } + pTmp2 = pTmp2->EndOfSectionNode(); + if( pTmp1 != pTmp2 ) + return false; // The "end" notes are in different sections + } + + // Test for Redlining - Can the Selection be moved at all, actually? + if( !getIDocumentRedlineAccess().IsIgnoreRedline() ) + { + SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->nNode.GetNode(), RedlineType::Delete ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwPosition aStPos( *pStt ), aEndPos( *pEnd ); + aStPos.nContent = 0; + SwContentNode* pCNd = pEnd->nNode.GetNode().GetContentNode(); + aEndPos.nContent = pCNd ? pCNd->Len() : 1; + bool bCheckDel = true; + + // There is a some Redline Delete Object for the range + for( ; nRedlPos < getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedlPos ) + { + const SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + if( !bCheckDel || RedlineType::Delete == pTmp->GetType() ) + { + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + switch( ComparePosition( *pRStt, *pREnd, aStPos, aEndPos )) + { + case SwComparePosition::CollideStart: + case SwComparePosition::Behind: // Pos1 comes after Pos2 + nRedlPos = getIDocumentRedlineAccess().GetRedlineTable().size(); + break; + + case SwComparePosition::CollideEnd: + case SwComparePosition::Before: // Pos1 comes before Pos2 + break; + case SwComparePosition::Inside: // Pos1 is completely inside Pos2 + // that's valid, but check all following for overlapping + bCheckDel = false; + break; + + case SwComparePosition::Outside: // Pos2 is completely inside Pos1 + case SwComparePosition::Equal: // Pos1 is equal to Pos2 + case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 in the beginning + case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at the end + return false; + } + } + } + } + } + + { + // Send DataChanged before moving. We then can detect + // which objects are still in the range. + // After the move they could come before/after the + // Position. + SwDataChanged aTmp( rPam ); + } + + SwNodeIndex aIdx( nOffset > 0 ? pEnd->nNode : pStt->nNode, nOffs ); + SwNodeRange aMvRg( pStt->nNode, 0, pEnd->nNode, +1 ); + + SwRangeRedline* pOwnRedl = nullptr; + if( getIDocumentRedlineAccess().IsRedlineOn() ) + { + // If the range is completely in the own Redline, we can move it! + SwRedlineTable::size_type nRedlPos = getIDocumentRedlineAccess().GetRedlinePos( pStt->nNode.GetNode(), RedlineType::Insert ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); + SwRangeRedline aTmpRedl( RedlineType::Insert, rPam ); + const SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + // Is completely in the range and is the own Redline too? + if( aTmpRedl.IsOwnRedline( *pTmp ) && + (pRStt->nNode < pStt->nNode || + (pRStt->nNode == pStt->nNode && !pRStt->nContent.GetIndex()) ) && + (pEnd->nNode < pREnd->nNode || + (pEnd->nNode == pREnd->nNode && + pCEndNd ? pREnd->nContent.GetIndex() == pCEndNd->Len() + : !pREnd->nContent.GetIndex() )) ) + { + pOwnRedl = pTmp; + if( nRedlPos + 1 < getIDocumentRedlineAccess().GetRedlineTable().size() ) + { + pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos+1 ]; + if( *pTmp->Start() == *pREnd ) + // then don't! + pOwnRedl = nullptr; + } + + if( pOwnRedl && + !( pRStt->nNode <= aIdx && aIdx <= pREnd->nNode )) + { + // it's not in itself, so don't move it + pOwnRedl = nullptr; + } + } + } + + if( !pOwnRedl ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + // First the Insert, then the Delete + SwPosition aInsPos( aIdx ); + aInsPos.nContent.Assign( aIdx.GetNode().GetContentNode(), 0 ); + + SwPaM aPam( pStt->nNode, 0, aMvRg.aEnd, 0 ); + + SwPaM& rOrigPam(rPam); + rOrigPam.DeleteMark(); + rOrigPam.GetPoint()->nNode = aIdx.GetIndex() - 1; + rOrigPam.GetPoint()->nContent.Assign( rOrigPam.GetContentNode(), 0 ); + + bool bDelLastPara = !aInsPos.nNode.GetNode().IsContentNode(); + + /* When copying to a non-content node Copy will + insert a paragraph before that node and insert before + that inserted node. Copy creates an SwUndoInserts that + does not cover the extra paragraph. Thus we insert the + extra paragraph ourselves, _with_ correct undo + information. */ + if (bDelLastPara) + { + /* aInsPos points to the non-content node. Move it to + the previous content node. */ + SwPaM aInsPam(aInsPos); + const bool bMoved = aInsPam.Move(fnMoveBackward); + OSL_ENSURE(bMoved, "No content node found!"); + + if (bMoved) + { + /* Append the new node after the content node + found. The new position to insert the moved + paragraph at is before the inserted + paragraph. */ + getIDocumentContentOperations().AppendTextNode(*aInsPam.GetPoint()); + aInsPos = *aInsPam.GetPoint(); + } + } + + --aIdx; // move before insertion + + getIDocumentContentOperations().CopyRange(aPam, aInsPos, SwCopyFlags::CheckPosInFly); + + // now delete all the delete redlines that were copied +#ifndef NDEBUG + size_t nRedlines(getIDocumentRedlineAccess().GetRedlineTable().size()); +#endif + if (nOffset > 0) + assert(aPam.End()->nNode.GetIndex() - aPam.Start()->nNode.GetIndex() + nOffset == aInsPos.nNode.GetIndex() - aPam.End()->nNode.GetIndex()); + else + assert(aPam.Start()->nNode.GetIndex() - aPam.End()->nNode.GetIndex() + nOffset == aInsPos.nNode.GetIndex() - aPam.End()->nNode.GetIndex()); + SwRedlineTable::size_type i; + getIDocumentRedlineAccess().GetRedline(*aPam.End(), &i); + for ( ; 0 < i; --i) + { // iterate backwards and offset via the start nodes difference + SwRangeRedline const*const pRedline = getIDocumentRedlineAccess().GetRedlineTable()[i - 1]; + if (*pRedline->End() < *aPam.Start()) + { + break; + } + if (pRedline->GetType() == RedlineType::Delete) + { + assert(*aPam.Start() <= *pRedline->Start()); // caller's fault + SwRangeRedline* pNewRedline; + { + SwPaM pam(*pRedline, nullptr); + sal_uLong const nCurrentOffset( + aIdx.GetIndex() + 1 - aPam.Start()->nNode.GetIndex()); + pam.GetPoint()->nNode += nCurrentOffset; + pam.GetPoint()->nContent.Assign(pam.GetPoint()->nNode.GetNode().GetContentNode(), pam.GetPoint()->nContent.GetIndex()); + pam.GetMark()->nNode += nCurrentOffset; + pam.GetMark()->nContent.Assign(pam.GetMark()->nNode.GetNode().GetContentNode(), pam.GetMark()->nContent.GetIndex()); + + pNewRedline = new SwRangeRedline( RedlineType::Delete, pam ); + } + // note: effectively this will DeleteAndJoin the pam! + getIDocumentRedlineAccess().AppendRedline(pNewRedline, true); + assert(getIDocumentRedlineAccess().GetRedlineTable().size() <= nRedlines); + } + } + + if( bDelLastPara ) + { + // We need to remove the last empty Node again + aIdx = aInsPos.nNode; + SwContentNode* pCNd = SwNodes::GoPrevious( &aInsPos.nNode ); + aInsPos.nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + + // All, that are in the to-be-deleted Node, need to be + // moved to the next Node + for(SwRangeRedline* pTmp : getIDocumentRedlineAccess().GetRedlineTable()) + { + SwPosition* pPos = &pTmp->GetBound(); + if( pPos->nNode == aIdx ) + { + ++pPos->nNode; + pPos->nContent.Assign( pPos->nNode.GetNode().GetContentNode(),0); + } + pPos = &pTmp->GetBound(false); + if( pPos->nNode == aIdx ) + { + ++pPos->nNode; + pPos->nContent.Assign( pPos->nNode.GetNode().GetContentNode(),0); + } + } + CorrRel( aIdx, aInsPos ); + + if (pCNd) + pCNd->JoinNext(); + } + + ++rOrigPam.GetPoint()->nNode; + rOrigPam.GetPoint()->nContent.Assign( rOrigPam.GetContentNode(), 0 ); + assert(*aPam.GetMark() < *aPam.GetPoint()); + if (aPam.GetPoint()->nNode.GetNode().IsEndNode()) + { // ensure redline ends on content node + --aPam.GetPoint()->nNode; + assert(aPam.GetPoint()->nNode.GetNode().IsTextNode()); + SwTextNode *const pNode(aPam.GetPoint()->nNode.GetNode().GetTextNode()); + aPam.GetPoint()->nContent.Assign(pNode, pNode->Len()); + } + + RedlineFlags eOld = getIDocumentRedlineAccess().GetRedlineFlags(); + if (GetIDocumentUndoRedo().DoesUndo()) + { + // this should no longer happen in calls from the UI but maybe via API + SAL_WARN_IF((eOld & RedlineFlags::ShowMask) != RedlineFlags::ShowMask, + "sw.core", "redlines will be moved in DeleteAndJoin"); + + getIDocumentRedlineAccess().SetRedlineFlags( + RedlineFlags::On | RedlineFlags::ShowInsert | RedlineFlags::ShowDelete ); + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRedlineDelete>(aPam, SwUndoId::DELETE)); + } + + SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, aPam ); + + // prevent assertion from aPam's target being deleted + // (Alternatively, one could just let aPam go out of scope, but + // that requires touching a lot of code.) + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound(false).nContent.Assign( nullptr, 0 ); + + getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + + aPam.GetBound().nContent.Assign(aPam.GetBound().nNode.GetNode().GetContentNode(), 0); + aPam.GetBound(false).nContent.Assign(aPam.GetBound(false).nNode.GetNode().GetContentNode(), 0); + sw::UpdateFramesForAddDeleteRedline(*this, aPam); + + getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + getIDocumentState().SetModified(); + + return true; + } + } + + if( !pOwnRedl && !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aTemp(aIdx); + getIDocumentRedlineAccess().SplitRedline(aTemp); + } + + sal_uLong nRedlSttNd(0), nRedlEndNd(0); + if( pOwnRedl ) + { + const SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End(); + nRedlSttNd = pRStt->nNode.GetIndex(); + nRedlEndNd = pREnd->nNode.GetIndex(); + } + + std::unique_ptr<SwUndoMoveNum> pUndo; + sal_uLong nMoved = 0; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMoveNum( rPam, nOffset, bIsOutlMv )); + nMoved = rPam.End()->nNode.GetIndex() - rPam.Start()->nNode.GetIndex() + 1; + } + + (void) pLayout; // note: move will insert between aIdx-1 and aIdx + assert(!pLayout // check not moving *into* delete redline (caller's fault) + || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::None + || aIdx.GetNode().GetRedlineMergeFlag() == SwNode::Merge::First); + getIDocumentContentOperations().MoveNodeRange( aMvRg, aIdx, SwMoveFlags::REDLINES ); + + if( pUndo ) + { + // i57907: Under circumstances (sections at the end of a chapter) + // the rPam.Start() is not moved to the new position. + // But aIdx should be at the new end position and as long as the + // number of moved paragraphs is nMoved, I know, where the new + // position is. + pUndo->SetStartNode( aIdx.GetIndex() - nMoved ); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + if( pOwnRedl ) + { + SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End(); + if( pRStt->nNode.GetIndex() != nRedlSttNd ) + { + pRStt->nNode = nRedlSttNd; + pRStt->nContent.Assign( pRStt->nNode.GetNode().GetContentNode(),0); + } + if( pREnd->nNode.GetIndex() != nRedlEndNd ) + { + pREnd->nNode = nRedlEndNd; + SwContentNode* pCNd = pREnd->nNode.GetNode().GetContentNode(); + pREnd->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); + } + } + + getIDocumentState().SetModified(); + return true; +} + +bool SwDoc::NumOrNoNum( const SwNodeIndex& rIdx, bool bDel ) +{ + bool bResult = false; + SwTextNode * pTextNd = rIdx.GetNode().GetTextNode(); + + if (pTextNd && pTextNd->GetNumRule() != nullptr && + (pTextNd->HasNumber() || pTextNd->HasBullet())) + { + if ( !pTextNd->IsCountedInList() == !bDel) + { + bool bOldNum = bDel; + bool bNewNum = !bDel; + pTextNd->SetCountedInList(bNewNum); + + getIDocumentState().SetModified(); + + bResult = true; + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumOrNoNum>(rIdx, bOldNum, bNewNum)); + } + } + else if (bDel && pTextNd->GetNumRule(false) && + pTextNd->GetActualListLevel() >= 0 && + pTextNd->GetActualListLevel() < MAXLEVEL) + { + SwPaM aPam(*pTextNd); + DelNumRules(aPam); + + bResult = true; + } + } + + return bResult; +} + +SwNumRule* SwDoc::GetNumRuleAtPos(SwPosition& rPos, + SwRootFrame const*const pLayout) +{ + SwNumRule* pRet = nullptr; + SwTextNode* pTNd = rPos.nNode.GetNode().GetTextNode(); + + if ( pTNd != nullptr ) + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTNd)) + { + pTNd = static_cast<SwTextFrame*>(pTNd->getLayoutFrame(pLayout))->GetMergedPara()->pParaPropsNode; + rPos.nNode = *pTNd; + rPos.nContent.Assign(pTNd, 0); + } + pRet = pTNd->GetNumRule(); + } + + return pRet; +} + +sal_uInt16 SwDoc::FindNumRule( const OUString& rName ) const +{ + for( sal_uInt16 n = mpNumRuleTable->size(); n; ) + if( (*mpNumRuleTable)[ --n ]->GetName() == rName ) + return n; + + return USHRT_MAX; +} + +SwNumRule* SwDoc::FindNumRulePtr( const OUString& rName ) const +{ + SwNumRule * pResult = maNumRuleMap[rName]; + + if ( !pResult ) + { + for (size_t n = 0; n < mpNumRuleTable->size(); ++n) + { + if ((*mpNumRuleTable)[n]->GetName() == rName) + { + pResult = (*mpNumRuleTable)[n]; + + break; + } + } + } + + return pResult; +} + +void SwDoc::AddNumRule(SwNumRule * pRule) +{ + if ((SAL_MAX_UINT16 - 1) <= mpNumRuleTable->size()) + { + OSL_ENSURE(false, "SwDoc::AddNumRule: table full."); + abort(); // this should never happen on real documents + } + mpNumRuleTable->push_back(pRule); + maNumRuleMap[pRule->GetName()] = pRule; + pRule->SetNumRuleMap(&maNumRuleMap); + + getIDocumentListsAccess().createListForListStyle( pRule->GetName() ); +} + +sal_uInt16 SwDoc::MakeNumRule( const OUString &rName, + const SwNumRule* pCpy, + bool bBroadcast, + const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode ) +{ + SwNumRule* pNew; + if( pCpy ) + { + pNew = new SwNumRule( *pCpy ); + + pNew->SetName( GetUniqueNumRuleName( &rName ), getIDocumentListsAccess() ); + + if( pNew->GetName() != rName ) + { + pNew->SetPoolFormatId( USHRT_MAX ); + pNew->SetPoolHelpId( USHRT_MAX ); + pNew->SetPoolHlpFileId( UCHAR_MAX ); + pNew->SetDefaultListId( OUString() ); + } + pNew->CheckCharFormats( this ); + } + else + { + pNew = new SwNumRule( GetUniqueNumRuleName( &rName ), + eDefaultNumberFormatPositionAndSpaceMode ); + } + + sal_uInt16 nRet = mpNumRuleTable->size(); + + AddNumRule(pNew); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoNumruleCreate>(pNew, this)); + } + + if (bBroadcast) + BroadcastStyleOperation(pNew->GetName(), SfxStyleFamily::Pseudo, + SfxHintId::StyleSheetCreated); + + return nRet; +} + +OUString SwDoc::GetUniqueNumRuleName( const OUString* pChkStr, bool bAutoNum ) const +{ + // If we got pChkStr, then the caller expects that in case it's not yet + // used, it'll be returned. + if( IsInMailMerge() && !pChkStr ) + { + OUString newName = "MailMergeNumRule" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpNumRuleTable->size() + 1 ); + return newName; + } + + OUString aName; + if( bAutoNum ) + { + static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr); + + if (bHack) + { + static sal_Int64 nIdCounter = SAL_CONST_INT64(8000000000); + aName = OUString::number(nIdCounter++); + } + else + { + unsigned int const n(comphelper::rng::uniform_uint_distribution(0, + std::numeric_limits<unsigned int>::max())); + aName = OUString::number(n); + } + if( pChkStr && pChkStr->isEmpty() ) + pChkStr = nullptr; + } + else if( pChkStr && !pChkStr->isEmpty() ) + aName = *pChkStr; + else + { + pChkStr = nullptr; + aName = SwResId( STR_NUMRULE_DEFNAME ); + } + + sal_uInt16 nNum(0), nTmp, nFlagSize = ( mpNumRuleTable->size() / 8 ) +2; + std::unique_ptr<sal_uInt8[]> pSetFlags(new sal_uInt8[ nFlagSize ]); + memset( pSetFlags.get(), 0, nFlagSize ); + + sal_Int32 nNmLen = aName.getLength(); + if( !bAutoNum && pChkStr ) + { + while( nNmLen-- && '0' <= aName[nNmLen] && aName[nNmLen] <= '9' ) + ; //nop + + if( ++nNmLen < aName.getLength() ) + { + aName = aName.copy(0, nNmLen ); + pChkStr = nullptr; + } + } + + for( auto const & pNumRule: *mpNumRuleTable ) + if( nullptr != pNumRule ) + { + const OUString sNm = pNumRule->GetName(); + if( sNm.startsWith( aName ) ) + { + // Determine Number and set the Flag + nNum = static_cast<sal_uInt16>(sNm.copy( nNmLen ).toInt32()); + if( nNum-- && nNum < mpNumRuleTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if( pChkStr && *pChkStr==sNm ) + pChkStr = nullptr; + } + + if( !pChkStr ) + { + // All Numbers have been flagged accordingly, so identify the right Number + nNum = mpNumRuleTable->size(); + for( sal_uInt16 n = 0; n < nFlagSize; ++n ) + if( 0xff != ( nTmp = pSetFlags[ n ] )) + { + // identify the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + if( pChkStr && !pChkStr->isEmpty() ) + return *pChkStr; + return aName + OUString::number( ++nNum ); +} + +void SwDoc::UpdateNumRule() +{ + const SwNumRuleTable& rNmTable = GetNumRuleTable(); + for( size_t n = 0; n < rNmTable.size(); ++n ) + if( rNmTable[ n ]->IsInvalidRule() ) + rNmTable[ n ]->Validate(); +} + +void SwDoc::MarkListLevel( const OUString& sListId, + const int nListLevel, + const bool bValue ) +{ + SwList* pList = getIDocumentListsAccess().getListByName( sListId ); + + if ( pList ) + { + // Set new marked list level and notify all affected nodes of the changed mark. + pList->MarkListLevel( nListLevel, bValue ); + } +} + +bool SwDoc::IsFirstOfNumRuleAtPos(const SwPosition & rPos, + SwRootFrame const& rLayout) +{ + bool bResult = false; + + const SwTextNode *const pTextNode = sw::GetParaPropsNode(rLayout, rPos.nNode); + if ( pTextNode != nullptr ) + { + bResult = pTextNode->IsFirstOfNumRule(rLayout); + } + + return bResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docredln.cxx b/sw/source/core/doc/docredln.cxx new file mode 100644 index 000000000..b98e89caf --- /dev/null +++ b/sw/source/core/doc/docredln.cxx @@ -0,0 +1,1906 @@ +/* -*- 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 <boost/property_tree/json_parser.hpp> + +#include <sal/log.hxx> +#include <tools/datetimeutils.hxx> +#include <hintids.hxx> +#include <svl/itemiter.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <unotools/datetime.hxx> +#include <sfx2/viewsh.hxx> +#include <swmodule.hxx> +#include <doc.hxx> +#include <docredln.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <docary.hxx> +#include <ndtxt.hxx> +#include <redline.hxx> +#include <UndoCore.hxx> +#include <hints.hxx> +#include <pamtyp.hxx> +#include <poolfmt.hxx> +#include <view.hxx> +#include <viewsh.hxx> +#include <viscrs.hxx> +#include <rootfrm.hxx> +#include <strings.hrc> +#include <wrtsh.hxx> +#include <txtfld.hxx> + +#include <flowfrm.hxx> + +using namespace com::sun::star; + +#ifdef DBG_UTIL + + void sw_DebugRedline( const SwDoc* pDoc ) + { + static SwRedlineTable::size_type nWatch = 0; // loplugin:constvars:ignore + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for( SwRedlineTable::size_type n = 0; n < rTable.size(); ++n ) + { + SwRedlineTable::size_type nDummy = 0; + const SwRangeRedline* pCurrent = rTable[ n ]; + const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr; + if( pCurrent == pNext ) + ++nDummy; + if( n == nWatch ) + ++nDummy; // Possible debugger breakpoint + } + } + +#endif + + +SwExtraRedlineTable::~SwExtraRedlineTable() +{ + DeleteAndDestroyAll(); +} + +void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos) + { + const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos); + xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); +} + +#if OSL_DEBUG_LEVEL > 0 +static bool CheckPosition( const SwPosition* pStt, const SwPosition* pEnd ) +{ + int nError = 0; + SwNode* pSttNode = &pStt->nNode.GetNode(); + SwNode* pEndNode = &pEnd->nNode.GetNode(); + SwNode* pSttTab = pSttNode->StartOfSectionNode()->FindTableNode(); + SwNode* pEndTab = pEndNode->StartOfSectionNode()->FindTableNode(); + SwNode* pSttStart = pSttNode; + while( pSttStart && (!pSttStart->IsStartNode() || pSttStart->IsSectionNode() || + pSttStart->IsTableNode() ) ) + pSttStart = pSttStart->StartOfSectionNode(); + SwNode* pEndStart = pEndNode; + while( pEndStart && (!pEndStart->IsStartNode() || pEndStart->IsSectionNode() || + pEndStart->IsTableNode() ) ) + pEndStart = pEndStart->StartOfSectionNode(); + assert(pSttTab == pEndTab); + if( pSttTab != pEndTab ) + nError = 1; + assert(pSttTab || pSttStart == pEndStart); + if( !pSttTab && pSttStart != pEndStart ) + nError |= 2; + if( nError ) + nError += 10; + return nError != 0; +} +#endif + +bool SwExtraRedlineTable::DeleteAllTableRedlines( SwDoc* pDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for (sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline); + if (pTableCellRedline) + { + const SwTableBox *pRedTabBox = &pTableCellRedline->GetTableBox(); + const SwTable& rRedTable = pRedTabBox->GetSttNd()->FindTableNode()->GetTable(); + if ( &rRedTable == &rTable ) + { + // Redline for this table + const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType) + { + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + continue; // don't increment position after delete + } + } + } + else + { + const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline); + if (pTableRowRedline) + { + const SwTableLine *pRedTabLine = &pTableRowRedline->GetTableLine(); + const SwTableBoxes &rRedTabBoxes = pRedTabLine->GetTabBoxes(); + const SwTable& rRedTable = rRedTabBoxes[0]->GetSttNd()->FindTableNode()->GetTable(); + if ( &rRedTable == &rTable ) + { + // Redline for this table + const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if (RedlineType::Any == nRedlineTypeToDelete || nRedlineTypeToDelete == nRedlineType) + + { + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + continue; // don't increment position after delete + } + } + } + } + ++nCurRedlinePos; + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +bool SwExtraRedlineTable::DeleteTableRowRedline( SwDoc* pDoc, const SwTableLine& rTableLine, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableRowRedline* pTableRowRedline = dynamic_cast<const SwTableRowRedline*>(pExtraRedline); + const SwTableLine *pRedTabLine = pTableRowRedline ? &pTableRowRedline->GetTableLine() : nullptr; + if ( pRedTabLine == &rTableLine ) + { + // Redline for this table row + const SwRedlineData& aRedlineData = pTableRowRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType ) + continue; + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + } + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +bool SwExtraRedlineTable::DeleteTableCellRedline( SwDoc* pDoc, const SwTableBox& rTableBox, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // #TODO - Add 'Undo' support for deleting 'Table Cell' redlines + /* + SwUndoRedline* pUndo = new SwUndoRedline( SwUndoId::REDLINE, rRange ); + if( pUndo->GetRedlSaveCount() ) + { + GetIDocumentUndoRedo().AppendUndo(pUndo); + } + else + delete pUndo; + */ + } + + for(sal_uInt16 nCurRedlinePos = 0; nCurRedlinePos < GetSize(); ++nCurRedlinePos ) + { + SwExtraRedline* pExtraRedline = GetRedline(nCurRedlinePos); + const SwTableCellRedline* pTableCellRedline = dynamic_cast<const SwTableCellRedline*>(pExtraRedline); + const SwTableBox *pRedTabBox = pTableCellRedline ? &pTableCellRedline->GetTableBox() : nullptr; + if ( pRedTabBox == &rTableBox ) + { + // Redline for this table cell + const SwRedlineData& aRedlineData = pTableCellRedline->GetRedlineData(); + const RedlineType nRedlineType = aRedlineData.GetType(); + + // Check if this redline object type should be deleted + if( RedlineType::Any != nRedlineTypeToDelete && nRedlineTypeToDelete != nRedlineType ) + continue; + + DeleteAndDestroy( nCurRedlinePos ); + bChg = true; + } + } + + if( bChg ) + pDoc->getIDocumentState().SetModified(); + + return bChg; +} + +namespace +{ + +void lcl_LOKInvalidateFrames(const SwModify& rMod, const SwRootFrame* pLayout, + SwFrameType const nFrameType, const Point* pPoint) +{ + SwIterator<SwFrame, SwModify, sw::IteratorMode::UnwrapMulti> aIter(rMod); + + for (SwFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + if ((pTmpFrame->GetType() & nFrameType) && + (!pLayout || pLayout == pTmpFrame->getRootFrame()) && + (!pTmpFrame->IsFlowFrame() || !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow())) + { + if (pPoint) + { + pTmpFrame->InvalidateSize(); + } + } + } +} + +void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor) +{ + if (!(rCursor.HasMark() && + rCursor.GetPoint()->nNode.GetNode().IsContentNode() && + rCursor.GetPoint()->nNode.GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) && + (rCursor.GetMark()->nNode == rCursor.GetPoint()->nNode || + (rCursor.GetMark()->nNode.GetNode().IsContentNode() && + rCursor.GetMark()->nNode.GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()))))) + { + return; + } + + + SwPosition *pStartPos = rCursor.Start(), + *pEndPos = rCursor.GetPoint() == pStartPos ? rCursor.GetMark() : rCursor.GetPoint(); + + + lcl_LOKInvalidateFrames(*(pStartPos->nNode.GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetSttPos()); + + lcl_LOKInvalidateFrames(*(pEndPos->nNode.GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetEndPos()); +} + +} // anonymous namespace + +/// Emits LOK notification about one addition / removal of a redline item. +void SwRedlineTable::LOKRedlineNotification(RedlineNotification nType, SwRangeRedline* pRedline) +{ + // Disable since usability is very low beyond some small number of changes. + static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr; + if (!comphelper::LibreOfficeKit::isActive() || bDisableRedlineComments) + return; + + boost::property_tree::ptree aRedline; + aRedline.put("action", (nType == RedlineNotification::Add ? "Add" : + (nType == RedlineNotification::Remove ? "Remove" : + (nType == RedlineNotification::Modify ? "Modify" : "???")))); + aRedline.put("index", pRedline->GetId()); + aRedline.put("author", pRedline->GetAuthorString(1).toUtf8().getStr()); + aRedline.put("type", SwRedlineTypeToOUString(pRedline->GetRedlineData().GetType()).toUtf8().getStr()); + aRedline.put("comment", pRedline->GetRedlineData().GetComment().toUtf8().getStr()); + aRedline.put("description", pRedline->GetDescr().toUtf8().getStr()); + OUString sDateTime = utl::toISO8601(pRedline->GetRedlineData().GetTimeStamp().GetUNODateTime()); + aRedline.put("dateTime", sDateTime.toUtf8().getStr()); + + SwPosition* pStartPos = pRedline->Start(); + SwPosition* pEndPos = pRedline->End(); + SwContentNode* pContentNd = pRedline->GetContentNode(); + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + if (pView && pContentNd) + { + SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos); + aCursor.SetMark(); + aCursor.GetMark()->nNode = pEndPos->nNode; + aCursor.GetMark()->nContent = pEndPos->nContent; + + aCursor.FillRects(); + + SwRects* pRects(&aCursor); + std::vector<OString> aRects; + for(const SwRect& rNextRect : *pRects) + aRects.push_back(rNextRect.SVRect().toString()); + + const OString sRects = comphelper::string::join("; ", aRects); + aRedline.put("textRange", sRects.getStr()); + + lcl_LOKInvalidateStartEndFrames(aCursor); + + // When this notify method is called text invalidation is not done yet + // Calling FillRects updates the text area so invalidation will not run on the correct rects + // So we need to do an own invalidation here. It invalidates text frames containing the redlining + SwDoc* pDoc = pRedline->GetDoc(); + SwViewShell* pSh; + if( pDoc && !pDoc->IsInDtor() ) + { + pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh ) + for(SwNodeIndex nIdx = pStartPos->nNode; nIdx <= pEndPos->nNode; ++nIdx) + { + SwContentNode* pContentNode = nIdx.GetNode().GetContentNode(); + if (pContentNode) + pSh->InvalidateWindows(pContentNode->FindLayoutRect()); + } + } + } + + boost::property_tree::ptree aTree; + aTree.add_child("redline", aRedline); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aPayload = aStream.str(); + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, aPayload.c_str()); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +bool SwRedlineTable::Insert(SwRangeRedline*& p) +{ + if( p->HasValidRange() ) + { + std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p ); + size_type nP = rv.first - begin(); + LOKRedlineNotification(RedlineNotification::Add, p); + p->CallDisplayFunc(nP); + return rv.second; + } + return InsertWithValidRanges( p ); +} + +bool SwRedlineTable::Insert(SwRangeRedline*& p, size_type& rP) +{ + if( p->HasValidRange() ) + { + std::pair<vector_type::const_iterator, bool> rv = maVector.insert( p ); + rP = rv.first - begin(); + p->CallDisplayFunc(rP); + return rv.second; + } + return InsertWithValidRanges( p, &rP ); +} + +namespace sw { + +std::vector<SwRangeRedline*> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p) +{ + std::vector<SwRangeRedline*> ret; + // Create valid "sub-ranges" from the Selection + SwPosition* pStt = p->Start(), + * pEnd = pStt == p->GetPoint() ? p->GetMark() : p->GetPoint(); + SwPosition aNewStt( *pStt ); + SwNodes& rNds = aNewStt.nNode.GetNodes(); + SwContentNode* pC; + + if( !aNewStt.nNode.GetNode().IsContentNode() ) + { + pC = rNds.GoNext( &aNewStt.nNode ); + if( pC ) + aNewStt.nContent.Assign( pC, 0 ); + else + aNewStt.nNode = rNds.GetEndOfContent(); + } + + SwRangeRedline* pNew = nullptr; + + if( aNewStt < *pEnd ) + do { + if( !pNew ) + pNew = new SwRangeRedline( p->GetRedlineData(), aNewStt ); + else + { + pNew->DeleteMark(); + *pNew->GetPoint() = aNewStt; + } + + pNew->SetMark(); + GoEndSection( pNew->GetPoint() ); + // i60396: If the redlines starts before a table but the table is the last member + // of the section, the GoEndSection will end inside the table. + // This will result in an incorrect redline, so we've to go back + SwNode* pTab = pNew->GetPoint()->nNode.GetNode().StartOfSectionNode()->FindTableNode(); + // We end in a table when pTab != 0 + if( pTab && !pNew->GetMark()->nNode.GetNode().StartOfSectionNode()->FindTableNode() ) + { // but our Mark was outside the table => Correction + do + { + // We want to be before the table + *pNew->GetPoint() = SwPosition(*pTab); + pC = GoPreviousNds( &pNew->GetPoint()->nNode, false ); // here we are. + if( pC ) + pNew->GetPoint()->nContent.Assign( pC, 0 ); + pTab = pNew->GetPoint()->nNode.GetNode().StartOfSectionNode()->FindTableNode(); + } while( pTab ); // If there is another table we have to repeat our step backwards + } + + if( *pNew->GetPoint() > *pEnd ) + { + pC = nullptr; + if( aNewStt.nNode != pEnd->nNode ) + do { + SwNode& rCurNd = aNewStt.nNode.GetNode(); + if( rCurNd.IsStartNode() ) + { + if( rCurNd.EndOfSectionIndex() < pEnd->nNode.GetIndex() ) + aNewStt.nNode = *rCurNd.EndOfSectionNode(); + else + break; + } + else if( rCurNd.IsContentNode() ) + pC = rCurNd.GetContentNode(); + ++aNewStt.nNode; + } while( aNewStt.nNode.GetIndex() < pEnd->nNode.GetIndex() ); + + if( aNewStt.nNode == pEnd->nNode ) + aNewStt.nContent = pEnd->nContent; + else if( pC ) + { + aNewStt.nNode = *pC; + aNewStt.nContent.Assign( pC, pC->Len() ); + } + + if( aNewStt <= *pEnd ) + *pNew->GetPoint() = aNewStt; + } + else + aNewStt = *pNew->GetPoint(); +#if OSL_DEBUG_LEVEL > 0 + CheckPosition( pNew->GetPoint(), pNew->GetMark() ); +#endif + + if( *pNew->GetPoint() != *pNew->GetMark() && + pNew->HasValidRange()) + { + ret.push_back(pNew); + pNew = nullptr; + } + + if( aNewStt >= *pEnd ) + break; + pC = rNds.GoNext( &aNewStt.nNode ); + if( !pC ) + break; + + aNewStt.nContent.Assign( pC, 0 ); + + } while( aNewStt < *pEnd ); + + delete pNew; + p.reset(); + return ret; +} + +} // namespace sw + +bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos) +{ + bool bAnyIns = false; + std::vector<SwRangeRedline*> const redlines( + GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p))); + for (SwRangeRedline * pRedline : redlines) + { + assert(pRedline->HasValidRange()); + size_type nInsPos; + if (Insert(pRedline, nInsPos)) + { + pRedline->CallDisplayFunc(nInsPos); + bAnyIns = true; + if (pInsPos && *pInsPos < nInsPos) + { + *pInsPos = nInsPos; + } + } + } + p = nullptr; + return bAnyIns; +} + +bool CompareSwRedlineTable::operator()(SwRangeRedline* const &lhs, SwRangeRedline* const &rhs) const +{ + return *lhs < *rhs; +} + +SwRedlineTable::~SwRedlineTable() +{ + maVector.DeleteAndDestroyAll(); +} + +SwRedlineTable::size_type SwRedlineTable::GetPos(const SwRangeRedline* p) const +{ + vector_type::const_iterator it = maVector.find(const_cast<SwRangeRedline*>(p)); + if( it == maVector.end() ) + return npos; + return it - maVector.begin(); +} + +void SwRedlineTable::Remove( const SwRangeRedline* p ) +{ + const size_type nPos = GetPos(p); + if (nPos == npos) + return; + Remove(nPos); +} + +void SwRedlineTable::Remove( size_type nP ) +{ + LOKRedlineNotification(RedlineNotification::Remove, maVector[nP]); + SwDoc* pDoc = nullptr; + if( !nP && 1 == size() ) + pDoc = maVector.front()->GetDoc(); + + maVector.erase( maVector.begin() + nP ); + + if( pDoc && !pDoc->IsInDtor() ) + { + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh ) + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + } +} + +void SwRedlineTable::DeleteAndDestroyAll() +{ + while (!maVector.empty()) + { + auto const pRedline = maVector.back(); + maVector.erase(maVector.size() - 1); + LOKRedlineNotification(RedlineNotification::Remove, pRedline); + delete pRedline; + } +} + +void SwRedlineTable::DeleteAndDestroy(size_type const nP) +{ + auto const pRedline = maVector[nP]; + maVector.erase(maVector.begin() + nP); + LOKRedlineNotification(RedlineNotification::Remove, pRedline); + delete pRedline; +} + +SwRedlineTable::size_type SwRedlineTable::FindNextOfSeqNo( size_type nSttPos ) const +{ + return nSttPos + 1 < size() + ? FindNextSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos+1 ) + : npos; +} + +SwRedlineTable::size_type SwRedlineTable::FindPrevOfSeqNo( size_type nSttPos ) const +{ + return nSttPos ? FindPrevSeqNo( operator[]( nSttPos )->GetSeqNo(), nSttPos-1 ) + : npos; +} + +/// Find the next or preceding Redline with the same seq.no. +/// We can limit the search using look ahead (0 searches the whole array). +SwRedlineTable::size_type SwRedlineTable::FindNextSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const +{ + auto const nLookahead = 20; + size_type nRet = npos; + if( nSeqNo && nSttPos < size() ) + { + size_type nEnd = size(); + const size_type nTmp = nSttPos + nLookahead; + if (nTmp < nEnd) + { + nEnd = nTmp; + } + + for( ; nSttPos < nEnd; ++nSttPos ) + if( nSeqNo == operator[]( nSttPos )->GetSeqNo() ) + { + nRet = nSttPos; + break; + } + } + return nRet; +} + +SwRedlineTable::size_type SwRedlineTable::FindPrevSeqNo( sal_uInt16 nSeqNo, size_type nSttPos ) const +{ + auto const nLookahead = 20; + size_type nRet = npos; + if( nSeqNo && nSttPos < size() ) + { + size_type nEnd = 0; + if( nSttPos > nLookahead ) + nEnd = nSttPos - nLookahead; + + ++nSttPos; + while( nSttPos > nEnd ) + if( nSeqNo == operator[]( --nSttPos )->GetSeqNo() ) + { + nRet = nSttPos; + break; + } + } + return nRet; +} + +const SwRangeRedline* SwRedlineTable::FindAtPosition( const SwPosition& rSttPos, + size_type& rPos, + bool bNext ) const +{ + const SwRangeRedline* pFnd = nullptr; + for( ; rPos < maVector.size() ; ++rPos ) + { + const SwRangeRedline* pTmp = (*this)[ rPos ]; + if( pTmp->HasMark() && pTmp->IsVisible() ) + { + const SwPosition* pRStt = pTmp->Start(), + * pREnd = pRStt == pTmp->GetPoint() ? pTmp->GetMark() + : pTmp->GetPoint(); + if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos ) + { + if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos ) + { + pFnd = pTmp; + break; + } + } + else + break; + } + } + return pFnd; +} + +void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos) + operator[](nCurRedlinePos)->dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +SwRedlineExtraData::~SwRedlineExtraData() +{ +} + +void SwRedlineExtraData::Reject( SwPaM& ) const +{ +} + +bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const +{ + return false; +} + +SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( const OUString& rColl, + sal_uInt16 nPoolFormatId, + const SfxItemSet* pItemSet, + bool bFormatAll ) + : m_sFormatNm(rColl), m_nPoolId(nPoolFormatId), m_bFormatAll(bFormatAll) +{ + if( pItemSet && pItemSet->Count() ) + m_pSet.reset( new SfxItemSet( *pItemSet ) ); +} + +SwRedlineExtraData_FormatColl::~SwRedlineExtraData_FormatColl() +{ +} + +SwRedlineExtraData* SwRedlineExtraData_FormatColl::CreateNew() const +{ + return new SwRedlineExtraData_FormatColl( m_sFormatNm, m_nPoolId, m_pSet.get(), m_bFormatAll ); +} + +void SwRedlineExtraData_FormatColl::Reject( SwPaM& rPam ) const +{ + SwDoc* pDoc = rPam.GetDoc(); + + // What about Undo? Is it turned off? + SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId + ? pDoc->FindTextFormatCollByName( m_sFormatNm ) + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId ); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + + const SwPosition* pStt = rPam.Start(), + * pEnd = pStt == rPam.GetPoint() ? rPam.GetMark() + : rPam.GetPoint(); + + if ( !m_bFormatAll || pEnd->nContent == 0 ) + { + // don't reject the format of the next paragraph (that is handled by the next redline) + if (aPam.GetPoint()->nNode > aPam.GetMark()->nNode) + { + aPam.GetPoint()->nNode--; + SwContentNode* pNode = aPam.GetPoint()->nNode.GetNode().GetContentNode(); + aPam.GetPoint()->nContent.Assign( pNode, pNode->Len() ); + } + else if (aPam.GetPoint()->nNode < aPam.GetMark()->nNode) + { + aPam.GetMark()->nNode--; + SwContentNode* pNode = aPam.GetMark()->nNode.GetNode().GetContentNode(); + aPam.GetMark()->nContent.Assign( pNode, pNode->Len() ); + } + } + + if( pColl ) + pDoc->SetTextFormatColl( aPam, pColl, false ); + + if( m_pSet ) + pDoc->getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet ); + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwRedlineExtraData_FormatColl::operator == ( const SwRedlineExtraData& r) const +{ + const SwRedlineExtraData_FormatColl& rCmp = static_cast<const SwRedlineExtraData_FormatColl&>(r); + return m_sFormatNm == rCmp.m_sFormatNm && m_nPoolId == rCmp.m_nPoolId && + m_bFormatAll == rCmp.m_bFormatAll && + ( ( !m_pSet && !rCmp.m_pSet ) || + ( m_pSet && rCmp.m_pSet && *m_pSet == *rCmp.m_pSet ) ); +} + +void SwRedlineExtraData_FormatColl::SetItemSet( const SfxItemSet& rSet ) +{ + if( rSet.Count() ) + m_pSet.reset( new SfxItemSet( rSet ) ); + else + m_pSet.reset(); +} + +SwRedlineExtraData_Format::SwRedlineExtraData_Format( const SfxItemSet& rSet ) +{ + SfxItemIter aIter( rSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + m_aWhichIds.push_back( pItem->Which() ); + } +} + +SwRedlineExtraData_Format::SwRedlineExtraData_Format( + const SwRedlineExtraData_Format& rCpy ) + : SwRedlineExtraData() +{ + m_aWhichIds.insert( m_aWhichIds.begin(), rCpy.m_aWhichIds.begin(), rCpy.m_aWhichIds.end() ); +} + +SwRedlineExtraData_Format::~SwRedlineExtraData_Format() +{ +} + +SwRedlineExtraData* SwRedlineExtraData_Format::CreateNew() const +{ + return new SwRedlineExtraData_Format( *this ); +} + +void SwRedlineExtraData_Format::Reject( SwPaM& rPam ) const +{ + SwDoc* pDoc = rPam.GetDoc(); + + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + // Actually we need to reset the Attribute here! + for( const auto& rWhichId : m_aWhichIds ) + { + pDoc->getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ), + SetAttrMode::DONTEXPAND ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwRedlineExtraData_Format::operator == ( const SwRedlineExtraData& rCmp ) const +{ + const size_t nEnd = m_aWhichIds.size(); + if( nEnd != static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds.size() ) + return false; + + for( size_t n = 0; n < nEnd; ++n ) + { + if( static_cast<const SwRedlineExtraData_Format&>(rCmp).m_aWhichIds[n] != m_aWhichIds[n]) + { + return false; + } + } + return true; +} + +SwRedlineData::SwRedlineData( RedlineType eT, std::size_t nAut ) + : m_pNext( nullptr ), m_pExtraData( nullptr ), + m_aStamp( DateTime::SYSTEM ), + m_eType( eT ), m_bAutoFormat(false), m_nAuthor( nAut ), m_nSeqNo( 0 ) +{ + m_aStamp.SetNanoSec( 0 ); +} + +SwRedlineData::SwRedlineData( + const SwRedlineData& rCpy, + bool bCpyNext ) + : m_pNext( ( bCpyNext && rCpy.m_pNext ) ? new SwRedlineData( *rCpy.m_pNext ) : nullptr ) + , m_pExtraData( rCpy.m_pExtraData ? rCpy.m_pExtraData->CreateNew() : nullptr ) + , m_sComment( rCpy.m_sComment ) + , m_aStamp( rCpy.m_aStamp ) + , m_eType( rCpy.m_eType ) + , m_bAutoFormat(false) + , m_nAuthor( rCpy.m_nAuthor ) + , m_nSeqNo( rCpy.m_nSeqNo ) +{ +} + +// For sw3io: We now own pNext! +SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT, + const OUString& rCmnt, SwRedlineData *pNxt) + : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(rCmnt), m_aStamp(rDT), + m_eType(eT), m_bAutoFormat(false), m_nAuthor(nAut), m_nSeqNo(0) +{ +} + +SwRedlineData::~SwRedlineData() +{ + delete m_pExtraData; + delete m_pNext; +} + +bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const +{ + DateTime aTime = GetTimeStamp(); + aTime.SetSec(0); + DateTime aCompareTime = rCmp.GetTimeStamp(); + aCompareTime.SetSec(0); + return m_nAuthor == rCmp.m_nAuthor && + m_eType == rCmp.m_eType && + m_sComment == rCmp.m_sComment && + aTime == aCompareTime && + (( !m_pNext && !rCmp.m_pNext ) || + ( m_pNext && rCmp.m_pNext && + m_pNext->CanCombine( *rCmp.m_pNext ))) && + (( !m_pExtraData && !rCmp.m_pExtraData ) || + ( m_pExtraData && rCmp.m_pExtraData && + *m_pExtraData == *rCmp.m_pExtraData )); +} + +/// ExtraData is copied. The Pointer's ownership is thus NOT transferred +/// to the Redline Object! +void SwRedlineData::SetExtraData( const SwRedlineExtraData* pData ) +{ + delete m_pExtraData; + + // Check if there is data - and if so - delete it + if( pData ) + m_pExtraData = pData->CreateNew(); + else + m_pExtraData = nullptr; +} + +static const char* STR_REDLINE_ARY[] = +{ + STR_UNDO_REDLINE_INSERT, + STR_UNDO_REDLINE_DELETE, + STR_UNDO_REDLINE_FORMAT, + STR_UNDO_REDLINE_TABLE, + STR_UNDO_REDLINE_FMTCOLL, + STR_UNDO_REDLINE_PARAGRAPH_FORMAT, + STR_UNDO_REDLINE_TABLE_ROW_INSERT, + STR_UNDO_REDLINE_TABLE_ROW_DELETE, + STR_UNDO_REDLINE_TABLE_CELL_INSERT, + STR_UNDO_REDLINE_TABLE_CELL_DELETE +}; + +OUString SwRedlineData::GetDescr() const +{ + return SwResId(STR_REDLINE_ARY[static_cast<int>(GetType())]); +} + +sal_uInt32 SwRangeRedline::m_nLastId = 1; + +SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), + m_pRedlineData( new SwRedlineData( eTyp, GetDoc()->getIDocumentRedlineAccess().GetRedlineAuthor() ) ), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rPam.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), + m_pRedlineData( new SwRedlineData( rData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rPam.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPosition& rPos ) + : SwPaM( rPos ), + m_pRedlineData( new SwRedlineData( rData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; +} + +SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy ) + : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ), + m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )), + m_pContentSect( nullptr ), + m_nId( m_nLastId++ ) +{ + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rCpy.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::~SwRangeRedline() +{ + if( m_pContentSect ) + { + // delete the ContentSection + if( !GetDoc()->IsInDtor() ) + GetDoc()->getIDocumentContentOperations().DeleteSection( &m_pContentSect->GetNode() ); + delete m_pContentSect; + } + delete m_pRedlineData; +} + +void MaybeNotifyRedlineModification(SwRangeRedline* pRedline, SwDoc* pDoc) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + const SwRedlineTable& rRedTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i) + { + if (rRedTable[i] == pRedline) + { + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedline); + break; + } + } +} + +void SwRangeRedline::MaybeNotifyRedlinePositionModification(long nTop) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if(!m_oLOKLastNodeTop || *m_oLOKLastNodeTop != nTop) + { + m_oLOKLastNodeTop = nTop; + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, this); + } +} + +void SwRangeRedline::SetStart( const SwPosition& rPos, SwPosition* pSttPtr ) +{ + if( !pSttPtr ) pSttPtr = Start(); + *pSttPtr = rPos; + + MaybeNotifyRedlineModification(this, GetDoc()); +} + +void SwRangeRedline::SetEnd( const SwPosition& rPos, SwPosition* pEndPtr ) +{ + if( !pEndPtr ) pEndPtr = End(); + *pEndPtr = rPos; + + MaybeNotifyRedlineModification(this, GetDoc()); +} + +/// Do we have a valid Selection? +bool SwRangeRedline::HasValidRange() const +{ + const SwNode* pPtNd = &GetPoint()->nNode.GetNode(), + * pMkNd = &GetMark()->nNode.GetNode(); + if( pPtNd->StartOfSectionNode() == pMkNd->StartOfSectionNode() && + !pPtNd->StartOfSectionNode()->IsTableNode() && + // invalid if points on the end of content + // end-of-content only invalid if no content index exists + ( pPtNd != pMkNd || GetContentIdx() != nullptr || + pPtNd != &pPtNd->GetNodes().GetEndOfContent() ) + ) + return true; + return false; +} + +void SwRangeRedline::CallDisplayFunc(size_t nMyPos) +{ + RedlineFlags eShow = RedlineFlags::ShowMask & GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + if (eShow == (RedlineFlags::ShowInsert | RedlineFlags::ShowDelete)) + Show(0, nMyPos); + else if (eShow == RedlineFlags::ShowInsert) + Hide(0, nMyPos); + else if (eShow == RedlineFlags::ShowDelete) + ShowOriginal(0, nMyPos); +} + +void SwRangeRedline::Show(sal_uInt16 nLoop, size_t nMyPos) +{ + if( 1 <= nLoop ) + { + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + switch( GetType() ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = true; + MoveFromSection(nMyPos); + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = true; + MoveFromSection(nMyPos); + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + InvalidateRange(Invalidation::Add); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } +} + +void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos) +{ + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + switch( GetType() ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = true; + if( 1 <= nLoop ) + MoveFromSection(nMyPos); + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = false; + switch( nLoop ) + { + case 0: MoveToSection(); break; + case 1: CopyToSection(); break; + case 2: DelCopyOfSection(nMyPos); break; + } + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + if( 1 <= nLoop ) + InvalidateRange(Invalidation::Remove); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos) +{ + SwDoc* pDoc = GetDoc(); + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + SwRedlineData* pCur; + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + // Determine the Type, it's the first on Stack + for( pCur = m_pRedlineData; pCur->m_pNext; ) + pCur = pCur->m_pNext; + + switch( pCur->m_eType ) + { + case RedlineType::Insert: // Content has been inserted + m_bIsVisible = false; + switch( nLoop ) + { + case 0: MoveToSection(); break; + case 1: CopyToSection(); break; + case 2: DelCopyOfSection(nMyPos); break; + } + break; + + case RedlineType::Delete: // Content has been deleted + m_bIsVisible = true; + if( 1 <= nLoop ) + MoveFromSection(nMyPos); + break; + + case RedlineType::Format: // Attributes have been applied + case RedlineType::Table: // Table structure has been modified + if( 1 <= nLoop ) + InvalidateRange(Invalidation::Remove); + break; + default: + break; + } + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +// trigger the Layout +void SwRangeRedline::InvalidateRange(Invalidation const eWhy) +{ + sal_uLong nSttNd = GetMark()->nNode.GetIndex(), + nEndNd = GetPoint()->nNode.GetIndex(); + sal_Int32 nSttCnt = GetMark()->nContent.GetIndex(); + sal_Int32 nEndCnt = GetPoint()->nContent.GetIndex(); + + if( nSttNd > nEndNd || ( nSttNd == nEndNd && nSttCnt > nEndCnt )) + { + sal_uLong nTmp = nSttNd; nSttNd = nEndNd; nEndNd = nTmp; + sal_Int32 nTmp2 = nSttCnt; nSttCnt = nEndCnt; nEndCnt = nTmp2; + } + + SwNodes& rNds = GetDoc()->GetNodes(); + for (sal_uLong n(nSttNd); n <= nEndNd; ++n) + { + SwNode* pNode = rNds[n]; + + if (pNode && pNode->IsTextNode()) + { + SwTextNode* pNd = pNode->GetTextNode(); + + SwUpdateAttr aHt( + n == nSttNd ? nSttCnt : 0, + n == nEndNd ? nEndCnt : pNd->GetText().getLength(), + RES_FMT_CHG); + + pNd->ModifyNotification(&aHt, &aHt); + + // SwUpdateAttr must be handled first, otherwise indexes are off + if (GetType() == RedlineType::Delete) + { + sal_Int32 const nStart(n == nSttNd ? nSttCnt : 0); + sal_Int32 const nLen((n == nEndNd ? nEndCnt : pNd->GetText().getLength()) - nStart); + if (eWhy == Invalidation::Add) + { + sw::RedlineDelText const hint(nStart, nLen); + pNd->CallSwClientNotify(hint); + } + else + { + sw::RedlineUnDelText const hint(nStart, nLen); + pNd->CallSwClientNotify(hint); + } + } + } + } +} + +/** Calculates the start and end position of the intersection rTmp and + text node nNdIdx */ +void SwRangeRedline::CalcStartEnd( sal_uLong nNdIdx, sal_Int32& rStart, sal_Int32& rEnd ) const +{ + const SwPosition *pRStt = Start(), *pREnd = End(); + if( pRStt->nNode < nNdIdx ) + { + if( pREnd->nNode > nNdIdx ) + { + rStart = 0; // Paragraph is completely enclosed + rEnd = COMPLETE_STRING; + } + else if (pREnd->nNode == nNdIdx) + { + rStart = 0; // Paragraph is overlapped in the beginning + rEnd = pREnd->nContent.GetIndex(); + } + else // redline ends before paragraph + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } + } + else if( pRStt->nNode == nNdIdx ) + { + rStart = pRStt->nContent.GetIndex(); + if( pREnd->nNode == nNdIdx ) + rEnd = pREnd->nContent.GetIndex(); // Within the Paragraph + else + rEnd = COMPLETE_STRING; // Paragraph is overlapped in the end + } + else + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } +} + +void SwRangeRedline::MoveToSection() +{ + if( !m_pContentSect ) + { + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwDoc* pDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->GetBound() == *pStt ) + pRedl->GetBound() = *pEnd; + if( pRedl->GetBound(false) == *pStt ) + pRedl->GetBound(false) = *pEnd; + } + } + + SwStartNode* pSttNd; + SwNodes& rNds = pDoc->GetNodes(); + if( pCSttNd || pCEndNd ) + { + SwTextFormatColl* pColl = (pCSttNd && pCSttNd->IsTextNode() ) + ? pCSttNd->GetTextNode()->GetTextColl() + : (pCEndNd && pCEndNd->IsTextNode() ) + ? pCEndNd->GetTextNode()->GetTextColl() + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( SwNodeIndex( rNds.GetEndOfRedlines() ), + SwNormalStartNode, pColl ); + SwTextNode* pTextNd = rNds[ pSttNd->GetIndex() + 1 ]->GetTextNode(); + + SwNodeIndex aNdIdx( *pTextNd ); + SwPosition aPos( aNdIdx, SwIndex( pTextNd )); + if( pCSttNd && pCEndNd ) + pDoc->getIDocumentContentOperations().MoveAndJoin( aPam, aPos ); + else + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( SwNodeIndex( rNds.GetEndOfRedlines() ) ); + + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + m_pContentSect = new SwNodeIndex( *pSttNd ); + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); + } + else + InvalidateRange(Invalidation::Remove); +} + +void SwRangeRedline::CopyToSection() +{ + if( m_pContentSect ) + return; + + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + SwStartNode* pSttNd; + SwDoc* pDoc = GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + bool bSaveCopyFlag = pDoc->IsCopyIsMove(), + bSaveRdlMoveFlg = pDoc->getIDocumentRedlineAccess().IsRedlineMove(); + pDoc->SetCopyIsMove( true ); + + // The IsRedlineMove() flag causes the behaviour of the + // DocumentContentOperationsManager::CopyFlyInFlyImpl() method to change, + // which will eventually be called by the CopyRange() below. + pDoc->getIDocumentRedlineAccess().SetRedlineMove(true); + + if( pCSttNd ) + { + SwTextFormatColl* pColl = pCSttNd->IsTextNode() + ? pCSttNd->GetTextNode()->GetTextColl() + : pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( SwNodeIndex( rNds.GetEndOfRedlines() ), + SwNormalStartNode, pColl ); + + SwNodeIndex aNdIdx( *pSttNd, 1 ); + SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode(); + SwPosition aPos( aNdIdx, SwIndex( pTextNd )); + pDoc->getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); + + // Take over the style from the EndNode if needed + // We don't want this in Doc::Copy + if( pCEndNd && pCEndNd != pCSttNd ) + { + SwContentNode* pDestNd = aPos.nNode.GetNode().GetContentNode(); + if( pDestNd ) + { + if( pDestNd->IsTextNode() && pCEndNd->IsTextNode() ) + pCEndNd->GetTextNode()->CopyCollFormat(*pDestNd->GetTextNode()); + else + pDestNd->ChgFormatColl( pCEndNd->GetFormatColl() ); + } + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( SwNodeIndex( rNds.GetEndOfRedlines() ) ); + + if( pCEndNd ) + { + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); + } + else + { + SwNodeIndex aInsPos( *pSttNd->EndOfSectionNode() ); + SwNodeRange aRg( pStt->nNode, 0, pEnd->nNode, 1 ); + pDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos); + } + } + m_pContentSect = new SwNodeIndex( *pSttNd ); + + pDoc->SetCopyIsMove( bSaveCopyFlag ); + pDoc->getIDocumentRedlineAccess().SetRedlineMove( bSaveRdlMoveFlg ); + +} + +void SwRangeRedline::DelCopyOfSection(size_t nMyPos) +{ + if( m_pContentSect ) + { + const SwPosition* pStt = Start(), + * pEnd = pStt == GetPoint() ? GetMark() : GetPoint(); + + SwDoc* pDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->nNode.GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->nNode.GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + if( pRedl->GetBound() == *pStt ) + pRedl->GetBound() = *pEnd; + if( pRedl->GetBound(false) == *pStt ) + pRedl->GetBound(false) = *pEnd; + } + } + + if( pCSttNd && pCEndNd ) + { + // #i100466# - force a <join next> on <delete and join> operation + // tdf#125319 - rather not? + pDoc->getIDocumentContentOperations().DeleteAndJoin(aPam/*, true*/); + } + else if( pCSttNd || pCEndNd ) + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + pDoc->getIDocumentContentOperations().DeleteRange( aPam ); + + if( m_bDelLastPara ) + { + // To prevent dangling references to the paragraph to + // be deleted, redline that point into this paragraph should be + // moved to the new end position. Since redlines in the redline + // table are sorted and the pEnd position is an endnode (see + // bDelLastPara condition above), only redlines before the + // current ones can be affected. + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + size_t n = nMyPos; + for( bool bBreak = false; !bBreak && n > 0; ) + { + --n; + bBreak = true; + if( rTable[ n ]->GetBound() == *aPam.GetPoint() ) + { + rTable[ n ]->GetBound() = *pEnd; + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *aPam.GetPoint() ) + { + rTable[ n ]->GetBound(false) = *pEnd; + bBreak = false; + } + } + + *GetPoint() = *pEnd; + *GetMark() = *pEnd; + DeleteMark(); + + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + aPam.DeleteMark(); + pDoc->getIDocumentContentOperations().DelFullPara( aPam ); + } + } + else + { + pDoc->getIDocumentContentOperations().DeleteRange( aPam ); + } + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); + } +} + +void SwRangeRedline::MoveFromSection(size_t nMyPos) +{ + if( m_pContentSect ) + { + SwDoc* pDoc = GetDoc(); + const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable(); + std::vector<SwPosition*> aBeforeArr, aBehindArr; + bool bBreak = false; + SwRedlineTable::size_type n; + + for( n = nMyPos+1; !bBreak && n < rTable.size(); ++n ) + { + bBreak = true; + if( rTable[ n ]->GetBound() == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBehindArr.push_back(&pRedl->GetBound()); + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBehindArr.push_back(&pRedl->GetBound(false)); + bBreak = false; + } + } + for( bBreak = false, n = nMyPos; !bBreak && n ; ) + { + --n; + bBreak = true; + if( rTable[ n ]->GetBound() == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBeforeArr.push_back(&pRedl->GetBound()); + bBreak = false; + } + if( rTable[ n ]->GetBound(false) == *GetPoint() ) + { + SwRangeRedline* pRedl = rTable[n]; + aBeforeArr.push_back(&pRedl->GetBound(false)); + bBreak = false; + } + } + + const SwNode* pKeptContentSectNode( &m_pContentSect->GetNode() ); // #i95711# + { + SwPaM aPam( m_pContentSect->GetNode(), + *m_pContentSect->GetNode().EndOfSectionNode(), 1, + ( m_bDelLastPara ? -2 : -1 ) ); + SwContentNode* pCNd = aPam.GetContentNode(); + if( pCNd ) + aPam.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + else + ++aPam.GetPoint()->nNode; + + SwFormatColl* pColl = pCNd && pCNd->Len() && aPam.GetPoint()->nNode != + aPam.GetMark()->nNode + ? pCNd->GetFormatColl() : nullptr; + + SwNodeIndex aNdIdx( GetPoint()->nNode, -1 ); + const sal_Int32 nPos = GetPoint()->nContent.GetIndex(); + + SwPosition aPos( *GetPoint() ); + if( m_bDelLastPara && *aPam.GetPoint() == *aPam.GetMark() ) + { + --aPos.nNode; + + pDoc->getIDocumentContentOperations().AppendTextNode( aPos ); + } + else + { + pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::ALLFLYS ); + } + + SetMark(); + *GetPoint() = aPos; + GetMark()->nNode = aNdIdx.GetIndex() + 1; + pCNd = GetMark()->nNode.GetNode().GetContentNode(); + GetMark()->nContent.Assign( pCNd, nPos ); + + if( m_bDelLastPara ) + { + ++GetPoint()->nNode; + pCNd = GetContentNode(); + GetPoint()->nContent.Assign( pCNd, 0 ); + m_bDelLastPara = false; + } + else if( pColl ) + pCNd = GetContentNode(); + + if( pColl && pCNd ) + pCNd->ChgFormatColl( pColl ); + } + + // #i95771# + // Under certain conditions the previous <SwDoc::Move(..)> has already + // removed the change tracking section of this <SwRangeRedline> instance from + // the change tracking nodes area. + // Thus, check if <pContentSect> still points to the change tracking section + // by comparing it with the "indexed" <SwNode> instance copied before + // perform the intrinsic move. + // Note: Such condition is e.g. a "delete" change tracking only containing a table. + if ( &m_pContentSect->GetNode() == pKeptContentSectNode ) + { + pDoc->getIDocumentContentOperations().DeleteSection( &m_pContentSect->GetNode() ); + } + delete m_pContentSect; + m_pContentSect = nullptr; + + // adjustment of redline table positions must take start and + // end into account, not point and mark. + for( auto& pItem : aBeforeArr ) + *pItem = *Start(); + for( auto& pItem : aBehindArr ) + *pItem = *End(); + } + else + InvalidateRange(Invalidation::Add); +} + +// for Undo +void SwRangeRedline::SetContentIdx( const SwNodeIndex* pIdx ) +{ + if( pIdx && !m_pContentSect ) + { + m_pContentSect = new SwNodeIndex( *pIdx ); + m_bIsVisible = false; + } + else if( !pIdx && m_pContentSect ) + { + delete m_pContentSect; + m_pContentSect = nullptr; + m_bIsVisible = false; + } + else + { + OSL_FAIL("SwRangeRedline::SetContentIdx: invalid state"); + } +} + +bool SwRangeRedline::CanCombine( const SwRangeRedline& rRedl ) const +{ + return IsVisible() && rRedl.IsVisible() && + m_pRedlineData->CanCombine( *rRedl.m_pRedlineData ); +} + +void SwRangeRedline::PushData( const SwRangeRedline& rRedl, bool bOwnAsNext ) +{ + SwRedlineData* pNew = new SwRedlineData( *rRedl.m_pRedlineData, false ); + if( bOwnAsNext ) + { + pNew->m_pNext = m_pRedlineData; + m_pRedlineData = pNew; + } + else + { + pNew->m_pNext = m_pRedlineData->m_pNext; + m_pRedlineData->m_pNext = pNew; + } +} + +bool SwRangeRedline::PopData() +{ + if( !m_pRedlineData->m_pNext ) + return false; + SwRedlineData* pCur = m_pRedlineData; + m_pRedlineData = pCur->m_pNext; + pCur->m_pNext = nullptr; + delete pCur; + return true; +} + +sal_uInt16 SwRangeRedline::GetStackCount() const +{ + sal_uInt16 nRet = 1; + for( SwRedlineData* pCur = m_pRedlineData; pCur->m_pNext; pCur = pCur->m_pNext ) + ++nRet; + return nRet; +} + +std::size_t SwRangeRedline::GetAuthor( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_nAuthor; +} + +OUString const & SwRangeRedline::GetAuthorString( sal_uInt16 nPos ) const +{ + return SW_MOD()->GetRedlineAuthor(GetRedlineData(nPos).m_nAuthor); +} + +const DateTime& SwRangeRedline::GetTimeStamp( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_aStamp; +} + +RedlineType SwRangeRedline::GetType( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_eType; +} + +const OUString& SwRangeRedline::GetComment( sal_uInt16 nPos ) const +{ + return GetRedlineData(nPos).m_sComment; +} + +bool SwRangeRedline::operator<( const SwRangeRedline& rCmp ) const +{ + if (*Start() < *rCmp.Start()) + return true; + + return *Start() == *rCmp.Start() && *End() < *rCmp.End(); +} + +const SwRedlineData & SwRangeRedline::GetRedlineData(const sal_uInt16 nPos) const +{ + SwRedlineData * pCur = m_pRedlineData; + + sal_uInt16 nP = nPos; + + while (nP > 0 && nullptr != pCur->m_pNext) + { + pCur = pCur->m_pNext; + + nP--; + } + + SAL_WARN_IF( nP != 0, "sw.core", "Pos " << nPos << " is " << nP << " too big"); + + return *pCur; +} + +OUString SwRangeRedline::GetDescr() +{ + // get description of redline data (e.g.: "insert $1") + OUString aResult = GetRedlineData().GetDescr(); + + SwPaM * pPaM = nullptr; + bool bDeletePaM = false; + + // if this redline is visible the content is in this PaM + if (nullptr == m_pContentSect) + { + pPaM = this; + } + else // otherwise it is saved in pContentSect + { + SwNodeIndex aTmpIdx( *m_pContentSect->GetNode().EndOfSectionNode() ); + pPaM = new SwPaM(*m_pContentSect, aTmpIdx ); + bDeletePaM = true; + } + + OUString sDescr = DenoteSpecialCharacters(pPaM->GetText()); + if (const SwTextNode *pTextNode = pPaM->GetNode().GetTextNode()) + { + if (const SwTextAttr* pTextAttr = pTextNode->GetFieldTextAttrAt(pPaM->GetPoint()->nContent.GetIndex() - 1, true )) + { + sDescr = SwResId(STR_START_QUOTE) + + pTextAttr->GetFormatField().GetField()->GetFieldName() + + SwResId(STR_END_QUOTE); + } + } + + // replace $1 in description by description of the redlines text + const OUString aTmpStr = ShortenString(sDescr, nUndoStringLength, SwResId(STR_LDOTS)); + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + aResult = aRewriter.Apply(aResult); + + if (bDeletePaM) + delete pPaM; + + return aResult; +} + +void SwRangeRedline::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwRangeRedline")); + + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SW_MOD()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(const_cast<SwRangeRedline*>(this)->GetDescr().toUtf8().getStr())); + + OString sRedlineType; + switch (GetType()) + { + case RedlineType::Insert: + sRedlineType = "REDLINE_INSERT"; + break; + case RedlineType::Delete: + sRedlineType = "REDLINE_DELETE"; + break; + case RedlineType::Format: + sRedlineType = "REDLINE_FORMAT"; + break; + default: + sRedlineType = "UNKNOWN"; + break; + } + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr())); + + SwPaM::dumpAsXml(pWriter); + + xmlTextWriterEndElement(pWriter); +} + +void SwExtraRedlineTable::Insert( SwExtraRedline* p ) +{ + m_aExtraRedlines.push_back( p ); + //p->CallDisplayFunc(); +} + +void SwExtraRedlineTable::DeleteAndDestroy(sal_uInt16 const nPos) +{ + /* + SwDoc* pDoc = 0; + if( !nP && nL && nL == size() ) + pDoc = front()->GetDoc(); + */ + + delete m_aExtraRedlines[nPos]; + m_aExtraRedlines.erase(m_aExtraRedlines.begin() + nPos); + + /* + SwViewShell* pSh; + if( pDoc && !pDoc->IsInDtor() && + 0 != ( pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) ) + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + */ +} + +void SwExtraRedlineTable::DeleteAndDestroyAll() +{ + while (!m_aExtraRedlines.empty()) + { + auto const pRedline = m_aExtraRedlines.back(); + m_aExtraRedlines.pop_back(); + delete pRedline; + } +} + +SwExtraRedline::~SwExtraRedline() +{ +} + +SwTableRowRedline::SwTableRowRedline(const SwRedlineData& rData, const SwTableLine& rTableLine) + : m_aRedlineData(rData) + , m_rTableLine(rTableLine) +{ +} + +SwTableRowRedline::~SwTableRowRedline() +{ +} + +SwTableCellRedline::SwTableCellRedline(const SwRedlineData& rData, const SwTableBox& rTableBox) + : m_aRedlineData(rData) + , m_rTableBox(rTableBox) +{ +} + +SwTableCellRedline::~SwTableCellRedline() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docruby.cxx b/sw/source/core/doc/docruby.cxx new file mode 100644 index 000000000..f1ce56a0d --- /dev/null +++ b/sw/source/core/doc/docruby.cxx @@ -0,0 +1,327 @@ +/* -*- 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 <memory> +#include <string.h> + +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + +#include <unotools/charclass.hxx> + +#include <hintids.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <ndtxt.hxx> +#include <txatbase.hxx> +#include <rubylist.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <breakit.hxx> +#include <swcrsr.hxx> + +using namespace ::com::sun::star::i18n; + +/* + * Members in the list: + * - String - the orig text + * - SwFormatRuby - the ruby attribute + */ +sal_uInt16 SwDoc::FillRubyList( const SwPaM& rPam, SwRubyList& rList ) +{ + const SwPaM *_pStartCursor = rPam.GetNext(), + *_pStartCursor2 = _pStartCursor; + bool bCheckEmpty = &rPam != _pStartCursor; + do { + const SwPosition* pStt = _pStartCursor->Start(), + * pEnd = pStt == _pStartCursor->GetPoint() + ? _pStartCursor->GetMark() + : _pStartCursor->GetPoint(); + if( !bCheckEmpty || ( pStt != pEnd && *pStt != *pEnd )) + { + SwPaM aPam( *pStt ); + do { + std::unique_ptr<SwRubyListEntry> pNew(new SwRubyListEntry); + if( pEnd != pStt ) + { + aPam.SetMark(); + *aPam.GetMark() = *pEnd; + } + if( SelectNextRubyChars( aPam, *pNew )) + { + rList.push_back(std::move(pNew)); + aPam.DeleteMark(); + } + else + { + if( *aPam.GetPoint() < *pEnd ) + { + // goto next paragraph + aPam.DeleteMark(); + aPam.Move( fnMoveForward, GoInNode ); + } + else + break; + } + } while( 30 > rList.size() && *aPam.GetPoint() < *pEnd ); + } + if( 30 <= rList.size() ) + break; + _pStartCursor = _pStartCursor->GetNext(); + } while( _pStartCursor != _pStartCursor2 ); + + return rList.size(); +} + +void SwDoc::SetRubyList( const SwPaM& rPam, const SwRubyList& rList ) +{ + GetIDocumentUndoRedo().StartUndo( SwUndoId::SETRUBYATTR, nullptr ); + std::set<sal_uInt16> aDelArr; + aDelArr.insert( RES_TXTATR_CJK_RUBY ); + + SwRubyList::size_type nListEntry = 0; + + const SwPaM *_pStartCursor = rPam.GetNext(), + *_pStartCursor2 = _pStartCursor; + bool bCheckEmpty = &rPam != _pStartCursor; + do { + const SwPosition* pStt = _pStartCursor->Start(), + * pEnd = pStt == _pStartCursor->GetPoint() + ? _pStartCursor->GetMark() + : _pStartCursor->GetPoint(); + if( !bCheckEmpty || ( pStt != pEnd && *pStt != *pEnd )) + { + + SwPaM aPam( *pStt ); + do { + SwRubyListEntry aCheckEntry; + if( pEnd != pStt ) + { + aPam.SetMark(); + *aPam.GetMark() = *pEnd; + } + if( SelectNextRubyChars( aPam, aCheckEntry )) + { + const SwRubyListEntry* pEntry = rList[ nListEntry++ ].get(); + if( aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr() ) + { + // set/reset the attribute + if( !pEntry->GetRubyAttr().GetText().isEmpty() ) + { + getIDocumentContentOperations().InsertPoolItem( aPam, pEntry->GetRubyAttr() ); + } + else + { + ResetAttrs( aPam, true, aDelArr ); + } + } + + if( !pEntry->GetText().isEmpty() && + aCheckEntry.GetText() != pEntry->GetText() ) + { + // text is changed, so replace the original + getIDocumentContentOperations().ReplaceRange( aPam, pEntry->GetText(), false ); + } + aPam.DeleteMark(); + } + else + { + if( *aPam.GetPoint() < *pEnd ) + { + // goto next paragraph + aPam.DeleteMark(); + aPam.Move( fnMoveForward, GoInNode ); + } + else + { + const SwRubyListEntry* pEntry = rList[ nListEntry++ ].get(); + + // set/reset the attribute + if( !pEntry->GetRubyAttr().GetText().isEmpty() && + !pEntry->GetText().isEmpty() ) + { + getIDocumentContentOperations().InsertString( aPam, pEntry->GetText() ); + aPam.SetMark(); + aPam.GetMark()->nContent -= pEntry->GetText().getLength(); + getIDocumentContentOperations().InsertPoolItem( + aPam, pEntry->GetRubyAttr(), SetAttrMode::DONTEXPAND ); + } + else + break; + aPam.DeleteMark(); + } + } + } while( nListEntry < rList.size() && *aPam.GetPoint() < *pEnd ); + } + if( 30 <= rList.size() ) + break; + _pStartCursor = _pStartCursor->GetNext(); + } while( _pStartCursor != _pStartCursor2 ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::SETRUBYATTR, nullptr ); +} + +bool SwDoc::SelectNextRubyChars( SwPaM& rPam, SwRubyListEntry& rEntry ) +{ + // Point must be the startposition, Mark is optional the end position + SwPosition* pPos = rPam.GetPoint(); + const SwTextNode* pTNd = pPos->nNode.GetNode().GetTextNode(); + OUString const& rText = pTNd->GetText(); + sal_Int32 nStart = pPos->nContent.GetIndex(); + sal_Int32 nEnd = rText.getLength(); + + bool bHasMark = rPam.HasMark(); + if( bHasMark ) + { + // in the same node? + if( rPam.GetMark()->nNode == pPos->nNode ) + { + // then use that end + const sal_Int32 nTEnd = rPam.GetMark()->nContent.GetIndex(); + if( nTEnd < nEnd ) + nEnd = nTEnd; + } + rPam.DeleteMark(); + } + + // search the start + // look where a ruby attribute starts + const SwpHints* pHts = pTNd->GetpSwpHints(); + const SwTextAttr* pAttr = nullptr; + if( pHts ) + { + for( size_t nHtIdx = 0; nHtIdx < pHts->Count(); ++nHtIdx ) + { + const SwTextAttr* pHt = pHts->Get(nHtIdx); + if( RES_TXTATR_CJK_RUBY == pHt->Which() && + pHt->GetAnyEnd() > nStart ) + { + if( pHt->GetStart() < nEnd ) + { + pAttr = pHt; + if( !bHasMark && nStart > pAttr->GetStart() ) + { + nStart = pAttr->GetStart(); + pPos->nContent = nStart; + } + } + break; + } + } + } + + if( !bHasMark && nStart && ( !pAttr || nStart != pAttr->GetStart()) ) + { + // skip to the word begin! + const sal_Int32 nWordStt = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nStart, + g_pBreakIt->GetLocale( pTNd->GetLang( nStart )), + WordType::ANYWORD_IGNOREWHITESPACES, + true ).startPos; + if (nWordStt < nStart && nWordStt >= 0) + { + nStart = nWordStt; + pPos->nContent = nStart; + } + } + + bool bAlphaNum = false; + sal_Int32 nWordEnd = nEnd; + CharClass& rCC = GetAppCharClass(); + while( nStart < nEnd ) + { + if( pAttr && nStart == pAttr->GetStart() ) + { + pPos->nContent = nStart; + if( !rPam.HasMark() ) + { + rPam.SetMark(); + pPos->nContent = pAttr->GetAnyEnd(); + if( pPos->nContent.GetIndex() > nEnd ) + pPos->nContent = nEnd; + rEntry.SetRubyAttr( pAttr->GetRuby() ); + } + break; + } + + sal_Int32 nChType = rCC.getType(rText, nStart); + bool bIgnoreChar = false, bIsAlphaNum = false, bChkNxtWrd = false; + switch( nChType ) + { + case UnicodeType::UPPERCASE_LETTER: + case UnicodeType::LOWERCASE_LETTER: + case UnicodeType::TITLECASE_LETTER: + case UnicodeType::DECIMAL_DIGIT_NUMBER: + bChkNxtWrd = bIsAlphaNum = true; + break; + + case UnicodeType::SPACE_SEPARATOR: + case UnicodeType::CONTROL: +/*??*/ case UnicodeType::PRIVATE_USE: + case UnicodeType::START_PUNCTUATION: + case UnicodeType::END_PUNCTUATION: + bIgnoreChar = true; + break; + + case UnicodeType::OTHER_LETTER: + bChkNxtWrd = true; + [[fallthrough]]; + default: + bIsAlphaNum = false; + break; + } + + if( rPam.HasMark() ) + { + if( bIgnoreChar || bIsAlphaNum != bAlphaNum || nStart >= nWordEnd ) + break; + } + else if( !bIgnoreChar ) + { + rPam.SetMark(); + bAlphaNum = bIsAlphaNum; + if (bChkNxtWrd) + { + // search the end of this word + nWordEnd = g_pBreakIt->GetBreakIter()->getWordBoundary( + rText, nStart, + g_pBreakIt->GetLocale( pTNd->GetLang( nStart )), + WordType::ANYWORD_IGNOREWHITESPACES, + true ).endPos; + if( 0 > nWordEnd || nWordEnd > nEnd || nWordEnd == nStart ) + nWordEnd = nEnd; + } + } + pTNd->GoNext( &pPos->nContent, CRSR_SKIP_CHARS ); + nStart = pPos->nContent.GetIndex(); + } + + nStart = rPam.GetMark()->nContent.GetIndex(); + rEntry.SetText( rText.copy( nStart, + rPam.GetPoint()->nContent.GetIndex() - nStart )); + return rPam.HasMark(); +} + +SwRubyListEntry::~SwRubyListEntry() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docsort.cxx b/sw/source/core/doc/docsort.cxx new file mode 100644 index 000000000..21ff81b7b --- /dev/null +++ b/sw/source/core/doc/docsort.cxx @@ -0,0 +1,937 @@ +/* -*- 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 <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <sortopt.hxx> +#include <docsort.hxx> +#include <UndoSort.hxx> +#include <UndoRedline.hxx> +#include <hints.hxx> +#include <tblsel.hxx> +#include <cellatr.hxx> +#include <redline.hxx> +#include <node2lay.hxx> +#include <frameformats.hxx> + +#include <set> +#include <utility> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star; + +SwSortOptions* SwSortElement::pOptions = nullptr; +SwDoc* SwSortElement::pDoc = nullptr; +const FlatFndBox* SwSortElement::pBox = nullptr; +CollatorWrapper* SwSortElement::pSortCollator = nullptr; +lang::Locale* SwSortElement::pLocale = nullptr; +OUString* SwSortElement::pLastAlgorithm = nullptr; +LocaleDataWrapper* SwSortElement::pLclData = nullptr; + +// List of all sorted elements + +/// Construct a SortElement for the Sort +void SwSortElement::Init( SwDoc* pD, const SwSortOptions& rOpt, + FlatFndBox const * pFltBx ) +{ + OSL_ENSURE( !pDoc && !pOptions && !pBox, "Who forgot to call Finit?" ); + pDoc = pD; + pOptions = new SwSortOptions( rOpt ); + pBox = pFltBx; + + LanguageType nLang = rOpt.nLanguage; + if ( nLang.anyOf( + LANGUAGE_NONE, + LANGUAGE_DONTKNOW)) + nLang = GetAppLanguage(); + pLocale = new lang::Locale( LanguageTag::convertToLocale( nLang ) ); + + pSortCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() ); +} + +void SwSortElement::Finit() +{ + delete pOptions; + pOptions = nullptr; + delete pLocale; + pLocale = nullptr; + delete pLastAlgorithm; + pLastAlgorithm = nullptr; + delete pSortCollator; + pSortCollator = nullptr; + delete pLclData; + pLclData = nullptr; + pDoc = nullptr; + pBox = nullptr; +} + +SwSortElement::~SwSortElement() +{ +} + +double SwSortElement::StrToDouble( const OUString& rStr ) +{ + if( !pLclData ) + pLclData = new LocaleDataWrapper( LanguageTag( *pLocale )); + + rtl_math_ConversionStatus eStatus; + sal_Int32 nEnd; + double nRet = pLclData->stringToDouble( rStr, true, &eStatus, &nEnd ); + + if( rtl_math_ConversionStatus_Ok != eStatus || nEnd == 0 ) + nRet = 0.0; + return nRet; +} + +int SwSortElement::keycompare(const SwSortElement& rCmp, sal_uInt16 nKey) const +{ + int nCmp = 0; + // The actual comparison + const SwSortElement *pOrig, *pCmp; + + const SwSortKey* pSrtKey = pOptions->aKeys[ nKey ].get(); + if( pSrtKey->eSortOrder == SwSortOrder::Ascending ) + { + pOrig = this; + pCmp = &rCmp; + } + else + { + pOrig = &rCmp; + pCmp = this; + } + + if( pSrtKey->bIsNumeric ) + { + double n1 = pOrig->GetValue( nKey ); + double n2 = pCmp->GetValue( nKey ); + + nCmp = n1 < n2 ? -1 : n1 == n2 ? 0 : 1; + } + else + { + if( !pLastAlgorithm || *pLastAlgorithm != pSrtKey->sSortType ) + { + if( pLastAlgorithm ) + *pLastAlgorithm = pSrtKey->sSortType; + else + pLastAlgorithm = new OUString( pSrtKey->sSortType ); + pSortCollator->loadCollatorAlgorithm( *pLastAlgorithm, + *pLocale, + pOptions->bIgnoreCase ? SW_COLLATOR_IGNORES : 0 ); + } + + nCmp = pSortCollator->compareString( + pOrig->GetKey( nKey ), pCmp->GetKey( nKey )); + } + return nCmp; +} + +bool SwSortElement::operator<(const SwSortElement& rCmp) const +{ + // The actual comparison + for(size_t nKey = 0; nKey < pOptions->aKeys.size(); ++nKey) + { + int nCmp = keycompare(rCmp, nKey); + + if (nCmp == 0) + continue; + + return nCmp < 0; + } + + return false; +} + +double SwSortElement::GetValue( sal_uInt16 nKey ) const +{ + return StrToDouble( GetKey( nKey )); +} + +/// SortingElement for Text +SwSortTextElement::SwSortTextElement(const SwNodeIndex& rPos) + : nOrg(rPos.GetIndex()), aPos(rPos) +{ +} + +OUString SwSortTextElement::GetKey(sal_uInt16 nId) const +{ + SwTextNode* pTextNd = aPos.GetNode().GetTextNode(); + if( !pTextNd ) + return OUString(); + + // for TextNodes + const OUString& rStr = pTextNd->GetText(); + + sal_Unicode nDeli = pOptions->cDeli; + sal_uInt16 nDCount = pOptions->aKeys[nId]->nColumnId, i = 1; + sal_Int32 nStart = 0; + + // Find the delimiter + while( nStart != -1 && i < nDCount) + if( -1 != ( nStart = rStr.indexOf( nDeli, nStart ) ) ) + { + nStart++; + i++; + } + + // Found next delimiter or end of String + // and copy + sal_Int32 nEnd = rStr.indexOf( nDeli, nStart+1 ); + if (nEnd == -1) + return rStr.copy( nStart ); + return rStr.copy( nStart, nEnd-nStart ); +} + +/// SortingElement for Tables +SwSortBoxElement::SwSortBoxElement( sal_uInt16 nRC ) + : nRow( nRC ) +{ +} + +/// Get Key for a cell +OUString SwSortBoxElement::GetKey(sal_uInt16 nKey) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey]->nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + // Extract the Text + OUStringBuffer aRetStr; + if( pFndBox ) + { // Get StartNode and skip it + const SwTableBox* pMyBox = pFndBox->GetBox(); + OSL_ENSURE(pMyBox, "No atomic Box"); + + if( pMyBox->GetSttNd() ) + { + // Iterate over all the Box's TextNodes + const SwNode *pNd = nullptr, *pEndNd = pMyBox->GetSttNd()->EndOfSectionNode(); + for( sal_uLong nIdx = pMyBox->GetSttIdx() + 1; pNd != pEndNd; ++nIdx ) + { + pNd = pDoc->GetNodes()[ nIdx ]; + if( pNd->IsTextNode() ) + aRetStr.append(pNd->GetTextNode()->GetText()); + } + } + } + return aRetStr.makeStringAndClear(); +} + +double SwSortBoxElement::GetValue( sal_uInt16 nKey ) const +{ + const FndBox_* pFndBox; + sal_uInt16 nCol = pOptions->aKeys[nKey]->nColumnId-1; + + if( SwSortDirection::Rows == pOptions->eDirection ) + pFndBox = pBox->GetBox(nCol, nRow); // Sort rows + else + pFndBox = pBox->GetBox(nRow, nCol); // Sort columns + + double nVal; + if( pFndBox ) + { + const SwFormat *pFormat = pFndBox->GetBox()->GetFrameFormat(); + if (pDoc->GetNumberFormatter()->IsTextFormat( pFormat->GetTableBoxNumFormat().GetValue())) + nVal = SwSortElement::GetValue( nKey ); + else + nVal = pFormat->GetTableBoxValue().GetValue(); + } + else + nVal = 0; + + return nVal; +} + +/// Sort Text in the Document +bool SwDoc::SortText(const SwPaM& rPaM, const SwSortOptions& rOpt) +{ + // Check if Frame is in the Text + const SwPosition *pStart = rPaM.Start(), *pEnd = rPaM.End(); + + // Set index to the Selection's start + for ( const auto *pFormat : *GetSpzFrameFormats() ) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + + if (pAPos && (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) && + pStart->nNode <= pAPos->nNode && pAPos->nNode <= pEnd->nNode ) + return false; + } + + // Check if only TextNodes are within the Selection + { + sal_uLong nStart = pStart->nNode.GetIndex(), + nEnd = pEnd->nNode.GetIndex(); + while( nStart <= nEnd ) + // Iterate over a selected range + if( !GetNodes()[ nStart++ ]->IsTextNode() ) + return false; + } + + bool const bUndo = GetIDocumentUndoRedo().DoesUndo(); + if( bUndo ) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + } + + SwPaM* pRedlPam = nullptr; + SwUndoRedlineSort* pRedlUndo = nullptr; + SwUndoSort* pUndoSort = nullptr; + + // To-Do - add 'SwExtraRedlineTable' also ? + if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )) + { + pRedlPam = new SwPaM( pStart->nNode, pEnd->nNode, -1, 1 ); + SwContentNode* pCNd = pRedlPam->GetContentNode( false ); + if( pCNd ) + pRedlPam->GetMark()->nContent = pCNd->Len(); + + if( getIDocumentRedlineAccess().IsRedlineOn() && !IDocumentRedlineAccess::IsShowOriginal( getIDocumentRedlineAccess().GetRedlineFlags() ) ) + { + if( bUndo ) + { + pRedlUndo = new SwUndoRedlineSort( *pRedlPam,rOpt ); + GetIDocumentUndoRedo().DoUndo(false); + } + // First copy the range + SwNodeIndex aEndIdx( pEnd->nNode, 1 ); + SwNodeRange aRg( pStart->nNode, aEndIdx ); + GetNodes().Copy_( aRg, aEndIdx ); + + // range is new from pEnd->nNode+1 to aEndIdx + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + + pRedlPam->GetMark()->nNode.Assign( pEnd->nNode.GetNode(), 1 ); + pCNd = pRedlPam->GetContentNode( false ); + pRedlPam->GetMark()->nContent.Assign( pCNd, 0 ); + + pRedlPam->GetPoint()->nNode.Assign( aEndIdx.GetNode() ); + pCNd = pRedlPam->GetContentNode(); + sal_Int32 nCLen = 0; + if( !pCNd ) + { + pCNd = GetNodes()[ aEndIdx.GetIndex()-1 ]->GetContentNode(); + if( pCNd ) + { + nCLen = pCNd->Len(); + pRedlPam->GetPoint()->nNode.Assign( *pCNd ); + } + } + pRedlPam->GetPoint()->nContent.Assign( pCNd, nCLen ); + + if( pRedlUndo ) + pRedlUndo->SetValues( rPaM ); + } + else + { + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + delete pRedlPam; + pRedlPam = nullptr; + } + } + + SwNodeIndex aStart(pStart->nNode); + SwSortElement::Init( this, rOpt ); + std::multiset<SwSortTextElement> aSortSet; + while( aStart <= pEnd->nNode ) + { + // Iterate over a selected range + aSortSet.insert(SwSortTextElement(aStart)); + ++aStart; + } + + // Now comes the tricky part: Move Nodes (and always keep Undo in mind) + sal_uLong nBeg = pStart->nNode.GetIndex(); + SwNodeRange aRg( aStart, aStart ); + + if( bUndo && !pRedlUndo ) + { + pUndoSort = new SwUndoSort(rPaM, rOpt); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + + GetIDocumentUndoRedo().DoUndo(false); + + size_t n = 0; + for (const auto& rElem : aSortSet) + { + aStart = nBeg + n; + aRg.aStart = rElem.aPos.GetIndex(); + aRg.aEnd = aRg.aStart.GetIndex() + 1; + + // Move Nodes + getIDocumentContentOperations().MoveNodeRange( aRg, aStart, + SwMoveFlags::DEFAULT ); + + // Insert Move in Undo + if(pUndoSort) + { + pUndoSort->Insert(rElem.nOrg, nBeg + n); + } + ++n; + } + // Delete all elements from the SortArray + aSortSet.clear(); + SwSortElement::Finit(); + + if( pRedlPam ) + { + if( pRedlUndo ) + { + pRedlUndo->SetSaveRange( *pRedlPam ); + // UGLY: temp. enable Undo + GetIDocumentUndoRedo().DoUndo(true); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pRedlUndo) ); + GetIDocumentUndoRedo().DoUndo(false); + } + + // nBeg is start of sorted range + SwNodeIndex aSttIdx( GetNodes(), nBeg ); + + // the copied range is deleted + SwRangeRedline *const pDeleteRedline( + new SwRangeRedline( RedlineType::Delete, *pRedlPam )); + + // pRedlPam points to nodes that may be deleted (hidden) by + // AppendRedline, so adjust it beforehand to prevent ASSERT + pRedlPam->GetPoint()->nNode = aSttIdx; + SwContentNode* pCNd = aSttIdx.GetNode().GetContentNode(); + pRedlPam->GetPoint()->nContent.Assign( pCNd, 0 ); + + getIDocumentRedlineAccess().AppendRedline(pDeleteRedline, true); + + // the sorted range is inserted + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlPam ), true); + + if( pRedlUndo ) + { + SwNodeIndex aInsEndIdx( pRedlPam->GetMark()->nNode, -1 ); + pRedlPam->GetMark()->nNode = aInsEndIdx; + SwContentNode *const pPrevNode = + pRedlPam->GetMark()->nNode.GetNode().GetContentNode(); + pRedlPam->GetMark()->nContent.Assign( pPrevNode, pPrevNode->Len() ); + + pRedlUndo->SetValues( *pRedlPam ); + } + + delete pRedlPam; + pRedlPam = nullptr; + } + GetIDocumentUndoRedo().DoUndo( bUndo ); + if( bUndo ) + { + GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + + return true; +} + +/// Sort Table in the Document +bool SwDoc::SortTable(const SwSelBoxes& rBoxes, const SwSortOptions& rOpt) +{ + // Via SwDoc for Undo! + OSL_ENSURE( !rBoxes.empty(), "no valid Box list" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // We begin sorting + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara ); + } + + if(aFndBox.GetLines().empty()) + return false; + + if( !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ) + getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any ); + + FndLines_t::size_type nStart = 0; + if( pTableNd->GetTable().GetRowsToRepeat() > 0 && rOpt.eDirection == SwSortDirection::Rows ) + { + // Uppermost selected Cell + FndLines_t& rLines = aFndBox.GetLines(); + + while( nStart < rLines.size() ) + { + // Respect Split Merge nesting, + // extract the upper most + SwTableLine* pLine = rLines[nStart]->GetLine(); + while ( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + if( pTableNd->GetTable().IsHeadline( *pLine ) ) + nStart++; + else + break; + } + // Are all selected in the HeaderLine? -> no Offset + if( nStart == rLines.size() ) + nStart = 0; + } + + // Switch to relative Formulas + SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + + // Table as a flat array structure + FlatFndBox aFlatBox(this, aFndBox); + + if(!aFlatBox.IsSymmetric()) + return false; + + // Delete HTML layout + pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + // Delete the Table's Frames + pTableNd->DelFrames(); + // ? TL_CHART2: ? + + SwUndoSort* pUndoSort = nullptr; + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndoSort = new SwUndoSort( rBoxes[0]->GetSttIdx(), + rBoxes.back()->GetSttIdx(), + *pTableNd, rOpt, aFlatBox.HasItemSets() ); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + // Insert KeyElements + sal_uInt16 nCount = (rOpt.eDirection == SwSortDirection::Rows) ? + aFlatBox.GetRows() : aFlatBox.GetCols(); + + // Sort SortList by Key + SwSortElement::Init( this, rOpt, &aFlatBox ); + std::multiset<SwSortBoxElement> aSortList; + + // When sorting, do not include the first row if the HeaderLine is repeated + for( sal_uInt16 i = static_cast<sal_uInt16>(nStart); i < nCount; ++i) + { + aSortList.insert(SwSortBoxElement(i)); + } + + // Move after Sorting + SwMovedBoxes aMovedList; + sal_uInt16 i = 0; + for (const auto& rElem : aSortList) + { + if(rOpt.eDirection == SwSortDirection::Rows) + { + MoveRow(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + else + { + MoveCol(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort); + } + ++i; + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const sal_uLong nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 ); + + // TL_CHART2: need to inform chart of probably changed cell names + UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + // Delete all Elements in the SortArray + aSortList.clear(); + SwSortElement::Finit(); + + getIDocumentState().SetModified(); + return true; +} + +/// Move a row +void MoveRow(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for( sal_uInt16 i=0; i < rBox.GetCols(); ++i ) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(i, nS); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(i, nT); + + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + + // and move it + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( i, nS ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a column +void MoveCol(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT, + SwMovedBoxes& rMovedList, SwUndoSort* pUD) +{ + for(sal_uInt16 i=0; i < rBox.GetRows(); ++i) + { // Get old cell position and remember it + const FndBox_* pSource = rBox.GetBox(nS, i); + + // new cell position + const FndBox_* pTarget = rBox.GetBox(nT, i); + + // and move it + const SwTableBox* pT = pTarget->GetBox(); + const SwTableBox* pS = pSource->GetBox(); + + // and move it + bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX; + MoveCell(pDoc, pS, pT, bMoved, pUD); + + rMovedList.push_back(pS); + + if( pS != pT ) + { + SwFrameFormat* pTFormat = pT->GetFrameFormat(); + const SfxItemSet* pSSet = rBox.GetItemSet( nS, i ); + + if( pSSet || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat(); + pTFormat->LockModify(); + if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) ) + pTFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( pSSet ) + pTFormat->SetFormatAttr( *pSSet ); + pTFormat->UnlockModify(); + } + } + } +} + +/// Move a single Cell +void MoveCell(SwDoc* pDoc, const SwTableBox* pSource, const SwTableBox* pTar, + bool bMovedBefore, SwUndoSort* pUD) +{ + OSL_ENSURE(pSource && pTar,"Source or target missing"); + + if(pSource == pTar) + return; + + if(pUD) + pUD->Insert( pSource->GetName(), pTar->GetName() ); + + // Set Pam source to the first ContentNode + SwNodeRange aRg( *pSource->GetSttNd(), 0, *pSource->GetSttNd() ); + SwNode* pNd = pDoc->GetNodes().GoNext( &aRg.aStart ); + + // If the Cell (Source) wasn't moved + // -> insert an empty Node and move the rest or the Mark + // points to the first ContentNode + if( pNd->StartOfSectionNode() == pSource->GetSttNd() ) + pNd = pDoc->GetNodes().MakeTextNode( aRg.aStart, + pDoc->GetDfltTextFormatColl() ); + aRg.aEnd = *pNd->EndOfSectionNode(); + + // If the Target is empty (there is one empty Node) + // -> move and delete it + SwNodeIndex aTar( *pTar->GetSttNd() ); + pNd = pDoc->GetNodes().GoNext( &aTar ); // next ContentNode + sal_uLong nCount = pNd->EndOfSectionIndex() - pNd->StartOfSectionIndex(); + + bool bDelFirst = false; + if( nCount == 2 ) + { + OSL_ENSURE( pNd->GetContentNode(), "No ContentNode"); + bDelFirst = !pNd->GetContentNode()->Len() && bMovedBefore; + } + + if(!bDelFirst) + { // We already have Content -> old Content Section Down + SwNodeRange aRgTar( aTar.GetNode(), 0, *pNd->EndOfSectionNode() ); + pDoc->GetNodes().SectionDown( &aRgTar ); + } + + // Insert the Source + SwNodeIndex aIns( *pTar->GetSttNd()->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIns, + SwMoveFlags::DEFAULT ); + + // If first Node is empty -> delete it + if(bDelFirst) + pDoc->GetNodes().Delete( aTar ); +} + +/// Generate two-dimensional array of FndBoxes +FlatFndBox::FlatFndBox(SwDoc* pDocPtr, const FndBox_& rBoxRef) : + pDoc(pDocPtr), + nRow(0), + nCol(0) +{ // If the array is symmetric + bSym = CheckLineSymmetry(rBoxRef); + if( bSym ) + { + // Determine column/row count + nCols = GetColCount(rBoxRef); + nRows = GetRowCount(rBoxRef); + + // Create linear array + size_t nCount = static_cast<size_t>(nRows) * nCols; + pArr = std::make_unique<FndBox_ const *[]>(nCount); + memset(pArr.get(), 0, sizeof(const FndBox_*) * nCount); + + FillFlat( rBoxRef ); + } +} + +FlatFndBox::~FlatFndBox() +{ +} + +/// All Lines of a Box need to have same number of Boxes +bool FlatFndBox::CheckLineSymmetry(const FndBox_& rBox) +{ + const FndLines_t &rLines = rBox.GetLines(); + FndBoxes_t::size_type nBoxes {0}; + + for (FndLines_t::size_type i=0; i < rLines.size(); ++i) + { + const FndLine_* pLn = rLines[i].get(); + const FndBoxes_t& rBoxes = pLn->GetBoxes(); + + // Number of Boxes of all Lines is unequal -> no symmetry + if( i && nBoxes != rBoxes.size()) + return false; + + nBoxes = rBoxes.size(); + if( !CheckBoxSymmetry( *pLn ) ) + return false; + } + return true; +} + +/// Check Box for symmetry (All Boxes of a Line need to have same number of Lines) +bool FlatFndBox::CheckBoxSymmetry(const FndLine_& rLn) +{ + const FndBoxes_t &rBoxes = rLn.GetBoxes(); + FndLines_t::size_type nLines {0}; + + for (FndBoxes_t::size_type i = 0; i < rBoxes.size(); ++i) + { + FndBox_ const*const pBox = rBoxes[i].get(); + const FndLines_t& rLines = pBox->GetLines(); + + // Number of Lines of all Boxes is unequal -> no symmetry + if( i && nLines != rLines.size() ) + return false; + + nLines = rLines.size(); + if( nLines && !CheckLineSymmetry( *pBox ) ) + return false; + } + return true; +} + +/// Maximum count of Columns (Boxes) +sal_uInt16 FlatFndBox::GetColCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + // Iterate over Lines + if( rLines.empty() ) + return 1; + + sal_uInt16 nSum = 0; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + sal_uInt16 nCount = 0; + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + for (const auto &rpB : rBoxes) + { // Iterate recursively over the Lines + nCount += rpB->GetLines().empty() ? 1 : GetColCount(*rpB); + } + + if( nSum < nCount ) + nSum = nCount; + } + return nSum; +} + +/// Maximum count of Rows (Lines) +sal_uInt16 FlatFndBox::GetRowCount(const FndBox_& rBox) +{ + const FndLines_t& rLines = rBox.GetLines(); + if( rLines.empty() ) + return 1; + + sal_uInt16 nLines = 0; + for (const auto & pLine : rLines) + { // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nLn = 1; + for (const auto &rpB : rBoxes) + { + if (!rpB->GetLines().empty()) + { // Iterate recursively over the Lines + nLn = std::max(GetRowCount(*rpB), nLn); + } + } + + nLines = nLines + nLn; + } + return nLines; +} + +/// Create a linear array of atomic FndBoxes +void FlatFndBox::FillFlat(const FndBox_& rBox, bool bLastBox) +{ + bool bModRow = false; + const FndLines_t& rLines = rBox.GetLines(); + + // Iterate over Lines + sal_uInt16 nOldRow = nRow; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nOldCol = nCol; + for( FndBoxes_t::size_type j = 0; j < rBoxes.size(); ++j ) + { + // Check the Box if it's an atomic one + const FndBox_ *const pBox = rBoxes[j].get(); + + if( pBox->GetLines().empty() ) + { + // save it + sal_uInt16 nOff = nRow * nCols + nCol; + pArr[nOff] = pBox; + + // Save the Formula/Format/Value values + const SwFrameFormat* pFormat = pBox->GetBox()->GetFrameFormat(); + if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE ) ) + { + auto pSet = std::make_unique<SfxItemSet>( + pDoc->GetAttrPool(), + svl::Items< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{}); + pSet->Put( pFormat->GetAttrSet() ); + if( ppItemSets.empty() ) + { + size_t nCount = static_cast<size_t>(nRows) * nCols; + ppItemSets.resize(nCount); + } + ppItemSets[nOff] = std::move(pSet); + } + + bModRow = true; + } + else + { + // Iterate recursively over the Lines of a Box + FillFlat( *pBox, ( j+1 == rBoxes.size() ) ); + } + nCol++; + } + if(bModRow) + nRow++; + nCol = nOldCol; + } + if(!bLastBox) + nRow = nOldRow; +} + +/// Access a specific Cell +const FndBox_* FlatFndBox::GetBox(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + sal_uInt16 nOff = n_Row * nCols + n_Col; + const FndBox_* pTmp = pArr[nOff]; + + OSL_ENSURE(n_Col < nCols && n_Row < nRows && pTmp, "invalid array access"); + return pTmp; +} + +const SfxItemSet* FlatFndBox::GetItemSet(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + OSL_ENSURE( ppItemSets.empty() || ( n_Col < nCols && n_Row < nRows), "invalid array access"); + + return !ppItemSets.empty() ? ppItemSets[unsigned(n_Row * nCols) + n_Col].get() : nullptr; +} + +sal_uInt16 SwMovedBoxes::GetPos(const SwTableBox* pTableBox) const +{ + std::vector<const SwTableBox*>::const_iterator it = std::find(mBoxes.begin(), mBoxes.end(), pTableBox); + return it == mBoxes.end() ? USHRT_MAX : it - mBoxes.begin(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docstat.cxx b/sw/source/core/doc/docstat.cxx new file mode 100644 index 000000000..c34e8d094 --- /dev/null +++ b/sw/source/core/doc/docstat.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <docstat.hxx> + +SwDocStat::SwDocStat() : + nTable(0), + nGrf(0), + nOLE(0), + nPage(1), + nPara(1), + nAllPara(1), + nWord(0), + nAsianWord(0), + nChar(0), + nCharExcludingSpaces(0), + bModified(true) +{} + +void SwDocStat::Reset() +{ + nTable = 0; + nGrf = 0; + nOLE = 0; + nPage = 1; + nPara = 1; + nAllPara= 1; + nWord = 0; + nAsianWord = 0; + nChar = 0; + nCharExcludingSpaces = 0; + bModified = true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx new file mode 100644 index 000000000..7ca7103ff --- /dev/null +++ b/sw/source/core/doc/doctxm.cxx @@ -0,0 +1,2076 @@ +/* -*- 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 <limits.h> +#include <hintids.hxx> +#include <editeng/formatbreakitem.hxx> +#include <comphelper/classids.hxx> +#include <docsh.hxx> +#include <ndole.hxx> +#include <txttxmrk.hxx> +#include <fmtpdsc.hxx> +#include <frmatr.hxx> +#include <pagedesc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pagefrm.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <doctxm.hxx> +#include <txmsrt.hxx> +#include <rolbck.hxx> +#include <poolfmt.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <UndoAttribute.hxx> +#include <UndoSection.hxx> +#include <swundo.hxx> +#include <mdiexp.hxx> +#include <docary.hxx> +#include <charfmt.hxx> +#include <fchrfmt.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <expfld.hxx> +#include <mvsave.hxx> +#include <node2lay.hxx> +#include <SwStyleNameMapper.hxx> +#include <breakit.hxx> +#include <scriptinfo.hxx> +#include <calbck.hxx> +#include <ToxTextGenerator.hxx> +#include <ToxTabStopTokenHandler.hxx> +#include <frameformats.hxx> +#include <tools/datetimeutils.hxx> +#include <tools/globname.hxx> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <o3tl/safeint.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +template<typename T, typename... Args> static +typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type +MakeSwTOXSortTabBase(SwRootFrame const*const pLayout, Args&& ... args) +{ + std::unique_ptr<T> pRet(new T(std::forward<Args>(args)...)); + pRet->InitText(pLayout); // ensure it's expanded with the layout + return pRet; +} + +void SwDoc::GetTOIKeys(SwTOIKeyType eTyp, std::vector<OUString>& rArr, + SwRootFrame const& rLayout) const +{ + rArr.clear(); + + // Look up all Primary and Secondary via the Pool + for (const SfxPoolItem* pPoolItem : GetAttrPool().GetItemSurrogates(RES_TXTATR_TOXMARK)) + { + const SwTOXMark* pItem = dynamic_cast<const SwTOXMark*>(pPoolItem); + if( !pItem ) + continue; + const SwTOXType* pTOXType = pItem->GetTOXType(); + if ( !pTOXType || pTOXType->GetType()!=TOX_INDEX ) + continue; + const SwTextTOXMark* pMark = pItem->GetTextTOXMark(); + if ( pMark && pMark->GetpTextNd() && + pMark->GetpTextNd()->GetNodes().IsDocNodes() && + (!rLayout.IsHideRedlines() + || !sw::IsMarkHintHidden(rLayout, *pMark->GetpTextNd(), *pMark))) + { + const OUString sStr = TOI_PRIMARY == eTyp + ? pItem->GetPrimaryKey() + : pItem->GetSecondaryKey(); + + if( !sStr.isEmpty() ) + rArr.push_back( sStr ); + } + } +} + +/// Get current table of contents Mark. +sal_uInt16 SwDoc::GetCurTOXMark( const SwPosition& rPos, + SwTOXMarks& rArr ) +{ + // search on Position rPos for all SwTOXMarks + SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode(); + if( !pTextNd || !pTextNd->GetpSwpHints() ) + return 0; + + const SwpHints & rHts = *pTextNd->GetpSwpHints(); + sal_Int32 nSttIdx; + const sal_Int32 *pEndIdx; + + const sal_Int32 nCurrentPos = rPos.nContent.GetIndex(); + + for( size_t n = 0; n < rHts.Count(); ++n ) + { + const SwTextAttr* pHt = rHts.Get(n); + if( RES_TXTATR_TOXMARK != pHt->Which() ) + continue; + if( ( nSttIdx = pHt->GetStart() ) < nCurrentPos ) + { + // also check the end + pEndIdx = pHt->End(); + if( nullptr == pEndIdx || *pEndIdx <= nCurrentPos ) + continue; // keep searching + } + else if( nSttIdx > nCurrentPos ) + // If Hint's Start is greater than rPos, break, because + // the attributes are sorted by Start! + break; + + SwTOXMark* pTMark = const_cast<SwTOXMark*>(&pHt->GetTOXMark()); + rArr.push_back( pTMark ); + } + return rArr.size(); +} + +/// Delete table of contents Mark +void SwDoc::DeleteTOXMark( const SwTOXMark* pTOXMark ) +{ + const SwTextTOXMark* pTextTOXMark = pTOXMark->GetTextTOXMark(); + assert(pTextTOXMark); + + SwTextNode& rTextNd = const_cast<SwTextNode&>(pTextTOXMark->GetTextNode()); + assert(rTextNd.GetpSwpHints()); + + if (pTextTOXMark->HasDummyChar()) + { + // tdf#106377 don't use SwUndoResetAttr, it uses NOTXTATRCHR + SwPaM tmp(rTextNd, pTextTOXMark->GetStart(), + rTextNd, pTextTOXMark->GetStart()+1); + assert(rTextNd.GetText()[pTextTOXMark->GetStart()] == CH_TXTATR_INWORD); + getIDocumentContentOperations().DeleteRange(tmp); + } + else + { + std::unique_ptr<SwRegHistory> aRHst; + if (GetIDocumentUndoRedo().DoesUndo()) + { + // save attributes for Undo + SwUndoResetAttr* pUndo = new SwUndoResetAttr( + SwPosition( rTextNd, SwIndex( &rTextNd, pTextTOXMark->GetStart() ) ), + RES_TXTATR_TOXMARK ); + GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + + aRHst.reset(new SwRegHistory(rTextNd, &pUndo->GetHistory())); + rTextNd.GetpSwpHints()->Register(aRHst.get()); + } + + rTextNd.DeleteAttribute( const_cast<SwTextTOXMark*>(pTextTOXMark) ); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + if( rTextNd.GetpSwpHints() ) + rTextNd.GetpSwpHints()->DeRegister(); + } + } + + getIDocumentState().SetModified(); +} + +namespace { + +/// Travel between table of content Marks +class CompareNodeContent +{ + sal_uLong nNode; + sal_Int32 nContent; +public: + CompareNodeContent( sal_uLong nNd, sal_Int32 nCnt ) + : nNode( nNd ), nContent( nCnt ) {} + + bool operator==( const CompareNodeContent& rCmp ) const + { return nNode == rCmp.nNode && nContent == rCmp.nContent; } + bool operator!=( const CompareNodeContent& rCmp ) const + { return nNode != rCmp.nNode || nContent != rCmp.nContent; } + bool operator< ( const CompareNodeContent& rCmp ) const + { return nNode < rCmp.nNode || + ( nNode == rCmp.nNode && nContent < rCmp.nContent); } + bool operator<=( const CompareNodeContent& rCmp ) const + { return nNode < rCmp.nNode || + ( nNode == rCmp.nNode && nContent <= rCmp.nContent); } + bool operator> ( const CompareNodeContent& rCmp ) const + { return nNode > rCmp.nNode || + ( nNode == rCmp.nNode && nContent > rCmp.nContent); } + bool operator>=( const CompareNodeContent& rCmp ) const + { return nNode > rCmp.nNode || + ( nNode == rCmp.nNode && nContent >= rCmp.nContent); } +}; + +} + +const SwTOXMark& SwDoc::GotoTOXMark( const SwTOXMark& rCurTOXMark, + SwTOXSearch eDir, bool bInReadOnly ) +{ + const SwTextTOXMark* pMark = rCurTOXMark.GetTextTOXMark(); + OSL_ENSURE(pMark, "pMark==0 invalid TextTOXMark"); + + const SwTextNode *pTOXSrc = pMark->GetpTextNd(); + + CompareNodeContent aAbsIdx( pTOXSrc->GetIndex(), pMark->GetStart() ); + CompareNodeContent aPrevPos( 0, 0 ); + CompareNodeContent aNextPos( ULONG_MAX, SAL_MAX_INT32 ); + CompareNodeContent aMax( 0, 0 ); + CompareNodeContent aMin( ULONG_MAX, SAL_MAX_INT32 ); + + const SwTOXMark* pNew = nullptr; + const SwTOXMark* pMax = &rCurTOXMark; + const SwTOXMark* pMin = &rCurTOXMark; + + const SwTOXType* pType = rCurTOXMark.GetTOXType(); + SwTOXMarks aMarks; + SwTOXMark::InsertTOXMarks( aMarks, *pType ); + + for(SwTOXMark* pTOXMark : aMarks) + { + if ( pTOXMark == &rCurTOXMark ) + continue; + + pMark = pTOXMark->GetTextTOXMark(); + if (!pMark) + continue; + + pTOXSrc = pMark->GetpTextNd(); + if (!pTOXSrc) + continue; + + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + const SwContentFrame* pCFrame = pTOXSrc->getLayoutFrame( + getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp); + if (!pCFrame) + continue; + + if ( bInReadOnly || !pCFrame->IsProtected() ) + { + CompareNodeContent aAbsNew( pTOXSrc->GetIndex(), pMark->GetStart() ); + switch( eDir ) + { + // The following (a bit more complicated) statements make it + // possible to also travel across Entries on the same (!) + // position. If someone has time, please feel free to optimize. + case TOX_SAME_PRV: + if (pTOXMark->GetText(nullptr) != rCurTOXMark.GetText(nullptr)) + break; + [[fallthrough]]; + case TOX_PRV: + if ( (aAbsNew < aAbsIdx && aAbsNew > aPrevPos) || + (aAbsIdx == aAbsNew && + (reinterpret_cast<sal_uLong>(&rCurTOXMark) > reinterpret_cast<sal_uLong>(pTOXMark) && + (!pNew || aPrevPos < aAbsIdx || reinterpret_cast<sal_uLong>(pNew) < reinterpret_cast<sal_uLong>(pTOXMark) ) )) || + (aPrevPos == aAbsNew && aAbsIdx != aAbsNew && + reinterpret_cast<sal_uLong>(pTOXMark) > reinterpret_cast<sal_uLong>(pNew)) ) + { + pNew = pTOXMark; + aPrevPos = aAbsNew; + if ( aAbsNew >= aMax ) + { + aMax = aAbsNew; + pMax = pTOXMark; + } + } + break; + + case TOX_SAME_NXT: + if (pTOXMark->GetText(nullptr) != rCurTOXMark.GetText(nullptr)) + break; + [[fallthrough]]; + case TOX_NXT: + if ( (aAbsNew > aAbsIdx && aAbsNew < aNextPos) || + (aAbsIdx == aAbsNew && + (reinterpret_cast<sal_uLong>(&rCurTOXMark) < reinterpret_cast<sal_uLong>(pTOXMark) && + (!pNew || aNextPos > aAbsIdx || reinterpret_cast<sal_uLong>(pNew) > reinterpret_cast<sal_uLong>(pTOXMark)) )) || + (aNextPos == aAbsNew && aAbsIdx != aAbsNew && + reinterpret_cast<sal_uLong>(pTOXMark) < reinterpret_cast<sal_uLong>(pNew)) ) + { + pNew = pTOXMark; + aNextPos = aAbsNew; + if ( aAbsNew <= aMin ) + { + aMin = aAbsNew; + pMin = pTOXMark; + } + } + break; + } + } + } + + // We couldn't find a successor + // Use minimum or maximum + if(!pNew) + { + switch(eDir) + { + case TOX_PRV: + case TOX_SAME_PRV: + pNew = pMax; + break; + case TOX_NXT: + case TOX_SAME_NXT: + pNew = pMin; + break; + default: + pNew = &rCurTOXMark; + } + } + return *pNew; +} + +SwTOXBaseSection* SwDoc::InsertTableOf( const SwPosition& rPos, + const SwTOXBase& rTOX, + const SfxItemSet* pSet, + bool bExpand, + SwRootFrame const*const pLayout) +{ + SwPaM aPam( rPos ); + return InsertTableOf( aPam, rTOX, pSet, bExpand, pLayout ); +} + +SwTOXBaseSection* SwDoc::InsertTableOf( const SwPaM& aPam, + const SwTOXBase& rTOX, + const SfxItemSet* pSet, + bool bExpand, + SwRootFrame const*const pLayout ) +{ + assert(!bExpand || pLayout != nullptr); + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSTOX, nullptr ); + + OUString sSectNm = GetUniqueTOXBaseName( *rTOX.GetTOXType(), rTOX.GetTOXName() ); + SwSectionData aSectionData( SectionType::ToxContent, sSectNm ); + + std::pair<SwTOXBase const*, sw::RedlineMode> const tmp(&rTOX, + pLayout && pLayout->IsHideRedlines() + ? sw::RedlineMode::Hidden + : sw::RedlineMode::Shown); + SwTOXBaseSection *const pNewSection = dynamic_cast<SwTOXBaseSection *>( + InsertSwSection(aPam, aSectionData, & tmp, pSet, false)); + if (pNewSection) + { + SwSectionNode *const pSectNd = pNewSection->GetFormat()->GetSectionNode(); + pNewSection->SetTOXName(sSectNm); // rTOX may have had no name... + + if( bExpand ) + { + // add value for 2nd parameter = true to + // indicate, that a creation of a new table of content has to be performed. + // Value of 1st parameter = default value. + pNewSection->Update( nullptr, pLayout, true ); + } + else if( rTOX.GetTitle().getLength()==1 && IsInReading() ) + // insert title of TOX + { + // then insert the headline section + SwNodeIndex aIdx( *pSectNd, +1 ); + + SwTextNode* pHeadNd = GetNodes().MakeTextNode( aIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + + SwSectionData headerData( SectionType::ToxHeader, pNewSection->GetTOXName()+"_Head" ); + + SwNodeIndex aStt( *pHeadNd ); --aIdx; + SwSectionFormat* pSectFormat = MakeSectionFormat(); + GetNodes().InsertTextSection( + aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false); + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSTOX, nullptr ); + + return pNewSection; +} + +void SwDoc::InsertTableOf( sal_uLong nSttNd, sal_uLong nEndNd, + const SwTOXBase& rTOX, + const SfxItemSet* pSet ) +{ + // check for recursive TOX + SwNode* pNd = GetNodes()[ nSttNd ]; + SwSectionNode* pSectNd = pNd->FindSectionNode(); + while( pSectNd ) + { + SectionType eT = pSectNd->GetSection().GetType(); + if( SectionType::ToxHeader == eT || SectionType::ToxContent == eT ) + return; + pSectNd = pSectNd->StartOfSectionNode()->FindSectionNode(); + } + + const OUString sSectNm = GetUniqueTOXBaseName(*rTOX.GetTOXType(), rTOX.GetTOXName()); + + SwSectionData aSectionData( SectionType::ToxContent, sSectNm ); + + SwNodeIndex aStt( GetNodes(), nSttNd ), aEnd( GetNodes(), nEndNd ); + SwSectionFormat* pFormat = MakeSectionFormat(); + if(pSet) + pFormat->SetFormatAttr(*pSet); + + SwSectionNode *const pNewSectionNode = + GetNodes().InsertTextSection(aStt, *pFormat, aSectionData, &rTOX, &aEnd); + if (!pNewSectionNode) + { + DelSectionFormat( pFormat ); + return; + } + + SwTOXBaseSection *const pNewSection( + dynamic_cast<SwTOXBaseSection*>(& pNewSectionNode->GetSection())); + if (pNewSection) + pNewSection->SetTOXName(sSectNm); // rTOX may have had no name... +} + +/// Get current table of contents +SwTOXBase* SwDoc::GetCurTOX( const SwPosition& rPos ) +{ + SwNode& rNd = rPos.nNode.GetNode(); + SwSectionNode* pSectNd = rNd.FindSectionNode(); + while( pSectNd ) + { + SectionType eT = pSectNd->GetSection().GetType(); + if( SectionType::ToxContent == eT ) + { + OSL_ENSURE( dynamic_cast< const SwTOXBaseSection *>( &pSectNd->GetSection()) != nullptr, + "no TOXBaseSection!" ); + SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>( + pSectNd->GetSection()); + return &rTOXSect; + } + pSectNd = pSectNd->StartOfSectionNode()->FindSectionNode(); + } + return nullptr; +} + +const SwAttrSet& SwDoc::GetTOXBaseAttrSet(const SwTOXBase& rTOXBase) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + SwSectionFormat const * pFormat = rTOXSect.GetFormat(); + OSL_ENSURE( pFormat, "invalid TOXBaseSection!" ); + return pFormat->GetAttrSet(); +} + +const SwTOXBase* SwDoc::GetDefaultTOXBase( TOXTypes eTyp, bool bCreate ) +{ + std::unique_ptr<SwTOXBase>* prBase = nullptr; + switch(eTyp) + { + case TOX_CONTENT: prBase = &mpDefTOXBases->pContBase; break; + case TOX_INDEX: prBase = &mpDefTOXBases->pIdxBase; break; + case TOX_USER: prBase = &mpDefTOXBases->pUserBase; break; + case TOX_TABLES: prBase = &mpDefTOXBases->pTableBase; break; + case TOX_OBJECTS: prBase = &mpDefTOXBases->pObjBase; break; + case TOX_ILLUSTRATIONS: prBase = &mpDefTOXBases->pIllBase; break; + case TOX_AUTHORITIES: prBase = &mpDefTOXBases->pAuthBase; break; + case TOX_BIBLIOGRAPHY: prBase = &mpDefTOXBases->pBiblioBase; break; + case TOX_CITATION: /** TODO */break; + } + if (!prBase) + return nullptr; + if(!(*prBase) && bCreate) + { + SwForm aForm(eTyp); + const SwTOXType* pType = GetTOXType(eTyp, 0); + prBase->reset(new SwTOXBase(pType, aForm, SwTOXElement::NONE, pType->GetTypeName())); + } + return prBase->get(); +} + +void SwDoc::SetDefaultTOXBase(const SwTOXBase& rBase) +{ + std::unique_ptr<SwTOXBase>* prBase = nullptr; + switch(rBase.GetType()) + { + case TOX_CONTENT: prBase = &mpDefTOXBases->pContBase; break; + case TOX_INDEX: prBase = &mpDefTOXBases->pIdxBase; break; + case TOX_USER: prBase = &mpDefTOXBases->pUserBase; break; + case TOX_TABLES: prBase = &mpDefTOXBases->pTableBase; break; + case TOX_OBJECTS: prBase = &mpDefTOXBases->pObjBase; break; + case TOX_ILLUSTRATIONS: prBase = &mpDefTOXBases->pIllBase; break; + case TOX_AUTHORITIES: prBase = &mpDefTOXBases->pAuthBase; break; + case TOX_BIBLIOGRAPHY: prBase = &mpDefTOXBases->pBiblioBase; break; + case TOX_CITATION: /** TODO */break; + } + if (!prBase) + return; + prBase->reset(new SwTOXBase(rBase)); +} + +/// Delete table of contents +bool SwDoc::DeleteTOX( const SwTOXBase& rTOXBase, bool bDelNodes ) +{ + // We only delete the TOX, not the Nodes + bool bRet = false; + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" ); + + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + SwSectionFormat const * pFormat = rTOXSect.GetFormat(); + /* Save the start node of the TOX' section. */ + SwSectionNode const * pMyNode = pFormat ? pFormat->GetSectionNode() : nullptr; + if (pMyNode) + { + GetIDocumentUndoRedo().StartUndo( SwUndoId::CLEARTOXRANGE, nullptr ); + + /* Save start node of section's surrounding. */ + SwNode const * pStartNd = pMyNode->StartOfSectionNode(); + + /* Look for the point where to move the cursors in the area to + delete to. This is done by first searching forward from the + end of the TOX' section. If no content node is found behind + the TOX one is searched before it. If this is not + successful, too, insert new text node behind the end of + the TOX' section. The cursors from the TOX' section will be + moved to the content node found or the new text node. */ + + /* Set PaM to end of TOX' section and search following content node. + aSearchPam will contain the point where to move the cursors + to. */ + SwPaM aSearchPam(*pMyNode->EndOfSectionNode()); + SwPosition aEndPos(*pStartNd->EndOfSectionNode()); + if (! aSearchPam.Move() /* no content node found */ + || *aSearchPam.GetPoint() >= aEndPos /* content node found + outside surrounding */ + ) + { + /* Set PaM to beginning of TOX' section and search previous + content node */ + SwPaM aTmpPam(*pMyNode); + aSearchPam = aTmpPam; + SwPosition aStartPos(*pStartNd); + + if ( ! aSearchPam.Move(fnMoveBackward) /* no content node found */ + || *aSearchPam.GetPoint() <= aStartPos /* content node + found outside + surrounding */ + ) + { + /* There is no content node in the surrounding of + TOX'. Append text node behind TOX' section. */ + + SwPosition aInsPos(*pMyNode->EndOfSectionNode()); + getIDocumentContentOperations().AppendTextNode(aInsPos); + + SwPaM aTmpPam1(aInsPos); + aSearchPam = aTmpPam1; + } + } + + /* PaM containing the TOX. */ + SwPaM aPam(*pMyNode->EndOfSectionNode(), *pMyNode); + + /* Move cursors contained in TOX to the above calculated point. */ + PaMCorrAbs(aPam, *aSearchPam.GetPoint()); + + if( !bDelNodes ) + { + SwSections aArr( 0 ); + pFormat->GetChildSections( aArr, SectionSort::Not, false ); + for( const auto pSect : aArr ) + { + if( SectionType::ToxHeader == pSect->GetType() ) + { + DelSectionFormat( pSect->GetFormat(), bDelNodes ); + } + } + } + + DelSectionFormat( const_cast<SwSectionFormat *>(pFormat), bDelNodes ); + + GetIDocumentUndoRedo().EndUndo( SwUndoId::CLEARTOXRANGE, nullptr ); + bRet = true; + } + + return bRet; +} + +/// Manage table of content types +sal_uInt16 SwDoc::GetTOXTypeCount(TOXTypes eTyp) const +{ + sal_uInt16 nCnt = 0; + for( auto const & pTOXType : *mpTOXTypes ) + if( eTyp == pTOXType->GetType() ) + ++nCnt; + return nCnt; +} + +const SwTOXType* SwDoc::GetTOXType( TOXTypes eTyp, sal_uInt16 nId ) const +{ + sal_uInt16 nCnt = 0; + for( auto const & pTOXType : *mpTOXTypes ) + if( eTyp == pTOXType->GetType() && nCnt++ == nId ) + return pTOXType.get(); + return nullptr; +} + +const SwTOXType* SwDoc::InsertTOXType( const SwTOXType& rTyp ) +{ + SwTOXType * pNew = new SwTOXType(rTyp); + mpTOXTypes->emplace_back( pNew ); + return pNew; +} + +OUString SwDoc::GetUniqueTOXBaseName( const SwTOXType& rType, + const OUString& sChkStr ) const +{ + if( IsInMailMerge()) + { + OUString newName = "MailMergeTOX" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( mpSectionFormatTable->size() + 1 ); + if( !sChkStr.isEmpty()) + newName += sChkStr; + return newName; + } + + bool bUseChkStr = !sChkStr.isEmpty(); + const OUString& aName( rType.GetTypeName() ); + const sal_Int32 nNmLen = aName.getLength(); + + 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 pSectionFormat : *mpSectionFormatTable ) + { + const SwSectionNode *pSectNd = pSectionFormat->GetSectionNode(); + if ( !pSectNd ) + continue; + + const SwSection& rSect = pSectNd->GetSection(); + if (rSect.GetType()==SectionType::ToxContent) + { + const OUString& rNm = rSect.GetSectionName(); + if ( rNm.startsWith(aName) ) + { + // Calculate number and set the Flag + nNum = rNm.copy( nNmLen ).toInt32(); + if( nNum-- && nNum < mpSectionFormatTable->size() ) + pSetFlags[ nNum / 8 ] |= (0x01 << ( nNum & 0x07 )); + } + if ( bUseChkStr && sChkStr==rNm ) + bUseChkStr = false; + } + } + + if( !bUseChkStr ) + { + // All Numbers have been flagged accordingly, so get the right Number + nNum = mpSectionFormatTable->size(); + for( SwSectionFormats::size_type n = 0; n < nFlagSize; ++n ) + { + sal_uInt8 nTmp = pSetFlags[ n ]; + if( nTmp != 0xff ) + { + // so get the Number + nNum = n * 8; + while( nTmp & 1 ) + { + ++nNum; + nTmp >>= 1; + } + break; + } + } + } + if ( bUseChkStr ) + return sChkStr; + return aName + OUString::number( ++nNum ); +} + +bool SwDoc::SetTOXBaseName(const SwTOXBase& rTOXBase, const OUString& rName) +{ + OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, + "no TOXBaseSection!" ); + SwTOXBaseSection* pTOX = const_cast<SwTOXBaseSection*>(static_cast<const SwTOXBaseSection*>(&rTOXBase)); + + if (GetUniqueTOXBaseName(*rTOXBase.GetTOXType(), rName) == rName) + { + pTOX->SetTOXName(rName); + pTOX->SetSectionName(rName); + getIDocumentState().SetModified(); + return true; + } + return false; +} + +static const SwTextNode* lcl_FindChapterNode( const SwNode& rNd, + SwRootFrame const*const pLayout, sal_uInt8 const nLvl = 0 ) +{ + const SwNode* pNd = &rNd; + if( pNd->GetNodes().GetEndOfExtras().GetIndex() > pNd->GetIndex() ) + { + // then find the "Anchor" (Body) position + Point aPt; + SwNode2Layout aNode2Layout( *pNd, pNd->GetIndex() ); + const SwFrame* pFrame = aNode2Layout.GetFrame( &aPt ); + + if( pFrame ) + { + SwPosition aPos( *pNd ); + pNd = GetBodyTextNode( *pNd->GetDoc(), aPos, *pFrame ); + OSL_ENSURE( pNd, "Where's the paragraph?" ); + } + } + return pNd ? pNd->FindOutlineNodeOfLevel(nLvl, pLayout) : nullptr; +} + +// Table of contents class +SwTOXBaseSection::SwTOXBaseSection(SwTOXBase const& rBase, SwSectionFormat & rFormat) + : SwTOXBase( rBase ) + , SwSection( SectionType::ToxContent, OUString(), rFormat ) +{ + SetProtect( rBase.IsProtected() ); + SetSectionName( GetTOXName() ); +} + +SwTOXBaseSection::~SwTOXBaseSection() +{ +} + +bool SwTOXBaseSection::SetPosAtStartEnd( SwPosition& rPos ) const +{ + bool bRet = false; + const SwSectionNode* pSectNd = GetFormat()->GetSectionNode(); + if( pSectNd ) + { + rPos.nNode = *pSectNd; + SwContentNode* pCNd = pSectNd->GetDoc()->GetNodes().GoNext( &rPos.nNode ); + rPos.nContent.Assign( pCNd, 0 ); + bRet = true; + } + return bRet; +} + +/// Collect table of contents content +void SwTOXBaseSection::Update(const SfxItemSet* pAttr, + SwRootFrame const*const pLayout, + const bool _bNewTOX) +{ + if (!SwTOXBase::GetRegisteredIn()->HasWriterListeners() || + !GetFormat()) + { + return; + } + SwSectionNode const*const pSectNd(GetFormat()->GetSectionNode()); + if (nullptr == pSectNd || + !pSectNd->GetNodes().IsDocNodes() || + IsHiddenFlag() || + (pLayout->IsHideRedlines() && pSectNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)) + { + return; + } + + if ( !mbKeepExpression ) + { + maMSTOCExpression.clear(); + } + + SwDoc* pDoc = const_cast<SwDoc*>(pSectNd->GetDoc()); + + assert(pDoc); //Where is the document? + + if (pAttr && GetFormat()) + pDoc->ChgFormat(*GetFormat(), *pAttr); + + // determine default page description, which will be used by the content nodes, + // if no appropriate one is found. + const SwPageDesc* pDefaultPageDesc; + { + pDefaultPageDesc = + pSectNd->GetSection().GetFormat()->GetPageDesc().GetPageDesc(); + if ( !_bNewTOX && !pDefaultPageDesc ) + { + // determine page description of table-of-content + size_t nPgDescNdIdx = pSectNd->GetIndex() + 1; + size_t* pPgDescNdIdx = &nPgDescNdIdx; + pDefaultPageDesc = pSectNd->FindPageDesc( pPgDescNdIdx ); + if ( nPgDescNdIdx < pSectNd->GetIndex() ) + { + pDefaultPageDesc = nullptr; + } + } + // consider end node of content section in the node array. + if ( !pDefaultPageDesc && + ( pSectNd->EndOfSectionNode()->GetIndex() < + (pSectNd->GetNodes().GetEndOfContent().GetIndex() - 1) ) + ) + { + // determine page description of content after table-of-content + SwNodeIndex aIdx( *(pSectNd->EndOfSectionNode()) ); + const SwContentNode* pNdAfterTOX = pSectNd->GetNodes().GoNext( &aIdx ); + const SwAttrSet& aNdAttrSet = pNdAfterTOX->GetSwAttrSet(); + const SvxBreak eBreak = aNdAttrSet.GetBreak().GetBreak(); + if ( !( eBreak == SvxBreak::PageBefore || + eBreak == SvxBreak::PageBoth ) + ) + { + pDefaultPageDesc = pNdAfterTOX->FindPageDesc(); + } + } + // consider start node of content section in the node array. + if ( !pDefaultPageDesc && + ( pSectNd->GetIndex() > + (pSectNd->GetNodes().GetEndOfContent().StartOfSectionIndex() + 1) ) + ) + { + // determine page description of content before table-of-content + SwNodeIndex aIdx( *pSectNd ); + pDefaultPageDesc = + SwNodes::GoPrevious( &aIdx )->FindPageDesc(); + + } + if ( !pDefaultPageDesc ) + { + // determine default page description + pDefaultPageDesc = &pDoc->GetPageDesc( 0 ); + } + } + + pDoc->getIDocumentState().SetModified(); + + // get current Language + SwTOXInternational aIntl( GetLanguage(), + TOX_INDEX == GetTOXType()->GetType() ? + GetOptions() : SwTOIOptions::NONE, + GetSortAlgorithm() ); + + m_aSortArr.clear(); + + // find the first layout node for this TOX, if it only find the content + // in his own chapter + const SwTextNode* pOwnChapterNode = IsFromChapter() + ? ::lcl_FindChapterNode( *pSectNd, pLayout ) + : nullptr; + + SwNode2LayoutSaveUpperFrames aN2L(*pSectNd); + const_cast<SwSectionNode*>(pSectNd)->DelFrames(); + + // This would be a good time to update the Numbering + pDoc->UpdateNumRule(); + + if( GetCreateType() & SwTOXElement::Mark ) + UpdateMarks( aIntl, pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::OutlineLevel ) + UpdateOutline( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Template ) + UpdateTemplate( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Ole || + TOX_OBJECTS == SwTOXBase::GetType()) + UpdateContent( SwTOXElement::Ole, pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Table || + (TOX_TABLES == SwTOXBase::GetType() && IsFromObjectNames()) ) + UpdateTable( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Graphic || + (TOX_ILLUSTRATIONS == SwTOXBase::GetType() && IsFromObjectNames())) + UpdateContent( SwTOXElement::Graphic, pOwnChapterNode, pLayout ); + + if( !GetSequenceName().isEmpty() && !IsFromObjectNames() && + (TOX_TABLES == SwTOXBase::GetType() || + TOX_ILLUSTRATIONS == SwTOXBase::GetType() ) ) + UpdateSequence( pOwnChapterNode, pLayout ); + + if( GetCreateType() & SwTOXElement::Frame ) + UpdateContent( SwTOXElement::Frame, pOwnChapterNode, pLayout ); + + if(TOX_AUTHORITIES == SwTOXBase::GetType()) + UpdateAuthorities( aIntl, pLayout ); + + // Insert AlphaDelimiters if needed (just for keywords) + if( TOX_INDEX == SwTOXBase::GetType() && + ( GetOptions() & SwTOIOptions::AlphaDelimiter ) ) + InsertAlphaDelimiter( aIntl ); + + // remove old content an insert one empty textnode (to hold the layout!) + SwTextNode* pFirstEmptyNd; + + SwUndoUpdateIndex * pUndo(nullptr); + { + pDoc->getIDocumentRedlineAccess().DeleteRedline( *pSectNd, true, RedlineType::Any ); + + SwNodeIndex aSttIdx( *pSectNd, +1 ); + SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() ); + pFirstEmptyNd = pDoc->GetNodes().MakeTextNode( aEndIdx, + pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + + { + // Task 70995 - save and restore PageDesc and Break Attributes + SwNodeIndex aNxtIdx( aSttIdx ); + const SwContentNode* pCNd = aNxtIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pDoc->GetNodes().GoNext( &aNxtIdx ); + assert(pCNd != pFirstEmptyNd); + assert(pCNd->GetIndex() < pFirstEmptyNd->GetIndex()); + if( pCNd->HasSwAttrSet() ) + { + SfxItemSet aBrkSet( pDoc->GetAttrPool(), aBreakSetRange ); + aBrkSet.Put( *pCNd->GetpSwAttrSet() ); + if( aBrkSet.Count() ) + pFirstEmptyNd->SetAttr( aBrkSet ); + } + } + + if (pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + // note: this will first append a SwUndoDelSection from the ctor... + pUndo = new SwUndoUpdateIndex(*this); + // tdf#123313 insert Undo *after* all CrossRefBookmark Undos have + // been inserted by the Update*() functions + pDoc->GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndoUpdateIndex>(pUndo)); + } + else + { + --aEndIdx; + SwPosition aPos( aEndIdx, SwIndex( pFirstEmptyNd, 0 )); + SwDoc::CorrAbs( aSttIdx, aEndIdx, aPos, true ); + + // delete flys in whole range including start node which requires + // giving the node before start node as Mark parameter, hence -1. + // (flys must be deleted because the anchor nodes are removed) + DelFlyInRange( SwNodeIndex(aSttIdx, -1), aEndIdx ); + + pDoc->GetNodes().Delete( aSttIdx, aEndIdx.GetIndex() - aSttIdx.GetIndex() ); + } + } + + // insert title of TOX + if ( !GetTitle().isEmpty() ) + { + // then insert the headline section + SwNodeIndex aIdx( *pSectNd, +1 ); + + SwTextNode* pHeadNd = pDoc->GetNodes().MakeTextNode( aIdx, + GetTextFormatColl( FORM_TITLE ) ); + pHeadNd->InsertText( GetTitle(), SwIndex( pHeadNd ) ); + + SwSectionData headerData( SectionType::ToxHeader, GetTOXName()+"_Head" ); + + SwNodeIndex aStt( *pHeadNd ); --aIdx; + SwSectionFormat* pSectFormat = pDoc->MakeSectionFormat(); + pDoc->GetNodes().InsertTextSection( + aStt, *pSectFormat, headerData, nullptr, &aIdx, true, false); + + if (pUndo) + { + pUndo->TitleSectionInserted(*pSectFormat); + } + } + + // Sort the List of all TOC Marks and TOC Sections + std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr ); + SwNodeIndex aInsPos( *pFirstEmptyNd, 1 ); + for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + // Put the Text into the TOC + sal_uInt16 nLvl = m_aSortArr[ nCnt ]->GetLevel(); + SwTextFormatColl* pColl = aCollArr[ nLvl ]; + if( !pColl ) + { + pColl = GetTextFormatColl( nLvl ); + aCollArr[ nLvl ] = pColl; + } + + // Generate: Set dynamic TabStops + SwTextNode* pTOXNd = pDoc->GetNodes().MakeTextNode( aInsPos , pColl ); + m_aSortArr[ nCnt ]->pTOXNd = pTOXNd; + + // Generate: Evaluate Form and insert the place holder for the + // page number. If it is a TOX_INDEX and the SwForm IsCommaSeparated() + // then a range of entries must be generated into one paragraph + size_t nRange = 1; + if(TOX_INDEX == SwTOXBase::GetType() && + GetTOXForm().IsCommaSeparated() && + m_aSortArr[nCnt]->GetType() == TOX_SORT_INDEX) + { + const SwTOXMark& rMark = m_aSortArr[nCnt]->pTextMark->GetTOXMark(); + const OUString& sPrimKey = rMark.GetPrimaryKey(); + const OUString& sSecKey = rMark.GetSecondaryKey(); + const SwTOXMark* pNextMark = nullptr; + while(m_aSortArr.size() > (nCnt + nRange) && + m_aSortArr[nCnt + nRange]->GetType() == TOX_SORT_INDEX ) + { + pNextMark = &(m_aSortArr[nCnt + nRange]->pTextMark->GetTOXMark()); + if( !pNextMark || + pNextMark->GetPrimaryKey() != sPrimKey || + pNextMark->GetSecondaryKey() != sSecKey) + break; + nRange++; + } + } + // pass node index of table-of-content section and default page description + // to method <GenerateText(..)>. + ::SetProgressState( 0, pDoc->GetDocShell() ); + + std::shared_ptr<sw::ToxTabStopTokenHandler> tabStopTokenHandler = + std::make_shared<sw::DefaultToxTabStopTokenHandler>( + pSectNd->GetIndex(), *pDefaultPageDesc, GetTOXForm().IsRelTabPos(), + pDoc->GetDocumentSettingManager().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ? + sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_INDENT : + sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_PAGE); + sw::ToxTextGenerator ttgn(GetTOXForm(), tabStopTokenHandler); + ttgn.GenerateText(GetFormat()->GetDoc(), m_aSortArr, nCnt, nRange, pLayout); + nCnt += nRange - 1; + } + + // delete the first dummy node and remove all Cursor into the previous node + aInsPos = *pFirstEmptyNd; + { + SwPaM aCorPam( *pFirstEmptyNd ); + aCorPam.GetPoint()->nContent.Assign( pFirstEmptyNd, 0 ); + if( !aCorPam.Move( fnMoveForward ) ) + aCorPam.Move( fnMoveBackward ); + SwNodeIndex aEndIdx( aInsPos, 1 ); + SwDoc::CorrAbs( aInsPos, aEndIdx, *aCorPam.GetPoint(), true ); + + // Task 70995 - save and restore PageDesc and Break Attributes + if( pFirstEmptyNd->HasSwAttrSet() ) + { + if( !GetTitle().isEmpty() ) + aEndIdx = *pSectNd; + else + aEndIdx = *pFirstEmptyNd; + SwContentNode* pCNd = pDoc->GetNodes().GoNext( &aEndIdx ); + if( pCNd ) // Robust against defect documents, e.g. i60336 + pCNd->SetAttr( *pFirstEmptyNd->GetpSwAttrSet() ); + } + } + + // now create the new Frames + sal_uLong nIdx = pSectNd->GetIndex(); + // don't delete if index is empty + if(nIdx + 2 < pSectNd->EndOfSectionIndex()) + pDoc->GetNodes().Delete( aInsPos ); + + aN2L.RestoreUpperFrames( pDoc->GetNodes(), nIdx, nIdx + 1 ); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = pDoc->GetAllLayouts(); + for ( const auto& rpLayout : aAllLayouts ) + { + SwFrame::CheckPageDescs( static_cast<SwPageFrame*>(rpLayout->Lower()) ); + } + + SetProtect( SwTOXBase::IsProtected() ); +} + +void SwTOXBaseSection::InsertAlphaDelimiter( const SwTOXInternational& rIntl ) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + OUString sLastDeli; + size_t i = 0; + while( i < m_aSortArr.size() ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + sal_uInt16 nLevel = m_aSortArr[i]->GetLevel(); + + // Skip AlphaDelimiter + if( nLevel == FORM_ALPHA_DELIMITER ) + continue; + + const OUString sDeli = rIntl.GetIndexKey( m_aSortArr[i]->GetText(), + m_aSortArr[i]->GetLocale() ); + + // Do we already have a Delimiter? + if( !sDeli.isEmpty() && sLastDeli != sDeli ) + { + // We skip all that are less than a small Blank (these are special characters) + if( ' ' <= sDeli[0] ) + { + std::unique_ptr<SwTOXCustom> pCst( + MakeSwTOXSortTabBase<SwTOXCustom>(nullptr, + TextAndReading(sDeli, OUString()), + FORM_ALPHA_DELIMITER, + rIntl, m_aSortArr[i]->GetLocale() )); + m_aSortArr.insert( m_aSortArr.begin() + i, std::move(pCst)); + i++; + } + sLastDeli = sDeli; + } + + // Skip until we get to the same or a lower Level + do { + i++; + } while (i < m_aSortArr.size() && m_aSortArr[i]->GetLevel() > nLevel); + } +} + +/// Evaluate Template +SwTextFormatColl* SwTOXBaseSection::GetTextFormatColl( sal_uInt16 nLevel ) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + const OUString& rName = GetTOXForm().GetTemplate( nLevel ); + SwTextFormatColl* pColl = !rName.isEmpty() ? pDoc->FindTextFormatCollByName(rName) :nullptr; + if( !pColl ) + { + sal_uInt16 nPoolFormat = 0; + const TOXTypes eMyType = SwTOXBase::GetType(); + switch( eMyType ) + { + case TOX_INDEX: nPoolFormat = RES_POOLCOLL_TOX_IDXH; break; + case TOX_USER: + if( nLevel < 6 ) + nPoolFormat = RES_POOLCOLL_TOX_USERH; + else + nPoolFormat = RES_POOLCOLL_TOX_USER6 - 6; + break; + case TOX_ILLUSTRATIONS: nPoolFormat = RES_POOLCOLL_TOX_ILLUSH; break; + case TOX_OBJECTS: nPoolFormat = RES_POOLCOLL_TOX_OBJECTH; break; + case TOX_TABLES: nPoolFormat = RES_POOLCOLL_TOX_TABLESH; break; + case TOX_AUTHORITIES: + case TOX_BIBLIOGRAPHY: + nPoolFormat = RES_POOLCOLL_TOX_AUTHORITIESH; break; + case TOX_CITATION: /** TODO */break; + case TOX_CONTENT: + // There's a jump in the ContentArea! + if( nLevel < 6 ) + nPoolFormat = RES_POOLCOLL_TOX_CNTNTH; + else + nPoolFormat = RES_POOLCOLL_TOX_CNTNT6 - 6; + break; + } + + if(eMyType == TOX_AUTHORITIES && nLevel) + nPoolFormat = nPoolFormat + 1; + else if(eMyType == TOX_INDEX && nLevel) + { + // pool: Level 1,2,3, Delimiter + // SwForm: Delimiter, Level 1,2,3 + nPoolFormat += 1 == nLevel ? nLevel + 3 : nLevel - 1; + } + else + nPoolFormat = nPoolFormat + nLevel; + pColl = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolFormat ); + } + return pColl; +} + +/// Create from Marks +void SwTOXBaseSection::UpdateMarks( const SwTOXInternational& rIntl, + const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + const SwTOXType* pType = static_cast<SwTOXType*>( SwTOXBase::GetRegisteredIn() ); + if( !pType->HasWriterListeners() ) + return; + + SwDoc* pDoc = GetFormat()->GetDoc(); + TOXTypes eTOXTyp = GetTOXType()->GetType(); + SwIterator<SwTOXMark,SwTOXType> aIter( *pType ); + + for (SwTOXMark* pMark = aIter.First(); pMark; pMark = aIter.Next()) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (pMark->GetTOXType()->GetType() == eTOXTyp) + { + SwTextTOXMark *const pTextMark(pMark->GetTextTOXMark()); + if (nullptr == pTextMark) + continue; + const SwTextNode* pTOXSrc = pTextMark->GetpTextNd(); + // Only insert TOXMarks from the Doc, not from the + // UNDO. + + // If selected use marks from the same chapter only + if( pTOXSrc->GetNodes().IsDocNodes() && + pTOXSrc->GetText().getLength() && pTOXSrc->HasWriterListeners() && + pTOXSrc->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) && + (!IsFromChapter() || ::lcl_FindChapterNode(*pTOXSrc, pLayout) == pOwnChapterNode) && + !pTOXSrc->IsHiddenByParaField() && + !SwScriptInfo::IsInHiddenRange(*pTOXSrc, pTextMark->GetStart()) && + (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsMarkHintHidden(*pLayout, *pTOXSrc, *pTextMark))) + { + if(TOX_INDEX == eTOXTyp) + { + // index entry mark + assert(g_pBreakIt); + lang::Locale aLocale = g_pBreakIt->GetLocale(pTOXSrc->GetLang(pTextMark->GetStart())); + + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_ENTRY, rIntl, aLocale )); + if(GetOptions() & SwTOIOptions::KeyAsEntry && + !pTextMark->GetTOXMark().GetPrimaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_PRIMARY_KEY, rIntl, aLocale )); + if (!pTextMark->GetTOXMark().GetSecondaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, *pTOXSrc, pTextMark, + GetOptions(), FORM_SECONDARY_KEY, rIntl, aLocale )); + } + } + } + else if( TOX_USER == eTOXTyp || + pMark->GetLevel() <= GetLevel()) + { // table of content mark + // also used for user marks + InsertSorted(MakeSwTOXSortTabBase<SwTOXContent>(pLayout, *pTOXSrc, pTextMark, rIntl)); + } + } + } + } +} + +/// Generate table of contents from outline +void SwTOXBaseSection::UpdateOutline( const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); + for( auto pOutlineNode : rOutlNds ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + SwTextNode* pTextNd = pOutlineNode->GetTextNode(); + if( pTextNd && pTextNd->Len() && pTextNd->HasWriterListeners() && + o3tl::make_unsigned( pTextNd->GetAttrOutlineLevel()) <= GetLevel() && + pTextNd->getLayoutFrame(pLayout) && + !pTextNd->IsHiddenByParaField() && + !pTextNd->HasHiddenCharAttribute( true ) && + (!pLayout || !pLayout->IsHideRedlines() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + ( !IsFromChapter() || + ::lcl_FindChapterNode(*pTextNd, pLayout) == pOwnChapterNode )) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXPara>(pLayout, *pTextNd, SwTOXElement::OutlineLevel)); + } + } +} + +/// Generate table of contents from template areas +void SwTOXBaseSection::UpdateTemplate(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + for(sal_uInt16 i = 0; i < MAXLEVEL; i++) + { + const OUString sTmpStyleNames = GetStyleNames(i); + if (sTmpStyleNames.isEmpty()) + continue; + + sal_Int32 nIndex = 0; + while (nIndex >= 0) + { + SwTextFormatColl* pColl = pDoc->FindTextFormatCollByName( + sTmpStyleNames.getToken( 0, TOX_STYLE_DELIMITER, nIndex )); + //TODO: no outline Collections in content indexes if OutlineLevels are already included + if( !pColl || + ( TOX_CONTENT == SwTOXBase::GetType() && + GetCreateType() & SwTOXElement::OutlineLevel && + pColl->IsAssignedToListLevelOfOutlineStyle()) ) + continue; + + SwIterator<SwTextNode,SwFormatColl> aIter( *pColl ); + for( SwTextNode* pTextNd = aIter.First(); pTextNd; pTextNd = aIter.Next() ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (pTextNd->GetText().getLength() && + pTextNd->getLayoutFrame(pLayout) && + pTextNd->GetNodes().IsDocNodes() && + (!pLayout || !pLayout->IsHideRedlines() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + (!IsFromChapter() || pOwnChapterNode == + ::lcl_FindChapterNode(*pTextNd, pLayout))) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXPara>(pLayout, *pTextNd, SwTOXElement::Template, i + 1)); + } + } + } + } +} + +/// Generate content from sequence fields +void SwTOXBaseSection::UpdateSequence(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwFieldType* pSeqField = pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, GetSequenceName(), false); + if(!pSeqField) + return; + + std::vector<SwFormatField*> vFields; + pSeqField->GatherFields(vFields); + for(auto pFormatField: vFields) + { + const SwTextField* pTextField = pFormatField->GetTextField(); + SwTextNode& rTextNode = pTextField->GetTextNode(); + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (rTextNode.GetText().getLength() && + rTextNode.getLayoutFrame(pLayout) && + ( !IsFromChapter() || + ::lcl_FindChapterNode(rTextNode, pLayout) == pOwnChapterNode) + && (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(pDoc->getIDocumentRedlineAccess(), *pTextField))) + { + const SwSetExpField& rSeqField = dynamic_cast<const SwSetExpField&>(*(pFormatField->GetField())); + const OUString sName = GetSequenceName() + + OUStringChar(cSequenceMarkSeparator) + + OUString::number( rSeqField.GetSeqNumber() ); + std::unique_ptr<SwTOXPara> pNew(new SwTOXPara( rTextNode, SwTOXElement::Sequence, 1, sName )); + // set indexes if the number or the reference text are to be displayed + if( GetCaptionDisplay() == CAPTION_TEXT ) + { + pNew->SetStartIndex( + SwGetExpField::GetReferenceTextPos( *pFormatField, *pDoc )); + } + else if(GetCaptionDisplay() == CAPTION_NUMBER) + { + pNew->SetEndIndex(pTextField->GetStart() + 1); + } + pNew->InitText(pLayout); + InsertSorted(std::move(pNew)); + } + } +} + +void SwTOXBaseSection::UpdateAuthorities(const SwTOXInternational& rIntl, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwFieldType* pAuthField = pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::TableOfAuthorities, OUString(), false); + if(!pAuthField) + return; + + std::vector<SwFormatField*> vFields; + pAuthField->GatherFields(vFields); + for(auto pFormatField: vFields) + { + const auto pTextField = pFormatField->GetTextField(); + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + ::SetProgressState( 0, pDoc->GetDocShell() ); + + if (rTextNode.GetText().getLength() && + rTextNode.getLayoutFrame(pLayout) && + (!pLayout || !pLayout->IsHideRedlines() + || !sw::IsFieldDeletedInModel(pDoc->getIDocumentRedlineAccess(), *pTextField))) + { + //#106485# the body node has to be used! + SwContentFrame *const pFrame = rTextNode.getLayoutFrame(pLayout); + SwPosition aFieldPos(rTextNode); + const SwTextNode* pTextNode = nullptr; + if(pFrame && !pFrame->IsInDocBody()) + pTextNode = GetBodyTextNode( *pDoc, aFieldPos, *pFrame ); + if(!pTextNode) + pTextNode = &rTextNode; + + InsertSorted(MakeSwTOXSortTabBase<SwTOXAuthority>(pLayout, *pTextNode, *pFormatField, rIntl)); + } + } +} + +static SwTOOElements lcl_IsSOObject( const SvGlobalName& rFactoryNm ) +{ + static const struct SoObjType { + SwTOOElements nFlag; + // GlobalNameId + struct { + sal_uInt32 n1; + sal_uInt16 n2, n3; + sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15; + } aGlNmIds[4]; + } aArr[] = { + { SwTOOElements::Math, + { {SO3_SM_CLASSID_60},{SO3_SM_CLASSID_50}, + {SO3_SM_CLASSID_40},{SO3_SM_CLASSID_30} } }, + { SwTOOElements::Chart, + { {SO3_SCH_CLASSID_60},{SO3_SCH_CLASSID_50}, + {SO3_SCH_CLASSID_40},{SO3_SCH_CLASSID_30} } }, + { SwTOOElements::Calc, + { {SO3_SC_CLASSID_60},{SO3_SC_CLASSID_50}, + {SO3_SC_CLASSID_40},{SO3_SC_CLASSID_30} } }, + { SwTOOElements::DrawImpress, + { {SO3_SIMPRESS_CLASSID_60},{SO3_SIMPRESS_CLASSID_50}, + {SO3_SIMPRESS_CLASSID_40},{SO3_SIMPRESS_CLASSID_30} } }, + { SwTOOElements::DrawImpress, + { {SO3_SDRAW_CLASSID_60},{SO3_SDRAW_CLASSID_50} } } + }; + + for( SoObjType const & rArr : aArr ) + for (auto & rId : rArr.aGlNmIds) + { + if( !rId.n1 ) + break; + SvGlobalName aGlbNm( rId.n1, rId.n2, rId.n3, + rId.b8, rId.b9, rId.b10, rId.b11, + rId.b12, rId.b13, rId.b14, rId.b15 ); + if( rFactoryNm == aGlbNm ) + { + return rArr.nFlag; + } + } + + return SwTOOElements::NONE; +} + +void SwTOXBaseSection::UpdateContent( SwTOXElement eMyType, + const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + // on the 1st Node of the 1st Section + sal_uLong nIdx = rNds.GetEndOfAutotext().StartOfSectionIndex() + 2, + nEndIdx = rNds.GetEndOfAutotext().GetIndex(); + + while( nIdx < nEndIdx ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwNode* pNd = rNds[ nIdx ]; + SwContentNode* pCNd = nullptr; + switch( eMyType ) + { + case SwTOXElement::Frame: + if( !pNd->IsNoTextNode() ) + { + pCNd = pNd->GetContentNode(); + if( !pCNd ) + { + SwNodeIndex aTmp( *pNd ); + pCNd = rNds.GoNext( &aTmp ); + } + } + break; + case SwTOXElement::Graphic: + if( pNd->IsGrfNode() ) + pCNd = static_cast<SwContentNode*>(pNd); + break; + case SwTOXElement::Ole: + if( pNd->IsOLENode() ) + { + bool bInclude = true; + if(TOX_OBJECTS == SwTOXBase::GetType()) + { + SwOLENode* pOLENode = pNd->GetOLENode(); + SwTOOElements nMyOLEOptions = GetOLEOptions(); + SwOLEObj& rOLEObj = pOLENode->GetOLEObj(); + + if( rOLEObj.IsOleRef() ) // Not yet loaded + { + SvGlobalName aTmpName( rOLEObj.GetOleRef()->getClassID() ); + SwTOOElements nObj = ::lcl_IsSOObject( aTmpName ); + bInclude = ( (nMyOLEOptions & SwTOOElements::Other) && SwTOOElements::NONE == nObj ) + || (nMyOLEOptions & nObj); + } + else + { + OSL_FAIL("OLE Object no loaded?"); + bInclude = false; + } + } + + if(bInclude) + pCNd = static_cast<SwContentNode*>(pNd); + } + break; + default: break; + } + + if( pCNd ) + { + // find node in body text + int nSetLevel = USHRT_MAX; + + //#111105# tables of tables|illustrations|objects don't support hierarchies + if( IsLevelFromChapter() && + TOX_TABLES != SwTOXBase::GetType() && + TOX_ILLUSTRATIONS != SwTOXBase::GetType() && + TOX_OBJECTS != SwTOXBase::GetType() ) + { + const SwTextNode* pOutlNd = ::lcl_FindChapterNode( *pCNd, + pLayout, MAXLEVEL - 1); + if( pOutlNd ) + { + if( pOutlNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle()) + { + nSetLevel = pOutlNd->GetTextColl()->GetAttrOutlineLevel(); + } + } + } + + if (pCNd->getLayoutFrame(pLayout) + && (!pLayout || !pLayout->IsHideRedlines() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && ( !IsFromChapter() || + ::lcl_FindChapterNode(*pCNd, pLayout) == pOwnChapterNode )) + { + std::unique_ptr<SwTOXPara> pNew( MakeSwTOXSortTabBase<SwTOXPara>( + pLayout, *pCNd, eMyType, + ( USHRT_MAX != nSetLevel ) + ? static_cast<sal_uInt16>(nSetLevel) + : FORM_ALPHA_DELIMITER ) ); + InsertSorted( std::move(pNew) ); + } + } + + nIdx = pNd->StartOfSectionNode()->EndOfSectionIndex() + 2; // 2 == End/Start Node + } +} + +/// Collect table entries +void SwTOXBaseSection::UpdateTable(const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + SwDoc* pDoc = GetFormat()->GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + const SwFrameFormats& rArr = *pDoc->GetTableFrameFormats(); + + for( auto pFrameFormat : rArr ) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwTable* pTmpTable = SwTable::FindTable( pFrameFormat ); + SwTableBox* pFBox; + if( pTmpTable && nullptr != (pFBox = pTmpTable->GetTabSortBoxes()[0] ) && + pFBox->GetSttNd() && pFBox->GetSttNd()->GetNodes().IsDocNodes() ) + { + const SwTableNode* pTableNd = pFBox->GetSttNd()->FindTableNode(); + SwNodeIndex aContentIdx( *pTableNd, 1 ); + + SwContentNode* pCNd; + while( nullptr != ( pCNd = rNds.GoNext( &aContentIdx ) ) && + aContentIdx.GetIndex() < pTableNd->EndOfSectionIndex() ) + { + if (pCNd->getLayoutFrame(pLayout) + && (!pLayout || !pLayout->IsHideRedlines() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && (!IsFromChapter() + || ::lcl_FindChapterNode(*pCNd, pLayout) == pOwnChapterNode)) + { + std::unique_ptr<SwTOXTable> pNew(new SwTOXTable( *pCNd )); + if( IsLevelFromChapter() && TOX_TABLES != SwTOXBase::GetType()) + { + const SwTextNode* pOutlNd = + ::lcl_FindChapterNode(*pCNd, pLayout, MAXLEVEL - 1); + if( pOutlNd ) + { + if( pOutlNd->GetTextColl()->IsAssignedToListLevelOfOutlineStyle()) + { + const int nTmp = pOutlNd->GetTextColl()->GetAttrOutlineLevel(); + pNew->SetLevel(static_cast<sal_uInt16>(nTmp)); + } + } + } + pNew->InitText(pLayout); + InsertSorted(std::move(pNew)); + break; + } + } + } + } +} + +/// Calculate PageNumber and insert after formatting +void SwTOXBaseSection::UpdatePageNum() +{ + if( m_aSortArr.empty() ) + return ; + + // Insert the current PageNumber into the TOC + SwPageFrame* pCurrentPage = nullptr; + sal_uInt16 nPage = 0; + SwDoc* pDoc = GetFormat()->GetDoc(); + + SwTOXInternational aIntl( GetLanguage(), + TOX_INDEX == GetTOXType()->GetType() ? + GetOptions() : SwTOIOptions::NONE, + GetSortAlgorithm() ); + + for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) + { + // Loop over all SourceNodes + + // process run in lines + size_t nRange = 0; + if(GetTOXForm().IsCommaSeparated() && + m_aSortArr[nCnt]->GetType() == TOX_SORT_INDEX) + { + const SwTOXMark& rMark = m_aSortArr[nCnt]->pTextMark->GetTOXMark(); + const OUString& sPrimKey = rMark.GetPrimaryKey(); + const OUString& sSecKey = rMark.GetSecondaryKey(); + const SwTOXMark* pNextMark = nullptr; + while(m_aSortArr.size() > (nCnt + nRange)&& + m_aSortArr[nCnt + nRange]->GetType() == TOX_SORT_INDEX && + nullptr != (pNextMark = &(m_aSortArr[nCnt + nRange]->pTextMark->GetTOXMark())) && + pNextMark->GetPrimaryKey() == sPrimKey && + pNextMark->GetSecondaryKey() == sSecKey) + nRange++; + } + else + nRange = 1; + + for(size_t nRunInEntry = nCnt; nRunInEntry < nCnt + nRange; ++nRunInEntry) + { + std::vector<sal_uInt16> aNums; // the PageNumber + std::vector<SwPageDesc*> aDescs; // The PageDescriptors matching the PageNumbers + std::vector<sal_uInt16> aMainNums; // contains page numbers of main entries + SwTOXSortTabBase* pSortBase = m_aSortArr[nRunInEntry].get(); + size_t nSize = pSortBase->aTOXSources.size(); + for (size_t j = 0; j < nSize; ++j) + { + ::SetProgressState( 0, pDoc->GetDocShell() ); + + SwTOXSource& rTOXSource = pSortBase->aTOXSources[j]; + if( rTOXSource.pNd ) + { + SwContentFrame* pFrame = rTOXSource.pNd->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + OSL_ENSURE( pFrame || pDoc->IsUpdateTOX(), "TOX, no Frame found"); + if( !pFrame ) + continue; + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->HasFollow() ) + { + // find the right one + SwTextFrame* pNext; + TextFrameIndex const nPos(static_cast<SwTextFrame*>(pFrame) + ->MapModelToView(static_cast<SwTextNode const*>(rTOXSource.pNd), + rTOXSource.nPos)); + for (;;) + { + pNext = static_cast<SwTextFrame*>(pFrame->GetFollow()); + if (!pNext || nPos < pNext->GetOffset()) + break; + pFrame = pNext; + } + } + + SwPageFrame* pTmpPage = pFrame->FindPageFrame(); + if( pTmpPage != pCurrentPage ) + { + nPage = pTmpPage->GetVirtPageNum(); + pCurrentPage = pTmpPage; + } + + // Insert as sorted + std::vector<sal_uInt16>::size_type i; + for( i = 0; i < aNums.size() && aNums[i] < nPage; ++i ) + ; + + if( i >= aNums.size() || aNums[ i ] != nPage ) + { + aNums.insert(aNums.begin() + i, nPage); + aDescs.insert(aDescs.begin() + i, pCurrentPage->GetPageDesc() ); + } + // is it a main entry? + if(TOX_SORT_INDEX == pSortBase->GetType() && + rTOXSource.bMainEntry) + { + aMainNums.push_back(nPage); + } + } + } + // Insert the PageNumber into the TOC TextNode + const SwTOXSortTabBase* pBase = m_aSortArr[ nCnt ].get(); + if(pBase->pTOXNd) + { + const SwTextNode* pTextNd = pBase->pTOXNd->GetTextNode(); + OSL_ENSURE( pTextNd, "no TextNode, wrong TOC" ); + + UpdatePageNum_( const_cast<SwTextNode*>(pTextNd), aNums, aDescs, &aMainNums, + aIntl ); + } + } + } + // Delete the mapping array after setting the right PageNumber + m_aSortArr.clear(); +} + +/// Replace the PageNumber place holders. Search for the page no. in the array +/// of main entry page numbers. +static bool lcl_HasMainEntry( const std::vector<sal_uInt16>* pMainEntryNums, sal_uInt16 nToFind ) +{ + if (!pMainEntryNums) + return false; + + for( auto nMainEntry : *pMainEntryNums ) + if (nToFind == nMainEntry) + return true; + return false; +} + +void SwTOXBaseSection::UpdatePageNum_( SwTextNode* pNd, + const std::vector<sal_uInt16>& rNums, + const std::vector<SwPageDesc*>& rDescs, + const std::vector<sal_uInt16>* pMainEntryNums, + const SwTOXInternational& rIntl ) +{ + // collect starts end ends of main entry character style + std::unique_ptr< std::vector<sal_uInt16> > xCharStyleIdx(pMainEntryNums ? new std::vector<sal_uInt16> : nullptr); + + OUString sSrchStr = OUStringChar(C_NUM_REPL) + S_PAGE_DELI + OUStringChar(C_NUM_REPL); + sal_Int32 nStartPos = pNd->GetText().indexOf(sSrchStr); + sSrchStr = OUStringChar(C_NUM_REPL) + OUStringChar(C_END_PAGE_NUM); + sal_Int32 nEndPos = pNd->GetText().indexOf(sSrchStr); + + if (-1 == nEndPos || rNums.empty()) + return; + + if (-1 == nStartPos || nStartPos > nEndPos) + nStartPos = nEndPos; + + sal_uInt16 nOld = rNums[0], + nBeg = nOld, + nCount = 0; + OUString aNumStr( rDescs[0]->GetNumType().GetNumStr( nBeg ) ); + if( xCharStyleIdx && lcl_HasMainEntry( pMainEntryNums, nBeg )) + { + xCharStyleIdx->push_back( 0 ); + } + + // Delete place holder + SwIndex aPos(pNd, nStartPos); + SwCharFormat* pPageNoCharFormat = nullptr; + SwpHints* pHints = pNd->GetpSwpHints(); + if(pHints) + for(size_t nHintIdx = 0; nHintIdx < pHints->Count(); ++nHintIdx) + { + const SwTextAttr* pAttr = pHints->Get(nHintIdx); + const sal_Int32 nTmpEnd = pAttr->End() ? *pAttr->End() : 0; + if( nStartPos >= pAttr->GetStart() && + (nStartPos + 2) <= nTmpEnd && + pAttr->Which() == RES_TXTATR_CHARFMT) + { + pPageNoCharFormat = pAttr->GetCharFormat().GetCharFormat(); + break; + } + } + pNd->EraseText(aPos, nEndPos - nStartPos + 2); + + std::vector<sal_uInt16>::size_type i; + for( i = 1; i < rNums.size(); ++i) + { + SvxNumberType aType( rDescs[i]->GetNumType() ); + if( TOX_INDEX == SwTOXBase::GetType() ) + { // Summarize for the following + // Add up all following + // break up if main entry starts or ends and + // insert a char style index + bool bMainEntryChanges = lcl_HasMainEntry(pMainEntryNums, nOld) + != lcl_HasMainEntry(pMainEntryNums, rNums[i]); + + if(nOld == rNums[i]-1 && !bMainEntryChanges && + (GetOptions() & (SwTOIOptions::FF|SwTOIOptions::Dash))) + nCount++; + else + { + // Flush for the following old values + if(GetOptions() & SwTOIOptions::FF) + { + if ( nCount >= 1 ) + aNumStr += rIntl.GetFollowingText( nCount > 1 ); + } + else if (nCount) //#58127# If nCount == 0, then the only PageNumber is already in aNumStr! + { + if (nCount == 1 ) + aNumStr += S_PAGE_DELI; + else + aNumStr += "-"; + + aNumStr += aType.GetNumStr( nBeg + nCount ); + } + + // Create new String + nBeg = rNums[i]; + aNumStr += S_PAGE_DELI; + //the change of the character style must apply after sPageDeli is appended + if (xCharStyleIdx && bMainEntryChanges) + { + xCharStyleIdx->push_back(aNumStr.getLength()); + } + aNumStr += aType.GetNumStr( nBeg ); + nCount = 0; + } + nOld = rNums[i]; + } + else + { // Insert all Numbers + aNumStr += aType.GetNumStr( rNums[i] ); + if (i+1 != rNums.size()) + aNumStr += S_PAGE_DELI; + } + } + // Flush when ending and the following old values + if( TOX_INDEX == SwTOXBase::GetType() ) + { + if(GetOptions() & SwTOIOptions::FF) + { + if( nCount >= 1 ) + aNumStr += rIntl.GetFollowingText( nCount > 1 ); + } + else + { + if(nCount >= 2) + aNumStr += "-"; + else if(nCount == 1) + aNumStr += S_PAGE_DELI; + //#58127# If nCount == 0, then the only PageNumber is already in aNumStr! + if(nCount) + aNumStr += rDescs[i-1]->GetNumType().GetNumStr( nBeg+nCount ); + } + } + pNd->InsertText( aNumStr, aPos, SwInsertFlags::EMPTYEXPAND | SwInsertFlags::FORCEHINTEXPAND ); + if(pPageNoCharFormat) + { + SwFormatCharFormat aCharFormat( pPageNoCharFormat ); + pNd->InsertItem(aCharFormat, nStartPos, nStartPos + aNumStr.getLength(), SetAttrMode::DONTEXPAND); + } + + // The main entries should get their character style + if (xCharStyleIdx && !xCharStyleIdx->empty() && !GetMainEntryCharStyle().isEmpty()) + { + // eventually the last index must me appended + if (xCharStyleIdx->size()&0x01) + xCharStyleIdx->push_back(aNumStr.getLength()); + + // search by name + SwDoc* pDoc = pNd->GetDoc(); + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( GetMainEntryCharStyle(), SwGetPoolIdFromName::ChrFmt ); + SwCharFormat* pCharFormat = nullptr; + if(USHRT_MAX != nPoolId) + pCharFormat = pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(nPoolId); + else + pCharFormat = pDoc->FindCharFormatByName( GetMainEntryCharStyle() ); + if(!pCharFormat) + pCharFormat = pDoc->MakeCharFormat(GetMainEntryCharStyle(), nullptr); + + // find the page numbers in aNumStr and set the character style + sal_Int32 nOffset = pNd->GetText().getLength() - aNumStr.getLength(); + SwFormatCharFormat aCharFormat(pCharFormat); + for (size_t j = 0; j < xCharStyleIdx->size(); j += 2) + { + sal_Int32 nStartIdx = (*xCharStyleIdx)[j] + nOffset; + sal_Int32 nEndIdx = (*xCharStyleIdx)[j + 1] + nOffset; + pNd->InsertItem(aCharFormat, nStartIdx, nEndIdx, SetAttrMode::DONTEXPAND); + } + + } +} + +void SwTOXBaseSection::InsertSorted(std::unique_ptr<SwTOXSortTabBase> pNew) +{ + Range aRange(0, m_aSortArr.size()); + if( TOX_INDEX == SwTOXBase::GetType() && pNew->pTextMark ) + { + const SwTOXMark& rMark = pNew->pTextMark->GetTOXMark(); + // Evaluate Key + // Calculate the range where to insert + if( !(GetOptions() & SwTOIOptions::KeyAsEntry) && + !rMark.GetPrimaryKey().isEmpty() ) + { + aRange = GetKeyRange( rMark.GetPrimaryKey(), + rMark.GetPrimaryKeyReading(), + *pNew, FORM_PRIMARY_KEY, aRange ); + + if( !rMark.GetSecondaryKey().isEmpty() ) + aRange = GetKeyRange( rMark.GetSecondaryKey(), + rMark.GetSecondaryKeyReading(), + *pNew, FORM_SECONDARY_KEY, aRange ); + } + } + // Search for identical entries and remove the trailing one + if(TOX_AUTHORITIES == SwTOXBase::GetType()) + { + for(short i = static_cast<short>(aRange.Min()); i < static_cast<short>(aRange.Max()); ++i) + { + SwTOXSortTabBase* pOld = m_aSortArr[i].get(); + if (pOld->equivalent(*pNew)) + { + if (pOld->sort_lt(*pNew)) + { + return; + } + else + { + // remove the old content + m_aSortArr.erase( m_aSortArr.begin() + i ); + aRange.Max()--; + break; + } + } + } + } + + // find position and insert + long i; + + for( i = aRange.Min(); i < aRange.Max(); ++i) + { // Only check for same level + SwTOXSortTabBase* pOld = m_aSortArr[i].get(); + if (pOld->equivalent(*pNew)) + { + if(TOX_AUTHORITIES != SwTOXBase::GetType()) + { + // Own entry for double entries or keywords + if( pOld->GetType() == TOX_SORT_CUSTOM && + SwTOXSortTabBase::GetOptions() & SwTOIOptions::KeyAsEntry) + continue; + + if(!(SwTOXSortTabBase::GetOptions() & SwTOIOptions::SameEntry)) + { // Own entry + m_aSortArr.insert(m_aSortArr.begin() + i, std::move(pNew)); + return; + } + // If the own entry is already present, add it to the references list + pOld->aTOXSources.push_back(pNew->aTOXSources[0]); + + return; + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_FAIL("Bibliography entries cannot be found here"); +#endif + } + if (pNew->sort_lt(*pOld)) + break; + } + // Skip SubLevel + while( TOX_INDEX == SwTOXBase::GetType() && i < aRange.Max() && + m_aSortArr[i]->GetLevel() > pNew->GetLevel() ) + i++; + + // Insert at position i + m_aSortArr.insert(m_aSortArr.begin()+i, std::move(pNew)); +} + +/// Find Key Range and insert if possible +Range SwTOXBaseSection::GetKeyRange(const OUString& rStr, const OUString& rStrReading, + const SwTOXSortTabBase& rNew, + sal_uInt16 nLevel, const Range& rRange ) +{ + const SwTOXInternational& rIntl = *rNew.pTOXIntl; + TextAndReading aToCompare(rStr, rStrReading); + + if( SwTOIOptions::InitialCaps & GetOptions() ) + { + aToCompare.sText = rIntl.ToUpper( aToCompare.sText, 0 ) + + aToCompare.sText.copy(1); + } + + OSL_ENSURE(rRange.Min() >= 0 && rRange.Max() >= 0, "Min Max < 0"); + + const long nMin = rRange.Min(); + const long nMax = rRange.Max(); + + long i; + + for( i = nMin; i < nMax; ++i) + { + SwTOXSortTabBase* pBase = m_aSortArr[i].get(); + + if( rIntl.IsEqual( pBase->GetText(), pBase->GetLocale(), + aToCompare, rNew.GetLocale() ) && + pBase->GetLevel() == nLevel ) + break; + } + if(i == nMax) + { // If not already present, create and insert + std::unique_ptr<SwTOXCustom> pKey(MakeSwTOXSortTabBase<SwTOXCustom>( + nullptr, aToCompare, nLevel, rIntl, rNew.GetLocale() )); + for(i = nMin; i < nMax; ++i) + { + if (nLevel == m_aSortArr[i]->GetLevel() && pKey->sort_lt(*m_aSortArr[i])) + break; + } + m_aSortArr.insert(m_aSortArr.begin() + i, std::move(pKey)); + } + const long nStart = i+1; + const long nEnd = m_aSortArr.size(); + + // Find end of range + for(i = nStart; i < nEnd; ++i) + { + if(m_aSortArr[i]->GetLevel() <= nLevel) + { + return Range(nStart, i); + } + } + return Range(nStart, nEnd); +} + +bool SwTOXBase::IsTOXBaseInReadonly() const +{ + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if (!pSect || !pSect->GetFormat()) + return false; + + const SwSectionNode* pSectNode = pSect->GetFormat()->GetSectionNode(); + if (!pSectNode) + return false; + + const SwDocShell* pDocSh = pSectNode->GetDoc()->GetDocShell(); + if (!pDocSh) + return false; + + if (pDocSh->IsReadOnly()) + return true; + + pSectNode = pSectNode->StartOfSectionNode()->FindSectionNode(); + if (!pSectNode) + return false; + + return pSectNode->GetSection().IsProtectFlag(); +} + +const SfxItemSet* SwTOXBase::GetAttrSet() const +{ + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if(pSect && pSect->GetFormat()) + return &pSect->GetFormat()->GetAttrSet(); + return nullptr; +} + +void SwTOXBase::SetAttrSet( const SfxItemSet& rSet ) +{ + SwTOXBaseSection *pSect = dynamic_cast<SwTOXBaseSection*>(this); + if( pSect && pSect->GetFormat() ) + pSect->GetFormat()->SetFormatAttr( rSet ); +} + +bool SwTOXBase::GetInfo( SfxPoolItem& rInfo ) const +{ + switch( rInfo.Which() ) + { + case RES_CONTENT_VISIBLE: + { + const SwTOXBaseSection *pSect = dynamic_cast<const SwTOXBaseSection*>(this); + if( pSect && pSect->GetFormat() ) + pSect->GetFormat()->GetInfo( rInfo ); + } + return false; + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/docxforms.cxx b/sw/source/core/doc/docxforms.cxx new file mode 100644 index 000000000..ea827d58d --- /dev/null +++ b/sw/source/core/doc/docxforms.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <docsh.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/frame/XModule.hpp> +#include <com/sun/star/xforms/Model.hpp> +#include <com/sun/star/xforms/XModel2.hpp> +#include <com/sun/star/xforms/XFormsUIHelper1.hpp> +#include <com/sun/star/xforms/XForms.hpp> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/container/XIndexAccess.hpp> + +using namespace ::com::sun::star; + +using uno::Reference; +using uno::UNO_QUERY; +using uno::makeAny; +using uno::Exception; +using xforms::XModel2; +using frame::XModule; +using xforms::XFormsUIHelper1; +using com::sun::star::container::XIndexAccess; + + +bool SwDoc::isXForms() const +{ + return mxXForms.is(); +} + +void SwDoc::initXForms( bool bCreateDefaultModel ) +{ + OSL_ENSURE( ! isXForms(), "please initialize only once" ); + + try + { + // create XForms components + mxXForms = xforms::XForms::create( comphelper::getProcessComponentContext() ); + + // change our module identifier, to be able to have a dedicated UI + Reference< XModule > xModule; + SwDocShell* pShell( GetDocShell() ); + if ( pShell ) + xModule.set(pShell->GetModel(), css::uno::UNO_QUERY); + OSL_ENSURE( xModule.is(), "SwDoc::initXForms: no XModule at the document!" ); + if ( xModule.is() ) + xModule->setIdentifier( "com.sun.star.xforms.XMLFormDocument" ); + + // create default model + if( bCreateDefaultModel && mxXForms.is() ) + { + OUString sName("Model 1"); + Reference<XModel2> xModel = xforms::Model::create( comphelper::getProcessComponentContext() ); + xModel->setID( sName ); + Reference<XFormsUIHelper1>( xModel, uno::UNO_QUERY_THROW )->newInstance( + "Instance 1", + OUString(), true ); + xModel->initialize(); + mxXForms->insertByName( sName, makeAny( xModel ) ); + OSL_ENSURE( mxXForms->hasElements(), "can't create XForms model" ); + } + + OSL_ENSURE( isXForms(), "initialization failed" ); + } + catch( const Exception& ) + { + } +} + +// #i113606#, to release the cyclic reference between XFormModel and bindings/submissions. +void SwDoc::disposeXForms( ) +{ + // get XForms models + if( mxXForms.is() ) + { + // iterate over all models + const uno::Sequence<OUString> aNames = mxXForms->getElementNames(); + for( const OUString& rName : aNames ) + { + Reference< xforms::XModel > xModel( + mxXForms->getByName( rName ), UNO_QUERY ); + + if( xModel.is() ) + { + // ask model for bindings + Reference< XIndexAccess > xBindings( + xModel->getBindings(), UNO_QUERY ); + + // Then release them one by one + int nCount = xBindings->getCount(); + for( int i = nCount-1; i >= 0; i-- ) + { + xModel->getBindings()->remove(xBindings->getByIndex( i )); + } + + // ask model for Submissions + Reference< XIndexAccess > xSubmissions( + xModel->getSubmissions(), UNO_QUERY ); + + // Then release them one by one + nCount = xSubmissions->getCount(); + for( int i = nCount-1; i >= 0; i-- ) + { + xModel->getSubmissions()->remove(xSubmissions->getByIndex( i )); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/extinput.cxx b/sw/source/core/doc/extinput.cxx new file mode 100644 index 000000000..d9d22c25f --- /dev/null +++ b/sw/source/core/doc/extinput.cxx @@ -0,0 +1,304 @@ +/* -*- 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 <algorithm> + +#include <com/sun/star/i18n/ScriptType.hpp> + +#include <editeng/langitem.hxx> +#include <osl/diagnose.h> +#include <svl/languageoptions.hxx> +#include <vcl/commandevent.hxx> + +#include <hintids.hxx> +#include <extinput.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <index.hxx> +#include <ndtxt.hxx> +#include <swundo.hxx> + +using namespace ::com::sun::star; + +SwExtTextInput::SwExtTextInput( const SwPaM& rPam, Ring* pRing ) + : SwPaM( *rPam.GetPoint(), static_cast<SwPaM*>(pRing) ), + m_eInputLanguage(LANGUAGE_DONTKNOW) +{ + m_bIsOverwriteCursor = false; + m_bInsText = true; +} + +SwExtTextInput::~SwExtTextInput() +{ + SwDoc *const pDoc = GetDoc(); + if (pDoc->IsInDtor()) { return; /* #i58606# */ } + + SwTextNode* pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + SwIndex& rIdx = GetPoint()->nContent; + sal_Int32 nSttCnt = rIdx.GetIndex(); + sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + if( nEndCnt != nSttCnt ) + { + // Prevent IME edited text being grouped with non-IME edited text. + bool bKeepGroupUndo = pDoc->GetIDocumentUndoRedo().DoesGroupUndo(); + pDoc->GetIDocumentUndoRedo().DoGroupUndo(false); + if( nEndCnt < nSttCnt ) + { + std::swap(nSttCnt, nEndCnt); + } + + // In order to get Undo/Redlining etc. working correctly, + // we need to go through the Doc interface + rIdx = nSttCnt; + const OUString sText( pTNd->GetText().copy(nSttCnt, nEndCnt - nSttCnt)); + if( m_bIsOverwriteCursor && !m_sOverwriteText.isEmpty() ) + { + const sal_Int32 nLen = sText.getLength(); + const sal_Int32 nOWLen = m_sOverwriteText.getLength(); + if( nLen > nOWLen ) + { + rIdx += nOWLen; + pTNd->EraseText( rIdx, nLen - nOWLen ); + rIdx = nSttCnt; + pTNd->ReplaceText( rIdx, nOWLen, m_sOverwriteText ); + if( m_bInsText ) + { + rIdx = nSttCnt; + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::OVERWRITE, nullptr ); + pDoc->getIDocumentContentOperations().Overwrite( *this, sText.copy( 0, nOWLen ) ); + pDoc->getIDocumentContentOperations().InsertString( *this, sText.copy( nOWLen ) ); + pDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::OVERWRITE, nullptr ); + } + } + else + { + pTNd->ReplaceText( rIdx, nLen, m_sOverwriteText.copy( 0, nLen )); + if( m_bInsText ) + { + rIdx = nSttCnt; + pDoc->getIDocumentContentOperations().Overwrite( *this, sText ); + } + } + } + else + { + pTNd->EraseText( rIdx, nEndCnt - nSttCnt ); + + if( m_bInsText ) + { + pDoc->getIDocumentContentOperations().InsertString( *this, sText ); + } + } + pDoc->GetIDocumentUndoRedo().DoGroupUndo(bKeepGroupUndo); + if (m_eInputLanguage != LANGUAGE_DONTKNOW) + { + sal_uInt16 nWhich = RES_CHRATR_LANGUAGE; + sal_Int16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage(m_eInputLanguage); + switch(nScriptType) + { + case i18n::ScriptType::ASIAN: + nWhich = RES_CHRATR_CJK_LANGUAGE; break; + case i18n::ScriptType::COMPLEX: + nWhich = RES_CHRATR_CTL_LANGUAGE; break; + } + // #i41974# Only set language attribute for CJK/CTL scripts. + if (RES_CHRATR_LANGUAGE != nWhich && pTNd->GetLang( nSttCnt, nEndCnt-nSttCnt, nScriptType) != m_eInputLanguage) + { + SvxLanguageItem aLangItem( m_eInputLanguage, nWhich ); + rIdx = nSttCnt; + GetMark()->nContent = nEndCnt; + pDoc->getIDocumentContentOperations().InsertPoolItem(*this, aLangItem ); + } + + } + } + } +} + +void SwExtTextInput::SetInputData( const CommandExtTextInputData& rData ) +{ + SwTextNode* pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if( pTNd ) + { + sal_Int32 nSttCnt = GetPoint()->nContent.GetIndex(); + sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + if( nEndCnt < nSttCnt ) + { + std::swap(nSttCnt, nEndCnt); + } + + SwIndex aIdx( pTNd, nSttCnt ); + const OUString& rNewStr = rData.GetText(); + + if( m_bIsOverwriteCursor && !m_sOverwriteText.isEmpty() ) + { + sal_Int32 nReplace = nEndCnt - nSttCnt; + const sal_Int32 nNewLen = rNewStr.getLength(); + if( nNewLen < nReplace ) + { + // We have to insert some characters from the saved original text + nReplace -= nNewLen; + aIdx += nNewLen; + pTNd->ReplaceText( aIdx, nReplace, + m_sOverwriteText.copy( nNewLen, nReplace )); + aIdx = nSttCnt; + nReplace = nNewLen; + } + else + { + const sal_Int32 nOWLen = m_sOverwriteText.getLength(); + if( nOWLen < nReplace ) + { + aIdx += nOWLen; + pTNd->EraseText( aIdx, nReplace-nOWLen ); + aIdx = nSttCnt; + nReplace = nOWLen; + } + else + { + nReplace = std::min(nOWLen, nNewLen); + } + } + + pTNd->ReplaceText( aIdx, nReplace, rNewStr ); + if( !HasMark() ) + SetMark(); + GetMark()->nContent = aIdx; + } + else + { + if( nSttCnt < nEndCnt ) + { + pTNd->EraseText( aIdx, nEndCnt - nSttCnt ); + } + + pTNd->InsertText( rNewStr, aIdx, + SwInsertFlags::EMPTYEXPAND ); + if( !HasMark() ) + SetMark(); + } + + GetPoint()->nContent = nSttCnt; + + m_aAttrs.clear(); + if( rData.GetTextAttr() ) + { + const ExtTextInputAttr *pAttrs = rData.GetTextAttr(); + m_aAttrs.insert( m_aAttrs.begin(), pAttrs, pAttrs + rData.GetText().getLength() ); + } + } +} + +void SwExtTextInput::SetOverwriteCursor( bool bFlag ) +{ + m_bIsOverwriteCursor = bFlag; + if (!m_bIsOverwriteCursor) + return; + + const SwTextNode *const pTNd = GetPoint()->nNode.GetNode().GetTextNode(); + if (pTNd) + { + const sal_Int32 nSttCnt = GetPoint()->nContent.GetIndex(); + const sal_Int32 nEndCnt = GetMark()->nContent.GetIndex(); + m_sOverwriteText = pTNd->GetText().copy( std::min(nSttCnt, nEndCnt) ); + if( !m_sOverwriteText.isEmpty() ) + { + const sal_Int32 nInPos = m_sOverwriteText.indexOf( CH_TXTATR_INWORD ); + const sal_Int32 nBrkPos = m_sOverwriteText.indexOf( CH_TXTATR_BREAKWORD ); + + // Find the first attr found, if any. + sal_Int32 nPos = std::min(nInPos, nBrkPos); + if (nPos<0) + { + nPos = std::max(nInPos, nBrkPos); + } + if (nPos>=0) + { + m_sOverwriteText = m_sOverwriteText.copy( 0, nPos ); + } + } + } +} + +// The Doc interfaces + +SwExtTextInput* SwDoc::CreateExtTextInput( const SwPaM& rPam ) +{ + SwExtTextInput* pNew = new SwExtTextInput( rPam, mpExtInputRing ); + if( !mpExtInputRing ) + mpExtInputRing = pNew; + pNew->SetMark(); + return pNew; +} + +void SwDoc::DeleteExtTextInput( SwExtTextInput* pDel ) +{ + if( pDel == mpExtInputRing ) + { + if( pDel->GetNext() != mpExtInputRing ) + mpExtInputRing = pDel->GetNext(); + else + mpExtInputRing = nullptr; + } + delete pDel; +} + +SwExtTextInput* SwDoc::GetExtTextInput( const SwNode& rNd, + sal_Int32 nContentPos ) const +{ + SwExtTextInput* pRet = nullptr; + if( mpExtInputRing ) + { + sal_uLong nNdIdx = rNd.GetIndex(); + SwExtTextInput* pTmp = mpExtInputRing; + do { + sal_uLong nPt = pTmp->GetPoint()->nNode.GetIndex(), + nMk = pTmp->GetMark()->nNode.GetIndex(); + sal_Int32 nPtCnt = pTmp->GetPoint()->nContent.GetIndex(); + sal_Int32 nMkCnt = pTmp->GetMark()->nContent.GetIndex(); + + if( nPt < nMk || ( nPt == nMk && nPtCnt < nMkCnt )) + { + sal_uLong nTmp = nMk; nMk = nPt; nPt = nTmp; + sal_Int32 nTmp2 = nMkCnt; nMkCnt = nPtCnt; nPtCnt = nTmp2; + } + + if( nMk <= nNdIdx && nNdIdx <= nPt && + ( nContentPos<0 || + ( nMkCnt <= nContentPos && nContentPos <= nPtCnt ))) + { + pRet = pTmp; + break; + } + pTmp = pTmp->GetNext(); + } while ( pTmp!=mpExtInputRing ); + } + return pRet; +} + +SwExtTextInput* SwDoc::GetExtTextInput() const +{ + OSL_ENSURE( !mpExtInputRing || !mpExtInputRing->IsMultiSelection(), + "more than one InputEngine available" ); + return mpExtInputRing; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/fmtcol.cxx b/sw/source/core/doc/fmtcol.cxx new file mode 100644 index 000000000..ab39abf31 --- /dev/null +++ b/sw/source/core/doc/fmtcol.cxx @@ -0,0 +1,627 @@ +/* -*- 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 <memory> +#include <libxml/xmlwriter.h> + +#include <sal/macros.h> +#include <osl/diagnose.h> +#include <hintids.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <doc.hxx> +#include <fmtcol.hxx> +#include <fmtcolfunc.hxx> +#include <hints.hxx> +#include <node.hxx> +#include <numrule.hxx> +#include <paratr.hxx> +#include <calbck.hxx> +#include <svl/intitem.hxx> + +namespace TextFormatCollFunc +{ + // #i71574# + void CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( + SwFormat* pFormat, + const SwNumRuleItem* pNewNumRuleItem ) + { + SwTextFormatColl* pTextFormatColl = dynamic_cast<SwTextFormatColl*>(pFormat); + if ( !pTextFormatColl ) + { + OSL_FAIL( "<TextFormatCollFunc::CheckTextFormatCollFuncForDeletionOfAssignmentToOutlineStyle> - misuse of method - it's only for instances of <SwTextFormatColl>" ); + return; + } + + // #i73790# + if ( !pTextFormatColl->StayAssignedToListLevelOfOutlineStyle() && + pTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + if (!pNewNumRuleItem) + { + (void)pTextFormatColl->GetItemState(RES_PARATR_NUMRULE, false, reinterpret_cast<const SfxPoolItem**>(&pNewNumRuleItem)); + } + if (pNewNumRuleItem) + { + const OUString& sNumRuleName = pNewNumRuleItem->GetValue(); + if ( sNumRuleName.isEmpty() || + sNumRuleName != pTextFormatColl->GetDoc()->GetOutlineNumRule()->GetName() ) + { + // delete assignment of paragraph style to list level of outline style. + pTextFormatColl->DeleteAssignmentToListLevelOfOutlineStyle(); + } + } + } + } + + SwNumRule* GetNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule( nullptr ); + + const SwNumRuleItem* pNumRuleItem(nullptr); + (void)rTextFormatColl.GetItemState(RES_PARATR_NUMRULE, false, reinterpret_cast<const SfxPoolItem**>(&pNumRuleItem)); + if (pNumRuleItem) + { + const OUString& sNumRuleName = pNumRuleItem->GetValue(); + if ( !sNumRuleName.isEmpty() ) + { + pNumRule = rTextFormatColl.GetDoc()->FindNumRulePtr( sNumRuleName ); + } + } + + return pNumRule; + } + + void AddToNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule = GetNumRule( rTextFormatColl ); + if ( pNumRule ) + { + pNumRule->AddParagraphStyle( rTextFormatColl ); + } + } + + void RemoveFromNumRule( SwTextFormatColl& rTextFormatColl ) + { + SwNumRule* pNumRule = GetNumRule( rTextFormatColl ); + if ( pNumRule ) + { + pNumRule->RemoveParagraphStyle( rTextFormatColl ); + } + } +} // end of namespace TextFormatCollFunc + +void SwTextFormatColl::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + if( GetDoc()->IsInDtor() ) + { + SwFormatColl::Modify( pOld, pNew ); + return; + } + + bool bNewParent( false ); // #i66431# - adjust type of <bNewParent> + const SvxULSpaceItem *pNewULSpace = nullptr, *pOldULSpace = nullptr; + const SvxLRSpaceItem *pNewLRSpace = nullptr, *pOldLRSpace = nullptr; + const SvxFontHeightItem* aFontSizeArr[3] = {nullptr,nullptr,nullptr}; + // #i70223# + const bool bAssignedToListLevelOfOutlineStyle(IsAssignedToListLevelOfOutlineStyle()); + const SwNumRuleItem* pNewNumRuleItem( nullptr ); + + const SwAttrSetChg *pNewChgSet = nullptr, *pOldChgSet = nullptr; + + switch( pOld ? pOld->Which() : pNew ? pNew->Which() : 0 ) + { + case RES_ATTRSET_CHG: + // Only recalculate if we're not the sender! + pNewChgSet = static_cast<const SwAttrSetChg*>(pNew); + pOldChgSet = static_cast<const SwAttrSetChg*>(pOld); + pNewChgSet->GetChgSet()->GetItemState( + RES_LR_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pNewLRSpace) ); + pNewChgSet->GetChgSet()->GetItemState( + RES_UL_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pNewULSpace) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[0])) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_CJK_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[1])) ); + pNewChgSet->GetChgSet()->GetItemState( RES_CHRATR_CTL_FONTSIZE, + false, reinterpret_cast<const SfxPoolItem**>(&(aFontSizeArr[2])) ); + // #i70223#, #i84745# + // check, if attribute set is applied to this paragraph style + if ( bAssignedToListLevelOfOutlineStyle && + pNewChgSet->GetTheChgdSet() == &GetAttrSet() ) + { + pNewChgSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false, + reinterpret_cast<const SfxPoolItem**>(&pNewNumRuleItem) ); + } + + break; + + case RES_FMT_CHG: + if( GetAttrSet().GetParent() ) + { + const SfxItemSet* pParent = GetAttrSet().GetParent(); + pNewLRSpace = &pParent->Get( RES_LR_SPACE ); + pNewULSpace = &pParent->Get( RES_UL_SPACE ); + aFontSizeArr[0] = &pParent->Get( RES_CHRATR_FONTSIZE ); + aFontSizeArr[1] = &pParent->Get( RES_CHRATR_CJK_FONTSIZE ); + aFontSizeArr[2] = &pParent->Get( RES_CHRATR_CTL_FONTSIZE ); + // #i66431# - modify has to be propagated, because of new parent format. + bNewParent = true; + } + break; + + case RES_LR_SPACE: + pNewLRSpace = static_cast<const SvxLRSpaceItem*>(pNew); + break; + case RES_UL_SPACE: + pNewULSpace = static_cast<const SvxULSpaceItem*>(pNew); + break; + case RES_CHRATR_FONTSIZE: + aFontSizeArr[0] = static_cast<const SvxFontHeightItem*>(pNew); + break; + case RES_CHRATR_CJK_FONTSIZE: + aFontSizeArr[1] = static_cast<const SvxFontHeightItem*>(pNew); + break; + case RES_CHRATR_CTL_FONTSIZE: + aFontSizeArr[2] = static_cast<const SvxFontHeightItem*>(pNew); + break; + // #i70223# + case RES_PARATR_NUMRULE: + if (bAssignedToListLevelOfOutlineStyle) + { + pNewNumRuleItem = static_cast<const SwNumRuleItem*>(pNew); + } + break; + default: + break; + } + + // #i70223# + if ( bAssignedToListLevelOfOutlineStyle && pNewNumRuleItem ) + { + TextFormatCollFunc::CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( + this, pNewNumRuleItem ); + } + + bool bContinue = true; + + // Check against the own attributes + if( pNewLRSpace && SfxItemState::SET == GetItemState( RES_LR_SPACE, false, + reinterpret_cast<const SfxPoolItem**>(&pOldLRSpace) )) + { + if( pOldLRSpace != pNewLRSpace ) // Avoid recursion (SetAttr!) + { + bool bChg = false; + SvxLRSpaceItem aNew( *pOldLRSpace ); + // We had a relative value -> recalculate + if( 100 != aNew.GetPropLeft() ) + { + long nTmp = aNew.GetLeft(); // keep so that we can compare + aNew.SetLeft( pNewLRSpace->GetLeft(), aNew.GetPropLeft() ); + bChg |= nTmp != aNew.GetLeft(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropRight() ) + { + long nTmp = aNew.GetRight(); // keep so that we can compare + aNew.SetRight( pNewLRSpace->GetRight(), aNew.GetPropRight() ); + bChg |= nTmp != aNew.GetRight(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropTextFirstLineOffset() ) + { + short nTmp = aNew.GetTextFirstLineOffset(); // keep so that we can compare + aNew.SetTextFirstLineOffset( pNewLRSpace->GetTextFirstLineOffset(), + aNew.GetPropTextFirstLineOffset() ); + bChg |= nTmp != aNew.GetTextFirstLineOffset(); + } + if( bChg ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + } + + if( pNewULSpace && SfxItemState::SET == GetItemState( + RES_UL_SPACE, false, reinterpret_cast<const SfxPoolItem**>(&pOldULSpace) ) && + pOldULSpace != pNewULSpace ) // Avoid recursion (SetAttr!) + { + SvxULSpaceItem aNew( *pOldULSpace ); + bool bChg = false; + // We had a relative value -> recalculate + if( 100 != aNew.GetPropUpper() ) + { + sal_uInt16 nTmp = aNew.GetUpper(); // keep so that we can compare + aNew.SetUpper( pNewULSpace->GetUpper(), aNew.GetPropUpper() ); + bChg |= nTmp != aNew.GetUpper(); + } + // We had a relative value -> recalculate + if( 100 != aNew.GetPropLower() ) + { + sal_uInt16 nTmp = aNew.GetLower(); // keep so that we can compare + aNew.SetLower( pNewULSpace->GetLower(), aNew.GetPropLower() ); + bChg |= nTmp != aNew.GetLower(); + } + if( bChg ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + + for( int nC = 0; nC < int(SAL_N_ELEMENTS(aFontSizeArr)); ++nC ) + { + const SvxFontHeightItem *pFSize = aFontSizeArr[ nC ], *pOldFSize; + if( pFSize && SfxItemState::SET == GetItemState( + pFSize->Which(), false, reinterpret_cast<const SfxPoolItem**>(&pOldFSize) ) && + // Avoid recursion (SetAttr!) + pFSize != pOldFSize ) + { + if( 100 == pOldFSize->GetProp() && + MapUnit::MapRelative == pOldFSize->GetPropUnit() ) + { + // We set it to absolute -> do not propagate it further, unless + // we set it! + if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + else + { + // We had a relative value -> recalculate + sal_uInt32 nTmp = pOldFSize->GetHeight(); // keep so that we can compare + SvxFontHeightItem aNew(240 , 100, pFSize->Which()); + aNew.SetHeight( pFSize->GetHeight(), pOldFSize->GetProp(), + pOldFSize->GetPropUnit() ); + if( nTmp != aNew.GetHeight() ) + { + SetFormatAttr( aNew ); + bContinue = nullptr != pOldChgSet || bNewParent; + } + // We set it to absolute -> do not propagate it further, unless + // we set it! + else if( pNewChgSet ) + bContinue = pNewChgSet->GetTheChgdSet() == &GetAttrSet(); + } + } + } + + if( bContinue ) + SwFormatColl::Modify( pOld, pNew ); +} + +bool SwTextFormatColl::IsAtDocNodeSet() const +{ + SwIterator<SwContentNode,SwFormatColl> aIter( *this ); + const SwNodes& rNds = GetDoc()->GetNodes(); + for( SwContentNode* pNode = aIter.First(); pNode; pNode = aIter.Next() ) + if( &(pNode->GetNodes()) == &rNds ) + return true; + + return false; +} + +bool SwTextFormatColl::SetFormatAttr( const SfxPoolItem& rAttr ) +{ + const bool bIsNumRuleItem = rAttr.Which() == RES_PARATR_NUMRULE; + if ( bIsNumRuleItem ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::SetFormatAttr( rAttr ); + + if ( bIsNumRuleItem ) + { + TextFormatCollFunc::AddToNumRule( *this ); + } + + return bRet; +} + +bool SwTextFormatColl::SetFormatAttr( const SfxItemSet& rSet ) +{ + const bool bIsNumRuleItemAffected = + rSet.GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET; + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::SetFormatAttr( rSet ); + + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::AddToNumRule( *this ); + } + + return bRet; +} + +bool SwTextFormatColl::ResetFormatAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 ) +{ + const bool bIsNumRuleItemAffected = + ( nWhich2 != 0 && nWhich2 > nWhich1 ) + ? ( nWhich1 <= RES_PARATR_NUMRULE && + RES_PARATR_NUMRULE <= nWhich2 ) + : nWhich1 == RES_PARATR_NUMRULE; + if ( bIsNumRuleItemAffected ) + { + TextFormatCollFunc::RemoveFromNumRule( *this ); + } + + const bool bRet = SwFormatColl::ResetFormatAttr( nWhich1, nWhich2 ); + + return bRet; +} + +// #i73790# +sal_uInt16 SwTextFormatColl::ResetAllFormatAttr() +{ + const bool bOldState( mbStayAssignedToListLevelOfOutlineStyle ); + mbStayAssignedToListLevelOfOutlineStyle = true; + // #i70748# + // Outline level is no longer a member, it is an attribute now. + // Thus, it needs to be restored, if the paragraph style is assigned + // to the outline style + const int nAssignedOutlineStyleLevel = IsAssignedToListLevelOfOutlineStyle() + ? GetAssignedOutlineStyleLevel() + : -1; + + sal_uInt16 nRet = SwFormatColl::ResetAllFormatAttr(); + + // #i70748# + if ( nAssignedOutlineStyleLevel != -1 ) + { + AssignToListLevelOfOutlineStyle( nAssignedOutlineStyleLevel ); + } + + mbStayAssignedToListLevelOfOutlineStyle = bOldState; + + return nRet; +} + +bool SwTextFormatColl::AreListLevelIndentsApplicable() const +{ + bool bAreListLevelIndentsApplicable( true ); + + if ( GetItemState( RES_PARATR_NUMRULE ) != SfxItemState::SET ) + { + // no list style applied to paragraph style + bAreListLevelIndentsApplicable = false; + } + else if ( GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // paragraph style has hard-set indent attributes + bAreListLevelIndentsApplicable = false; + } + else if ( GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // list style is directly applied to paragraph style and paragraph + // style has no hard-set indent attributes + bAreListLevelIndentsApplicable = true; + } + else + { + // list style is applied through one of the parent paragraph styles and + // paragraph style has no hard-set indent attributes + + // check parent paragraph styles + const SwTextFormatColl* pColl = dynamic_cast<const SwTextFormatColl*>(DerivedFrom()); + while ( pColl ) + { + if ( pColl->GetAttrSet().GetItemState( RES_LR_SPACE, false ) == SfxItemState::SET ) + { + // indent attributes found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = false; + break; + } + + if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET ) + { + // paragraph style with the list style found and until now no + // indent attributes are found in the paragraph style hierarchy. + bAreListLevelIndentsApplicable = true; + break; + } + + pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom()); + OSL_ENSURE( pColl, + "<SwTextFormatColl::AreListLevelIndentsApplicable()> - something wrong in paragraph style hierarchy. The applied list style is not found." ); + } + } + + return bAreListLevelIndentsApplicable; +} + +void SwTextFormatColl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + GetAttrSet().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +void SwTextFormatColls::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColls")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +//FEATURE::CONDCOLL + +SwCollCondition::SwCollCondition( SwTextFormatColl* pColl, Master_CollCondition nMasterCond, + sal_uLong nSubCond ) + : SwClient( pColl ), m_nCondition( nMasterCond ), + m_nSubCondition( nSubCond ) +{ +} + +SwCollCondition::SwCollCondition( const SwCollCondition& rCopy ) + : SwClient( const_cast<SwModify*>(rCopy.GetRegisteredIn()) ), + m_nCondition( rCopy.m_nCondition ), + m_nSubCondition( rCopy.m_nSubCondition ) +{ +} + +SwCollCondition::~SwCollCondition() +{ +} + +void SwCollCondition::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +bool SwCollCondition::operator==( const SwCollCondition& rCmp ) const +{ + return ( m_nCondition == rCmp.m_nCondition ) + && ( m_nSubCondition == rCmp.m_nSubCondition ); +} + +void SwCollCondition::SetCondition( Master_CollCondition nCond, sal_uLong nSubCond ) +{ + m_nCondition = nCond; + m_nSubCondition = nSubCond; +} + +SwConditionTextFormatColl::~SwConditionTextFormatColl() +{ +} + +const SwCollCondition* SwConditionTextFormatColl::HasCondition( + const SwCollCondition& rCond ) const +{ + for (const auto &rpFnd : m_CondColls) + { + if (*rpFnd == rCond) + return rpFnd.get(); + } + + return nullptr; +} + +void SwConditionTextFormatColl::InsertCondition( const SwCollCondition& rCond ) +{ + for (SwFormatCollConditions::size_type n = 0; n < m_CondColls.size(); ++n) + { + if (*m_CondColls[ n ] == rCond) + { + m_CondColls.erase( m_CondColls.begin() + n ); + break; + } + } + + // Not found -> so insert it + m_CondColls.push_back( std::make_unique<SwCollCondition> (rCond) ); +} + +void SwConditionTextFormatColl::RemoveCondition( const SwCollCondition& rCond ) +{ + for (SwFormatCollConditions::size_type n = 0; n < m_CondColls.size(); ++n) + { + if (*m_CondColls[ n ] == rCond) + { + m_CondColls.erase( m_CondColls.begin() + n ); + } + } +} + +void SwConditionTextFormatColl::SetConditions( const SwFormatCollConditions& rCndClls ) +{ + // Copy the Conditions, but first delete the old ones + m_CondColls.clear(); + SwDoc& rDoc = *GetDoc(); + for (const auto &rpFnd : rCndClls) + { + SwTextFormatColl *const pTmpColl = rpFnd->GetTextFormatColl() + ? rDoc.CopyTextColl( *rpFnd->GetTextFormatColl() ) + : nullptr; + std::unique_ptr<SwCollCondition> pNew; + pNew.reset(new SwCollCondition( pTmpColl, rpFnd->GetCondition(), + rpFnd->GetSubCondition() )); + m_CondColls.push_back( std::move(pNew) ); + } +} + +// FEATURE::CONDCOLL +void SwTextFormatColl::SetAttrOutlineLevel( int nLevel) +{ + OSL_ENSURE( 0 <= nLevel && nLevel <= MAXLEVEL ,"SwTextFormatColl: Level Out Of Range" ); + SetFormatAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL, + static_cast<sal_uInt16>(nLevel) ) ); +} + +int SwTextFormatColl::GetAttrOutlineLevel() const +{ + return GetFormatAttr(RES_PARATR_OUTLINELEVEL).GetValue(); +} + +int SwTextFormatColl::GetAssignedOutlineStyleLevel() const +{ + OSL_ENSURE( IsAssignedToListLevelOfOutlineStyle(), + "<SwTextFormatColl::GetAssignedOutlineStyleLevel()> - misuse of method"); + return GetAttrOutlineLevel() - 1; +} + +void SwTextFormatColl::AssignToListLevelOfOutlineStyle(const int nAssignedListLevel) +{ + mbAssignedToOutlineStyle = true; + SetAttrOutlineLevel(nAssignedListLevel+1); + + // #i100277# + SwIterator<SwTextFormatColl,SwFormatColl> aIter( *this ); + SwTextFormatColl* pDerivedTextFormatColl = aIter.First(); + while ( pDerivedTextFormatColl != nullptr ) + { + if ( !pDerivedTextFormatColl->IsAssignedToListLevelOfOutlineStyle() ) + { + if ( pDerivedTextFormatColl->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::DEFAULT ) + { + SwNumRuleItem aItem; + pDerivedTextFormatColl->SetFormatAttr( aItem ); + } + if ( pDerivedTextFormatColl->GetItemState( RES_PARATR_OUTLINELEVEL, false ) == SfxItemState::DEFAULT ) + { + pDerivedTextFormatColl->SetAttrOutlineLevel( 0 ); + } + } + + pDerivedTextFormatColl = aIter.Next(); + } +} + +void SwTextFormatColl::DeleteAssignmentToListLevelOfOutlineStyle() +{ + mbAssignedToOutlineStyle = false; + ResetFormatAttr(RES_PARATR_OUTLINELEVEL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/ftnidx.cxx b/sw/source/core/doc/ftnidx.cxx new file mode 100644 index 000000000..07250b97e --- /dev/null +++ b/sw/source/core/doc/ftnidx.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <txtftn.hxx> +#include <fmtftn.hxx> +#include <ftninfo.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <ftnidx.hxx> +#include <ndtxt.hxx> +#include <ndindex.hxx> +#include <section.hxx> +#include <fmtftntx.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> + +namespace sw { + +bool IsFootnoteDeleted(IDocumentRedlineAccess const& rIDRA, + SwTextFootnote const& rTextFootnote) +{ + SwRedlineTable::size_type tmp; + SwPosition const pos(const_cast<SwTextNode&>(rTextFootnote.GetTextNode()), + rTextFootnote.GetStart()); + SwRangeRedline const*const pRedline(rIDRA.GetRedline(pos, &tmp)); + return (pRedline + && pRedline->GetType() == RedlineType::Delete + && *pRedline->GetPoint() != *pRedline->GetMark()); +} + +} + +using sw::IsFootnoteDeleted; + +bool CompareSwFootnoteIdxs::operator()(SwTextFootnote* const& lhs, SwTextFootnote* const& rhs) const +{ + sal_uLong nIdxLHS = SwTextFootnote_GetIndex( lhs ); + sal_uLong nIdxRHS = SwTextFootnote_GetIndex( rhs ); + return ( nIdxLHS == nIdxRHS && lhs->GetStart() < rhs->GetStart() ) || nIdxLHS < nIdxRHS; +} + +void SwFootnoteIdxs::UpdateFootnote( const SwNodeIndex& rStt ) +{ + if( empty() ) + return; + + // Get the NodesArray using the first foot note's StartIndex + SwDoc* pDoc = rStt.GetNode().GetDoc(); + if( pDoc->IsInReading() ) + return ; + SwTextFootnote* pTextFootnote; + + const SwEndNoteInfo& rEndInfo = pDoc->GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = pDoc->GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(pDoc->getIDocumentRedlineAccess()); + + // For normal foot notes we treat per-chapter and per-document numbering + // separately. For Endnotes we only have per-document numbering. + if( FTNNUM_CHAPTER == rFootnoteInfo.m_eNum ) + { + SwRootFrame const* pLayout(nullptr); + o3tl::sorted_vector<SwRootFrame*> layouts = pDoc->GetAllLayouts(); + // sw_redlinehide: here we need to know if there's *any* layout with + // IsHideRedlines(), because then the hidden-numbers have to be updated + for (SwRootFrame const* pTmp : layouts) + { + if (pTmp->IsHideRedlines()) + { + pLayout = pTmp; + } + } + + const SwOutlineNodes& rOutlNds = pDoc->GetNodes().GetOutLineNds(); + const SwNode *pChapterStartHidden(&pDoc->GetNodes().GetEndOfExtras()); + sal_uLong nChapterStart(pChapterStartHidden->GetIndex()); + sal_uLong nChapterEnd(pDoc->GetNodes().GetEndOfContent().GetIndex()); + sal_uLong nChapterEndHidden(nChapterEnd); + if( !rOutlNds.empty() ) + { + // Find the Chapter's start, which contains rStt + size_t n = 0; + + for( ; n < rOutlNds.size(); ++n ) + if( rOutlNds[ n ]->GetIndex() > rStt.GetIndex() ) + break; // found it! + else if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + nChapterStart = rOutlNds[ n ]->GetIndex(); + if (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[n]->GetTextNode())) + { + pChapterStartHidden = rOutlNds[ n ]; + } + } + // now find the end of the range + for( ; n < rOutlNds.size(); ++n ) + if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + nChapterEnd = rOutlNds[ n ]->GetIndex(); + break; + } + + // continue to find end of hidden-chapter + for ( ; n < rOutlNds.size(); ++n) + { + if (rOutlNds[n]->GetTextNode()->GetAttrOutlineLevel() == 1 + && (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[n]->GetTextNode()))) + { + nChapterEndHidden = rOutlNds[n]->GetIndex(); + break; + } + } + } + + size_t nPos = 0; + size_t nFootnoteNo = 1; + size_t nFootnoteNoHidden = 1; + if (SeekEntry( *pChapterStartHidden, &nPos ) && nPos) + { + // Step forward until the Index is not the same anymore + const SwNode* pCmpNd = &rStt.GetNode(); + while( nPos && pCmpNd == &((*this)[ --nPos ]->GetTextNode()) ) + ; + ++nPos; + } + + if( nPos == size() ) // nothing found + return; + + if( rOutlNds.empty() ) + { + nFootnoteNo = nPos+1; + if (nPos) + { + nFootnoteNoHidden = (*this)[nPos - 1]->GetFootnote().GetNumberRLHidden() + 1; + } + } + + for( ; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + sal_uLong const nNode(pTextFootnote->GetTextNode().GetIndex()); + if (nChapterEndHidden <= nNode) + break; + + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() && !rFootnote.IsEndNote() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + (nChapterStart <= nNode && nNode < nChapterEnd) + ? rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo + : rFootnote.GetNumber(), + rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden, + rFootnote.GetNumStr() ); + if (nChapterStart <= nNode && nNode < nChapterEnd) + { + ++nFootnoteNo; + } + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + } + + SwUpdFootnoteEndNtAtEnd aNumArr; + + // unless we have per-document numbering, only look at endnotes here + const bool bEndNoteOnly = FTNNUM_DOC != rFootnoteInfo.m_eNum; + + size_t nPos; + size_t nFootnoteNo = 1; + size_t nEndNo = 1; + size_t nFootnoteNoHidden = 1; + size_t nEndNoHidden = 1; + sal_uLong nUpdNdIdx = rStt.GetIndex(); + for( nPos = 0; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + if( nUpdNdIdx <= pTextFootnote->GetTextNode().GetIndex() ) + break; + + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + if (!aNumArr.ChkNumber(rIDRA, *pTextFootnote).first) + { + if( pTextFootnote->GetFootnote().IsEndNote() ) + { + nEndNo++; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndNoHidden; + } + } + else + { + nFootnoteNo++; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + } + } + + // Set the array number for all footnotes starting from nPos + for( ; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + std::pair<sal_uInt16, sal_uInt16> nSectNo = aNumArr.ChkNumber(rIDRA, *pTextFootnote); + if (!nSectNo.first && (rFootnote.IsEndNote() || !bEndNoteOnly)) + { + if (rFootnote.IsEndNote()) + { + nSectNo.first = rEndInfo.m_nFootnoteOffset + nEndNo; + ++nEndNo; + nSectNo.second = rEndInfo.m_nFootnoteOffset + nEndNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndNoHidden; + } + } + else + { + nSectNo.first = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo; + ++nFootnoteNo; + nSectNo.second = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + + if (nSectNo.first) + { + pTextFootnote->SetNumber(nSectNo.first, nSectNo.second, rFootnote.GetNumStr()); + } + } + } +} + +void SwFootnoteIdxs::UpdateAllFootnote() +{ + if( empty() ) + return; + + // Get the NodesArray via the StartIndex of the first Footnote + SwDoc* pDoc = const_cast<SwDoc*>((*this)[ 0 ]->GetTextNode().GetDoc()); + SwTextFootnote* pTextFootnote; + const SwEndNoteInfo& rEndInfo = pDoc->GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = pDoc->GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(pDoc->getIDocumentRedlineAccess()); + + SwUpdFootnoteEndNtAtEnd aNumArr; + + SwRootFrame const* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = pDoc->GetAllLayouts(); + // For normal Footnotes per-chapter and per-document numbering are treated separately. + // For Endnotes we only have document-wise numbering. + if( FTNNUM_CHAPTER == rFootnoteInfo.m_eNum ) + { + // sw_redlinehide: here we need to know if there's *any* layout with + // IsHideRedlines(), because then the hidden-numbers have to be updated + for (SwRootFrame const* pTmp : aAllLayouts) + { + if (pTmp->IsHideRedlines()) + { + pLayout = pTmp; + } + } + + const SwOutlineNodes& rOutlNds = pDoc->GetNodes().GetOutLineNds(); + sal_uInt16 nNo = 1; // Number for the Footnotes + sal_uInt16 nNoNo = 1; + size_t nFootnoteIdx = 0; // Index into theFootnoteIdx array + for( size_t n = 0; n < rOutlNds.size(); ++n ) + { + if ( rOutlNds[ n ]->GetTextNode()->GetAttrOutlineLevel() == 1 ) + { + sal_uLong nCapStt = rOutlNds[ n ]->GetIndex(); // Start of a new chapter + for( ; nFootnoteIdx < size(); ++nFootnoteIdx ) + { + pTextFootnote = (*this)[ nFootnoteIdx ]; + if( pTextFootnote->GetTextNode().GetIndex() >= nCapStt ) + break; + + // Endnotes are per-document only + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( !rFootnote.IsEndNote() && rFootnote.GetNumStr().isEmpty() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + rFootnoteInfo.m_nFootnoteOffset + nNo, + rFootnoteInfo.m_nFootnoteOffset + nNoNo, + rFootnote.GetNumStr() ); + ++nNo; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nNoNo; + } + } + } + if( nFootnoteIdx >= size() ) + break; // ok, everything is updated + nNo = 1; + // sw_redlinehide: this means the numbers are layout dependent in chapter case + if (!pLayout || sw::IsParaPropsNode(*pLayout, *rOutlNds[ n ]->GetTextNode())) + { + nNoNo = 1; + } + } + } + + for (nNo = 1, nNoNo = 1; nFootnoteIdx < size(); ++nFootnoteIdx) + { + // Endnotes are per-document + pTextFootnote = (*this)[ nFootnoteIdx ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( !rFootnote.IsEndNote() && rFootnote.GetNumStr().isEmpty() && + !SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *pTextFootnote )) + { + pTextFootnote->SetNumber( + rFootnoteInfo.m_nFootnoteOffset + nNo, + rFootnoteInfo.m_nFootnoteOffset + nNoNo, + rFootnote.GetNumStr() ); + ++nNo; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nNoNo; + } + } + } + } + + // We use bool here, so that we also iterate through the Endnotes with a chapter setting. + const bool bEndNoteOnly = FTNNUM_DOC != rFootnoteInfo.m_eNum; + sal_uInt16 nFootnoteNo = 1; + sal_uInt16 nEndnoteNo = 1; + sal_uInt16 nFootnoteNoHidden = 1; + sal_uInt16 nEndnoteNoHidden = 1; + for( size_t nPos = 0; nPos < size(); ++nPos ) + { + pTextFootnote = (*this)[ nPos ]; + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if( rFootnote.GetNumStr().isEmpty() ) + { + std::pair<sal_uInt16, sal_uInt16> nSectNo = aNumArr.ChkNumber(rIDRA, *pTextFootnote); + if (!nSectNo.first && (rFootnote.IsEndNote() || !bEndNoteOnly)) + { + if (rFootnote.IsEndNote()) + { + nSectNo.first = rEndInfo.m_nFootnoteOffset + nEndnoteNo; + ++nEndnoteNo; + nSectNo.second = rEndInfo.m_nFootnoteOffset + nEndnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nEndnoteNoHidden; + } + } + else + { + nSectNo.first = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNo; + ++nFootnoteNo; + nSectNo.second = rFootnoteInfo.m_nFootnoteOffset + nFootnoteNoHidden; + if (!IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + ++nFootnoteNoHidden; + } + } + } + + if (nSectNo.first) + { + pTextFootnote->SetNumber(nSectNo.first, nSectNo.second, rFootnote.GetNumStr()); + } + } + } + + if (pLayout && FTNNUM_PAGE == rFootnoteInfo.m_eNum) + for( auto aLayout : aAllLayouts ) + aLayout->UpdateFootnoteNums(); +} + +SwTextFootnote* SwFootnoteIdxs::SeekEntry( const SwNodeIndex& rPos, size_t* pFndPos ) const +{ + sal_uLong nIdx = rPos.GetIndex(); + + size_t nO = size(); + size_t nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const size_t nM = nU + ( nO - nU ) / 2; + sal_uLong nNdIdx = SwTextFootnote_GetIndex( (*this)[ nM ] ); + if( nNdIdx == nIdx ) + { + if( pFndPos ) + *pFndPos = nM; + return (*this)[ nM ]; + } + else if( nNdIdx < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pFndPos ) + *pFndPos = nU; + return nullptr; + } + else + nO = nM - 1; + } + } + if( pFndPos ) + *pFndPos = nU; + return nullptr; +} + +const SwSectionNode* SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( + const SwTextFootnote& rTextFootnote ) +{ + sal_uInt16 nWh = rTextFootnote.GetFootnote().IsEndNote() ? + sal_uInt16(RES_END_AT_TXTEND) : sal_uInt16(RES_FTN_AT_TXTEND); + const SwSectionNode* pNd = rTextFootnote.GetTextNode().FindSectionNode(); + while( pNd ) + { + sal_uInt16 nVal = static_cast<const SwFormatFootnoteEndAtTextEnd&>(pNd->GetSection().GetFormat()-> + GetFormatAttr( nWh )).GetValue(); + if( FTNEND_ATTXTEND_OWNNUMSEQ == nVal || FTNEND_ATTXTEND_OWNNUMANDFMT == nVal ) + break; + pNd = pNd->StartOfSectionNode()->FindSectionNode(); + } + + return pNd; +} + +std::pair<sal_uInt16, sal_uInt16> SwUpdFootnoteEndNtAtEnd::GetNumber( + IDocumentRedlineAccess const& rIDRA, + const SwTextFootnote& rTextFootnote, + const SwSectionNode& rNd ) +{ + std::pair<sal_uInt16, sal_uInt16> nRet(0, 0); + sal_uInt16 nWh; + std::vector<const SwSectionNode*>* pArr; + std::vector<std::pair<sal_uInt16, sal_uInt16>> *pNum; + if( rTextFootnote.GetFootnote().IsEndNote() ) + { + pArr = &m_aEndSections; + pNum = &m_aEndNumbers; + nWh = RES_END_AT_TXTEND; + } + else + { + pArr = &m_aFootnoteSections; + pNum = &m_aFootnoteNumbers; + nWh = RES_FTN_AT_TXTEND; + } + + for( size_t n = pArr->size(); n; ) + if( (*pArr)[ --n ] == &rNd ) + { + nRet.first = ++((*pNum)[ n ].first); + if (!IsFootnoteDeleted(rIDRA, rTextFootnote)) + { + ++((*pNum)[ n ].second); + } + nRet.second = ((*pNum)[ n ].second); + break; + } + + if (!nRet.first) + { + pArr->push_back( &rNd ); + sal_uInt16 const tmp = static_cast<const SwFormatFootnoteEndAtTextEnd&>( + rNd.GetSection().GetFormat()-> + GetFormatAttr( nWh )).GetOffset(); + nRet.first = tmp + 1; + nRet.second = tmp + 1; + pNum->push_back( nRet ); + } + return nRet; +} + +std::pair<sal_uInt16, sal_uInt16> SwUpdFootnoteEndNtAtEnd::ChkNumber( + IDocumentRedlineAccess const& rIDRA, + const SwTextFootnote& rTextFootnote) +{ + const SwSectionNode* pSectNd = FindSectNdWithEndAttr( rTextFootnote ); + return pSectNd + ? GetNumber(rIDRA, rTextFootnote, *pSectNd) + : std::pair<sal_uInt16, sal_uInt16>(0, 0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/gctable.cxx b/sw/source/core/doc/gctable.cxx new file mode 100644 index 000000000..8a7105c43 --- /dev/null +++ b/sw/source/core/doc/gctable.cxx @@ -0,0 +1,450 @@ +/* -*- 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 <tblrwcl.hxx> +#include <algorithm> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <osl/diagnose.h> + +using namespace ::editeng; + +static const SvxBorderLine* GetLineTB( const SvxBoxItem* pBox, bool bTop ) +{ + return bTop ? pBox->GetTop() : pBox->GetBottom(); +} + +bool SwGCBorder_BoxBrd::CheckLeftBorderOfFormat( const SwFrameFormat& rFormat ) +{ + const SfxPoolItem* pItem; + if( SfxItemState::SET == rFormat.GetItemState( RES_BOX, true, &pItem ) ) + { + const SvxBorderLine* pBrd = static_cast<const SvxBoxItem*>(pItem)->GetLeft(); + if( pBrd ) + { + if( *pBrdLn == *pBrd ) + bAnyBorderFnd = true; + return true; + } + } + return false; +} + +static bool lcl_GCBorder_ChkBoxBrd_B( const SwTableBox* pBox, SwGCBorder_BoxBrd* pPara ); + +static bool lcl_GCBorder_ChkBoxBrd_L( const SwTableLine* pLine, SwGCBorder_BoxBrd* pPara ) +{ + const SwTableBox* pBox = pLine->GetTabBoxes().front(); + return lcl_GCBorder_ChkBoxBrd_B( pBox, pPara ); +} + +static bool lcl_GCBorder_ChkBoxBrd_B( const SwTableBox* pBox, SwGCBorder_BoxBrd* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + for( auto pLine : pBox->GetTabLines() ) + { + if (!lcl_GCBorder_ChkBoxBrd_L( pLine, pPara )) + { + return false; + } + } + return true; + } + + return pPara->CheckLeftBorderOfFormat( *pBox->GetFrameFormat() ); +} + +static void lcl_GCBorder_GetLastBox_B( const SwTableBox* pBox, SwTableBoxes* pPara ); + +static void lcl_GCBorder_GetLastBox_L( const SwTableLine* pLine, SwTableBoxes* pPara ) +{ + const SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + SwTableBox* pBox = rBoxes.back(); + lcl_GCBorder_GetLastBox_B( pBox, pPara ); +} + +static void lcl_GCBorder_GetLastBox_B( const SwTableBox* pBox, SwTableBoxes* pPara ) +{ + const SwTableLines& rLines = pBox->GetTabLines(); + if( !rLines.empty() ) + { + for( const SwTableLine* pLine : rLines ) + lcl_GCBorder_GetLastBox_L( pLine, pPara ); + } + else + pPara->push_back( const_cast<SwTableBox*>(pBox) ); +} + +// Find the "end" of the passed BorderLine. Returns the "Layout"Pos! +static sal_uInt16 lcl_FindEndPosOfBorder( const SwCollectTableLineBoxes& rCollTLB, + const SvxBorderLine& rBrdLn, size_t& rStt, bool bTop ) +{ + sal_uInt16 nPos, nLastPos = 0; + for( size_t nEnd = rCollTLB.Count(); rStt < nEnd; ++rStt ) + { + const SfxPoolItem* pItem; + const SvxBorderLine* pBrd; + const SwTableBox& rBox = rCollTLB.GetBox( rStt, &nPos ); + + if( SfxItemState::SET != rBox.GetFrameFormat()->GetItemState(RES_BOX,true, &pItem ) ) + break; + pBrd = GetLineTB( static_cast<const SvxBoxItem*>(pItem), bTop ); + if( !pBrd || *pBrd != rBrdLn ) + break; + nLastPos = nPos; + } + return nLastPos; +} + +static const SvxBorderLine* lcl_GCBorder_GetBorder( const SwTableBox& rBox, + bool bTop, + const SfxPoolItem** ppItem ) +{ + return SfxItemState::SET == rBox.GetFrameFormat()->GetItemState( RES_BOX, true, ppItem ) + ? GetLineTB( static_cast<const SvxBoxItem*>(*ppItem), bTop ) + : nullptr; +} + +static void lcl_GCBorder_DelBorder( const SwCollectTableLineBoxes& rCollTLB, + size_t& rStt, bool bTop, + const SvxBorderLine& rLine, + const SfxPoolItem* pItem, + sal_uInt16 nEndPos, + SwShareBoxFormats* pShareFormats ) +{ + SwTableBox* pBox = const_cast<SwTableBox*>(&rCollTLB.GetBox( rStt )); + sal_uInt16 nNextPos; + const SvxBorderLine* pLn = &rLine; + + do { + if( pLn && *pLn == rLine ) + { + SvxBoxItem aBox( *static_cast<const SvxBoxItem*>(pItem) ); + if( bTop ) + aBox.SetLine( nullptr, SvxBoxItemLine::TOP ); + else + aBox.SetLine( nullptr, SvxBoxItemLine::BOTTOM ); + + if( pShareFormats ) + pShareFormats->SetAttr( *pBox, aBox ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aBox ); + } + + if( ++rStt >= rCollTLB.Count() ) + break; + + pBox = const_cast<SwTableBox*>(&rCollTLB.GetBox( rStt, &nNextPos )); + if( nNextPos > nEndPos ) + break; + + pLn = lcl_GCBorder_GetBorder( *pBox, bTop, &pItem ); + + } while( true ); +} + +static void lcl_GC_Box_Border( const SwTableBox* pBox, SwGCLineBorder* pPara ); + +void sw_GC_Line_Border( const SwTableLine* pLine, SwGCLineBorder* pGCPara ) +{ + // First the right edge with the left edge of the succeeding Box within this Line + { + SwGCBorder_BoxBrd aBPara; + const SvxBorderLine* pBrd; + const SfxPoolItem* pItem; + const SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( SwTableBoxes::size_type n = 0, nBoxes = rBoxes.size() - 1; n < nBoxes; ++n ) + { + SwTableBoxes aBoxes; + { + SwTableBox* pBox = rBoxes[ n ]; + if( pBox->GetSttNd() ) + aBoxes.insert( aBoxes.begin(), pBox ); + else + lcl_GCBorder_GetLastBox_B( pBox, &aBoxes ); + } + + for( SwTableBoxes::size_type i = aBoxes.size(); i; ) + { + SwTableBox* pBox = aBoxes[ --i ]; + if( SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( RES_BOX, true, &pItem ) ) + { + pBrd = static_cast<const SvxBoxItem*>(pItem)->GetRight(); + if( pBrd ) + { + aBPara.SetBorder( *pBrd ); + const SwTableBox* pNextBox = rBoxes[n+1]; + if( lcl_GCBorder_ChkBoxBrd_B( pNextBox, &aBPara ) && + aBPara.IsAnyBorderFound() ) + { + SvxBoxItem aBox( *static_cast<const SvxBoxItem*>(pItem) ); + aBox.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + if( pGCPara->pShareFormats ) + pGCPara->pShareFormats->SetAttr( *pBox, aBox ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aBox ); + } + } + } + } + + aBoxes.clear(); + } + } + + // And now the own bottom edge with the succeeding top edge + if( !pGCPara->IsLastLine() ) + { + SwCollectTableLineBoxes aBottom( false ); + SwCollectTableLineBoxes aTop( true ); + + sw_Line_CollectBox( pLine, &aBottom ); + + const SwTableLine* pNextLine = (*pGCPara->pLines)[ pGCPara->nLinePos+1 ]; + sw_Line_CollectBox( pNextLine, &aTop ); + + // remove all "duplicated" Lines that are the same + sal_uInt16 nBtmPos, nTopPos; + + size_t nSttBtm {0}; + size_t nSttTop {0}; + const size_t nEndBtm {aBottom.Count()}; + const size_t nEndTop {aTop.Count()}; + + const SwTableBox *pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + const SwTableBox *pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + const SfxPoolItem *pBtmItem = nullptr, *pTopItem = nullptr; + const SvxBorderLine *pBtmLine(nullptr), *pTopLine(nullptr); + bool bGetTopItem = true, bGetBtmItem = true; + + do { + if( bGetBtmItem ) + pBtmLine = lcl_GCBorder_GetBorder( *pBtmBox, false, &pBtmItem ); + if( bGetTopItem ) + pTopLine = lcl_GCBorder_GetBorder( *pTopBox, true, &pTopItem ); + + if( pTopLine && pBtmLine && *pTopLine == *pBtmLine ) + { + // We can remove one, but which one? + const size_t nSavSttBtm {nSttBtm}; + const size_t nSavSttTop {nSttTop}; + sal_uInt16 nBtmEndPos = ::lcl_FindEndPosOfBorder( aBottom, + *pTopLine, nSttBtm, false ); + if( !nBtmEndPos ) nBtmEndPos = nBtmPos; + sal_uInt16 nTopEndPos = ::lcl_FindEndPosOfBorder( aTop, + *pTopLine, nSttTop, true ); + if( !nTopEndPos ) nTopEndPos = nTopPos; + + if( nTopEndPos <= nBtmEndPos ) + { + // Delete the TopBorders until BottomEndPos + nSttTop = nSavSttTop; + if( nTopPos <= nBtmEndPos ) + lcl_GCBorder_DelBorder( aTop, --nSttTop, true, + *pBtmLine, pTopItem, nBtmEndPos, + pGCPara->pShareFormats ); + else + nSttBtm = nSavSttBtm; + } + else + { + // Else delete the BottomBorders until TopEndPos + nSttBtm = nSavSttBtm; + if( nBtmPos <= nTopEndPos ) + lcl_GCBorder_DelBorder( aBottom, --nSttBtm, false, + *pTopLine, pBtmItem, nTopEndPos, + pGCPara->pShareFormats ); + else + nSttTop = nSavSttTop; + } + nTopPos = nBtmPos; + } + + if( nTopPos == nBtmPos ) + { + if( nSttBtm >= nEndBtm || nSttTop >= nEndTop ) + break; + + pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + bGetTopItem = bGetBtmItem = true; + } + else if( nTopPos < nBtmPos ) + { + if( nSttTop >= nEndTop ) + break; + pTopBox = &aTop.GetBox( nSttTop++, &nTopPos ); + bGetTopItem = true; + bGetBtmItem = false; + } + else + { + if( nSttBtm >= nEndBtm ) + break; + pBtmBox = &aBottom.GetBox( nSttBtm++, &nBtmPos ); + bGetTopItem = false; + bGetBtmItem = true; + } + + } while( true ); + } + + for( const auto& rpBox : pLine->GetTabBoxes() ) + lcl_GC_Box_Border(rpBox, pGCPara ); + + ++pGCPara->nLinePos; +} + +static void lcl_GC_Box_Border( const SwTableBox* pBox, SwGCLineBorder* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + SwGCLineBorder aPara( *pBox ); + aPara.pShareFormats = pPara->pShareFormats; + for( const SwTableLine* pLine : pBox->GetTabLines() ) + sw_GC_Line_Border( pLine, &aPara ); + } +} + +namespace { + +struct GCLinePara +{ + SwTableLines* pLns; + SwShareBoxFormats* pShareFormats; + + GCLinePara( SwTableLines& rLns, GCLinePara* pPara = nullptr ) + : pLns( &rLns ), pShareFormats( pPara ? pPara->pShareFormats : nullptr ) + {} +}; + +} + +static bool lcl_MergeGCLine(SwTableLine* pLine, GCLinePara* pPara); + +static bool lcl_MergeGCBox(SwTableBox* pTableBox, GCLinePara* pPara) +{ + if( !pTableBox->GetTabLines().empty() ) + { + // ATTENTION: The Line count can change! + GCLinePara aPara( pTableBox->GetTabLines(), pPara ); + for( SwTableLines::size_type n = 0; + n < pTableBox->GetTabLines().size() && lcl_MergeGCLine( pTableBox->GetTabLines()[n], &aPara ); + ++n ) + ; + + if( 1 == pTableBox->GetTabLines().size() ) + { + // we have a box with a single line, so we just replace it by the line's boxes + SwTableLine* pInsLine = pTableBox->GetUpper(); + SwTableLine* pCpyLine = pTableBox->GetTabLines()[0]; + SwTableBoxes::iterator it = std::find( pInsLine->GetTabBoxes().begin(), pInsLine->GetTabBoxes().end(), pTableBox ); + for( auto pTabBox : pCpyLine->GetTabBoxes() ) + pTabBox->SetUpper( pInsLine ); + + // remove the old box from its parent line + it = pInsLine->GetTabBoxes().erase( it ); + // insert the nested line's boxes in its place + pInsLine->GetTabBoxes().insert( it, pCpyLine->GetTabBoxes().begin(), pCpyLine->GetTabBoxes().end()); + pCpyLine->GetTabBoxes().clear(); + // destroy the removed box + delete pTableBox; + + return false; // set up anew + } + } + return true; +} + +static bool lcl_MergeGCLine(SwTableLine* pLn, GCLinePara* pGCPara) +{ + SwTableBoxes::size_type nBoxes = pLn->GetTabBoxes().size(); + if( nBoxes ) + { + while( 1 == nBoxes ) + { + // We have a Box with Lines + SwTableBox* pBox = pLn->GetTabBoxes().front(); + if( pBox->GetTabLines().empty() ) + break; + + SwTableLine* pLine = pBox->GetTabLines()[0]; + + // pLine turns into the current Line (that is rpLine), the rest is moved + // into the LinesArray past the current one. + // The LinesArray is in pPara! + SwTableLines::size_type nLines = pBox->GetTabLines().size(); + + SwTableLines& rLns = *pGCPara->pLns; + sal_uInt16 nInsPos = rLns.GetPos( pLn ); + OSL_ENSURE( USHRT_MAX != nInsPos, "Could not find Line!" ); + + SwTableBox* pUpper = pLn->GetUpper(); + + rLns.erase( rLns.begin() + nInsPos ); // remove the Line from the array + rLns.insert( rLns.begin() + nInsPos, pBox->GetTabLines().begin(), pBox->GetTabLines().end() ); + + // JP 31.03.99: Bug 60000 + // Pass the attributes of the to-be-deleted Lines to the "inserted" one + const SfxPoolItem* pItem; + if( SfxItemState::SET == pLn->GetFrameFormat()->GetItemState( + RES_BACKGROUND, true, &pItem )) + { + SwTableLines& rBoxLns = pBox->GetTabLines(); + for( auto pBoxLine : rBoxLns ) + if( SfxItemState::SET != pBoxLine->GetFrameFormat()-> + GetItemState( RES_BACKGROUND )) + pGCPara->pShareFormats->SetAttr( *pBoxLine, *pItem ); + } + + pBox->GetTabLines().erase( pBox->GetTabLines().begin(), pBox->GetTabLines().begin() + nLines ); // Remove Lines from the array + + delete pLn; + + // Set the dependency anew + while( nLines-- ) + rLns[ nInsPos++ ]->SetUpper( pUpper ); + + pLn = pLine; // and set up anew + nBoxes = pLn->GetTabBoxes().size(); + } + + // ATTENTION: The number of boxes can change! + for( SwTableBoxes::size_type nLen = 0; nLen < pLn->GetTabBoxes().size(); ++nLen ) + if( !lcl_MergeGCBox( pLn->GetTabBoxes()[nLen], pGCPara )) + --nLen; + } + return true; +} + +// Clean structure a bit +void SwTable::GCLines() +{ + // ATTENTION: The Line count can change! + GCLinePara aPara( GetTabLines() ); + SwShareBoxFormats aShareFormats; + aPara.pShareFormats = &aShareFormats; + for( SwTableLines::size_type n = 0; n < GetTabLines().size() && + lcl_MergeGCLine( GetTabLines()[n], &aPara ); ++n ) + ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/htmltbl.cxx b/sw/source/core/doc/htmltbl.cxx new file mode 100644 index 000000000..f791e7d34 --- /dev/null +++ b/sw/source/core/doc/htmltbl.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 <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <memory> + +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <frmfmt.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <flyfrm.hxx> +#include <poolfmt.hxx> +#include <viewsh.hxx> +#include <tabfrm.hxx> +#include <viewopt.hxx> +#include <htmltbl.hxx> +#include <calbck.hxx> +#include <o3tl/numeric.hxx> +#ifdef DBG_UTIL +#include <tblrwcl.hxx> +#endif + +using namespace ::com::sun::star; + +#define COLFUZZY 20 +#define MAX_TABWIDTH (USHRT_MAX - 2001) + +namespace { + +class SwHTMLTableLayoutConstraints +{ + sal_uInt16 nRow; // start row + sal_uInt16 nCol; // start column + sal_uInt16 nColSpan; // the column's COLSPAN + + std::unique_ptr<SwHTMLTableLayoutConstraints> pNext; // the next constraint + + sal_uLong nMinNoAlign, nMaxNoAlign; // provisional result of AL-Pass 1 + +public: + SwHTMLTableLayoutConstraints( sal_uLong nMin, sal_uLong nMax, sal_uInt16 nRow, + sal_uInt16 nCol, sal_uInt16 nColSp ); + + sal_uLong GetMinNoAlign() const { return nMinNoAlign; } + sal_uLong GetMaxNoAlign() const { return nMaxNoAlign; } + + SwHTMLTableLayoutConstraints *InsertNext( SwHTMLTableLayoutConstraints *pNxt ); + SwHTMLTableLayoutConstraints* GetNext() const { return pNext.get(); } + + sal_uInt16 GetColSpan() const { return nColSpan; } + sal_uInt16 GetColumn() const { return nCol; } +}; + +} + +SwHTMLTableLayoutCnts::SwHTMLTableLayoutCnts(const SwStartNode *pSttNd, + std::shared_ptr<SwHTMLTableLayout> const& rTab, + bool bNoBrTag, + std::shared_ptr<SwHTMLTableLayoutCnts> const& rNxt ) : + xNext( rNxt ), pBox( nullptr ), xTable( rTab ), pStartNode( pSttNd ), + nPass1Done( 0 ), nWidthSet( 0 ), bNoBreakTag( bNoBrTag ) +{} + +const SwStartNode *SwHTMLTableLayoutCnts::GetStartNode() const +{ + return pBox ? pBox->GetSttNd() : pStartNode; +} + +SwHTMLTableLayoutCell::SwHTMLTableLayoutCell(std::shared_ptr<SwHTMLTableLayoutCnts> const& rCnts, + sal_uInt16 nRSpan, sal_uInt16 nCSpan, + sal_uInt16 nWidth, bool bPercentWidth, + bool bNWrapOpt ) : + xContents(rCnts), + nRowSpan( nRSpan ), nColSpan( nCSpan ), + nWidthOption( nWidth ), bPercentWidthOption( bPercentWidth ), + bNoWrapOption( bNWrapOpt ) +{} + +SwHTMLTableLayoutColumn::SwHTMLTableLayoutColumn( sal_uInt16 nWidth, + bool bRelWidth, + bool bLBorder ) : + nMinNoAlign(MINLAY), nMaxNoAlign(MINLAY), nAbsMinNoAlign(MINLAY), + nMin(0), nMax(0), + nAbsColWidth(0), nRelColWidth(0), + nWidthOption( nWidth ), bRelWidthOption( bRelWidth ), + bLeftBorder( bLBorder ) +{} + +SwHTMLTableLayoutConstraints::SwHTMLTableLayoutConstraints( + sal_uLong nMin, sal_uLong nMax, sal_uInt16 nRw, sal_uInt16 nColumn, sal_uInt16 nColSp ): + nRow( nRw ), nCol( nColumn ), nColSpan( nColSp ), + nMinNoAlign( nMin ), nMaxNoAlign( nMax ) +{} + +SwHTMLTableLayoutConstraints *SwHTMLTableLayoutConstraints::InsertNext( + SwHTMLTableLayoutConstraints *pNxt ) +{ + SwHTMLTableLayoutConstraints *pPrev = nullptr; + SwHTMLTableLayoutConstraints *pConstr = this; + while( pConstr ) + { + if( pConstr->nRow > pNxt->nRow || + pConstr->GetColumn() > pNxt->GetColumn() ) + break; + pPrev = pConstr; + pConstr = pConstr->GetNext(); + } + + if( pPrev ) + { + pNxt->pNext = std::move(pPrev->pNext); + pPrev->pNext.reset( pNxt ); + pConstr = this; + } + else + { + pNxt->pNext.reset( this ); + pConstr = pNxt; + } + + return pConstr; +} + +SwHTMLTableLayout::SwHTMLTableLayout( const SwTable * pTable, + sal_uInt16 nRws, sal_uInt16 nCls, + bool bColsOpt, bool bColTgs, + sal_uInt16 nWdth, bool bPercentWdth, + sal_uInt16 nBorderOpt, sal_uInt16 nCellPad, + sal_uInt16 nCellSp, SvxAdjust eAdjust, + sal_uInt16 nLMargin, sal_uInt16 nRMargin, + sal_uInt16 nBWidth, sal_uInt16 nLeftBWidth, + sal_uInt16 nRightBWidth ) + : m_aColumns( nCls ) + , m_aCells( static_cast<size_t>(nRws)*nCls ) + , m_pSwTable( pTable ) + , m_nMin( 0 ) + , m_nMax( 0 ) + , m_nRows( nRws ) + , m_nCols( nCls ) + , m_nLeftMargin( nLMargin ) + , m_nRightMargin( nRMargin ) + , m_nInhAbsLeftSpace( 0 ) + , m_nInhAbsRightSpace( 0 ) + , m_nRelLeftFill( 0 ) + , m_nRelRightFill( 0 ) + , m_nRelTabWidth( 0 ) + , m_nWidthOption( nWdth ) + , m_nCellPadding( nCellPad ) + , m_nCellSpacing( nCellSp ) + , m_nBorder( nBorderOpt ) + , m_nLeftBorderWidth( nLeftBWidth ) + , m_nRightBorderWidth( nRightBWidth ) + , m_nInhLeftBorderWidth( 0 ) + , m_nInhRightBorderWidth( 0 ) + , m_nBorderWidth( nBWidth ) + , m_nDelayedResizeAbsAvail( 0 ) + , m_nLastResizeAbsAvail( 0 ) + , m_nPass1Done( 0 ) + , m_nWidthSet( 0 ) + , m_eTableAdjust( eAdjust ) + , m_bColsOption( bColsOpt ) + , m_bColTags( bColTgs ) + , m_bPercentWidthOption( bPercentWdth ) + , m_bUseRelWidth( false ) + , m_bMustResize( true ) + , m_bExportable( true ) + , m_bBordersChanged( false ) + , m_bMayBeInFlyFrame( false ) + , m_bDelayedResizeRecalc( false) + , m_bMustNotResize( false ) + , m_bMustNotRecalc( false ) +{ + m_aResizeTimer.SetInvokeHandler( LINK( this, SwHTMLTableLayout, + DelayedResize_Impl ) ); +} + +SwHTMLTableLayout::~SwHTMLTableLayout() +{ +} + +/// The border widths are calculated like in Netscape: +/// Outer border: BORDER + CELLSPACING + CELLPADDING +/// Inner border: CELLSPACING + CELLPADDING +/// However, we respect the border widths in SW if bSwBorders is set, +/// so that we don't wrap wrongly. +/// We also need to respect the distance to the content. Even if +/// only the opposite side has a border. +sal_uInt16 SwHTMLTableLayout::GetLeftCellSpace( sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uInt16 nSpace = m_nCellSpacing + m_nCellPadding; + + if( nCol == 0 ) + { + nSpace = nSpace + m_nBorder; + + if( bSwBorders && nSpace < m_nLeftBorderWidth ) + nSpace = m_nLeftBorderWidth; + } + else if( bSwBorders ) + { + if( GetColumn(nCol)->HasLeftBorder() ) + { + if( nSpace < m_nBorderWidth ) + nSpace = m_nBorderWidth; + } + else if( nCol+nColSpan == m_nCols && m_nRightBorderWidth && + nSpace < MIN_BORDER_DIST ) + { + OSL_ENSURE( !m_nCellPadding, "GetLeftCellSpace: CELLPADDING!=0" ); + // If the opposite side has a border we need to respect at + // least the minimum distance to the content. + // Additionally, we could also use nCellPadding for this. + nSpace = MIN_BORDER_DIST; + } + } + + return nSpace; +} + +sal_uInt16 SwHTMLTableLayout::GetRightCellSpace( sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uInt16 nSpace = m_nCellPadding; + + if( nCol+nColSpan == m_nCols ) + { + nSpace += m_nBorder + m_nCellSpacing; + if( bSwBorders && nSpace < m_nRightBorderWidth ) + nSpace = m_nRightBorderWidth; + } + else if( bSwBorders && GetColumn(nCol)->HasLeftBorder() && + nSpace < MIN_BORDER_DIST ) + { + OSL_ENSURE( !m_nCellPadding, "GetRightCellSpace: CELLPADDING!=0" ); + // If the opposite side has a border we need to respect at + // least the minimum distance to the content. + // Additionally, we could also use nCellPadding for this. + nSpace = MIN_BORDER_DIST; + } + + return nSpace; +} + +void SwHTMLTableLayout::AddBorderWidth( sal_uLong &rMin, sal_uLong &rMax, + sal_uLong &rAbsMin, + sal_uInt16 nCol, sal_uInt16 nColSpan, + bool bSwBorders ) const +{ + sal_uLong nAdd = GetLeftCellSpace( nCol, nColSpan, bSwBorders ) + + GetRightCellSpace( nCol, nColSpan, bSwBorders ); + + rMin += nAdd; + rMax += nAdd; + rAbsMin += nAdd; +} + +void SwHTMLTableLayout::SetBoxWidth( SwTableBox *pBox, sal_uInt16 nCol, + sal_uInt16 nColSpan ) const +{ + SwFrameFormat *pFrameFormat = pBox->GetFrameFormat(); + + // calculate the box's width + SwTwips nFrameWidth = 0; + while( nColSpan-- ) + nFrameWidth += GetColumn( nCol++ )->GetRelColWidth(); + + // and reset + pFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nFrameWidth, 0 )); +} + +void SwHTMLTableLayout::GetAvail( sal_uInt16 nCol, sal_uInt16 nColSpan, + sal_uInt16& rAbsAvail, sal_uInt16& rRelAvail ) const +{ + rAbsAvail = 0; + rRelAvail = 0; + for( sal_uInt16 i=nCol; i<nCol+nColSpan;i++ ) + { + const SwHTMLTableLayoutColumn *pColumn = GetColumn(i); + rAbsAvail = rAbsAvail + pColumn->GetAbsColWidth(); + rRelAvail = rRelAvail + pColumn->GetRelColWidth(); + } +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByVisArea( const SwDoc& rDoc ) +{ + SwViewShell const *pVSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pVSh ) + { + return static_cast<sal_uInt16>(pVSh->GetBrowseWidth()); + } + + return 0; +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidth( const SwDoc& rDoc ) +{ + // If we have a layout, we can get the width from there. + const SwRootFrame *pRootFrame = rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRootFrame ) + { + const SwFrame *pPageFrame = pRootFrame->GetLower(); + if( pPageFrame ) + return static_cast<sal_uInt16>(pPageFrame->getFramePrintArea().Width()); + } + + // #i91658# + // Assertion removed which state that no browse width is available. + // Investigation reveals that all calls can handle the case that no browse + // width is provided. + return GetBrowseWidthByVisArea( rDoc ); +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByTabFrame( + const SwTabFrame& rTabFrame ) const +{ + SwTwips nWidth = 0; + + const SwFrame *pUpper = rTabFrame.GetUpper(); + if( MayBeInFlyFrame() && pUpper->IsFlyFrame() && + static_cast<const SwFlyFrame *>(pUpper)->GetAnchorFrame() ) + { + // If the table is located within a self-created frame, the anchor's + // width is relevant not the frame's width. + // For paragraph-bound frames we don't respect paragraph indents. + const SwFrame *pAnchor = static_cast<const SwFlyFrame *>(pUpper)->GetAnchorFrame(); + if( pAnchor->IsTextFrame() ) + nWidth = pAnchor->getFrameArea().Width(); + else + nWidth = pAnchor->getFramePrintArea().Width(); + } + else + { + nWidth = pUpper->getFramePrintArea().Width(); + } + + SwTwips nUpperDummy = 0; + long nRightOffset = 0, + nLeftOffset = 0; + rTabFrame.CalcFlyOffsets( nUpperDummy, nLeftOffset, nRightOffset ); + nWidth -= (nLeftOffset + nRightOffset); + + return static_cast<sal_uInt16>(std::min(nWidth, SwTwips(SAL_MAX_UINT16))); +} + +sal_uInt16 SwHTMLTableLayout::GetBrowseWidthByTable( const SwDoc& rDoc ) const +{ + sal_uInt16 nBrowseWidth = 0; + SwTabFrame* pFrame = SwIterator<SwTabFrame,SwFormat>( *m_pSwTable->GetFrameFormat() ).First(); + if( pFrame ) + { + nBrowseWidth = GetBrowseWidthByTabFrame( *pFrame ); + } + else + { + nBrowseWidth = SwHTMLTableLayout::GetBrowseWidth( rDoc ); + } + + return nBrowseWidth; +} + +const SwStartNode *SwHTMLTableLayout::GetAnyBoxStartNode() const +{ + const SwStartNode *pBoxSttNd; + + const SwTableBox* pBox = m_pSwTable->GetTabLines()[0]->GetTabBoxes()[0]; + while( nullptr == (pBoxSttNd = pBox->GetSttNd()) ) + { + OSL_ENSURE( !pBox->GetTabLines().empty(), + "Box without start node and lines" ); + OSL_ENSURE( !pBox->GetTabLines().front()->GetTabBoxes().empty(), + "Line without boxes" ); + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + } + + return pBoxSttNd; +} + +SwFrameFormat *SwHTMLTableLayout::FindFlyFrameFormat() const +{ + const SwTableNode *pTableNd = GetAnyBoxStartNode()->FindTableNode(); + OSL_ENSURE( pTableNd, "No Table-Node?" ); + return pTableNd->GetFlyFormat(); +} + +static void lcl_GetMinMaxSize( sal_uLong& rMinNoAlignCnts, sal_uLong& rMaxNoAlignCnts, + sal_uLong& rAbsMinNoAlignCnts, + SwTextNode const *pTextNd, sal_uLong nIdx, bool bNoBreak ) +{ + pTextNd->GetMinMaxSize( nIdx, rMinNoAlignCnts, rMaxNoAlignCnts, + rAbsMinNoAlignCnts ); + OSL_ENSURE( rAbsMinNoAlignCnts <= rMinNoAlignCnts, + "GetMinMaxSize: absmin > min" ); + OSL_ENSURE( rMinNoAlignCnts <= rMaxNoAlignCnts, + "GetMinMaxSize: max > min" ); + + // The maximal width for a <PRE> paragraph is the minimal width + const SwFormatColl *pColl = &pTextNd->GetAnyFormatColl(); + while( pColl && !pColl->IsDefault() && + (USER_FMT & pColl->GetPoolFormatId()) ) + { + pColl = static_cast<const SwFormatColl *>(pColl->DerivedFrom()); + } + + // <NOBR> in the whole cell apply to text but not to tables. + // Netscape only considers this for graphics. + if( (pColl && RES_POOLCOLL_HTML_PRE==pColl->GetPoolFormatId()) || bNoBreak ) + { + rMinNoAlignCnts = rMaxNoAlignCnts; + rAbsMinNoAlignCnts = rMaxNoAlignCnts; + } +} + +void SwHTMLTableLayout::AutoLayoutPass1() +{ + m_nPass1Done++; + + m_nMin = m_nMax = 0; // clear pass1 info + + bool bFixRelWidths = false; + sal_uInt16 i; + + std::unique_ptr<SwHTMLTableLayoutConstraints> xConstraints; + + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + pColumn->ClearPass1Info( !HasColTags() ); + sal_uInt16 nMinColSpan = USHRT_MAX; // Column count to which the calculated width refers to + sal_uInt16 nColSkip = USHRT_MAX; // How many columns need to be skipped + + for( sal_uInt16 j=0; j<m_nRows; j++ ) + { + SwHTMLTableLayoutCell *pCell = GetCell(j,i); + SwHTMLTableLayoutCnts *pCnts = pCell->GetContents().get(); + + // We need to examine all rows in order to + // get the column that should be calculated next. + sal_uInt16 nColSpan = pCell->GetColSpan(); + if( nColSpan < nColSkip ) + nColSkip = nColSpan; + + if( !pCnts || !pCnts->IsPass1Done(m_nPass1Done) ) + { + // The cell is empty or it's content was not edited + if( nColSpan < nMinColSpan ) + nMinColSpan = nColSpan; + + sal_uLong nMinNoAlignCell = 0; + sal_uLong nMaxNoAlignCell = 0; + sal_uLong nAbsMinNoAlignCell = 0; + sal_uLong nMaxTableCell = 0; + sal_uLong nAbsMinTableCell = 0; + + while( pCnts ) + { + const SwStartNode *pSttNd = pCnts->GetStartNode(); + if( pSttNd ) + { + const SwDoc *pDoc = pSttNd->GetDoc(); + sal_uLong nIdx = pSttNd->GetIndex(); + while( !(pDoc->GetNodes()[nIdx])->IsEndNode() ) + { + SwTextNode *pTextNd = (pDoc->GetNodes()[nIdx])->GetTextNode(); + if( pTextNd ) + { + sal_uLong nMinNoAlignCnts = 0; + sal_uLong nMaxNoAlignCnts = 0; + sal_uLong nAbsMinNoAlignCnts = 0; + + lcl_GetMinMaxSize( nMinNoAlignCnts, + nMaxNoAlignCnts, + nAbsMinNoAlignCnts, + pTextNd, nIdx, + pCnts->HasNoBreakTag() ); + + if( nMinNoAlignCnts > nMinNoAlignCell ) + nMinNoAlignCell = nMinNoAlignCnts; + if( nMaxNoAlignCnts > nMaxNoAlignCell ) + nMaxNoAlignCell = nMaxNoAlignCnts; + if( nAbsMinNoAlignCnts > nAbsMinNoAlignCell ) + nAbsMinNoAlignCell = nAbsMinNoAlignCnts; + } + else + { + SwTableNode *pTabNd = (pDoc->GetNodes()[nIdx])->GetTableNode(); + if( pTabNd ) + { + SwHTMLTableLayout *pChild = pTabNd->GetTable().GetHTMLTableLayout(); + if( pChild ) + { + pChild->AutoLayoutPass1(); + sal_uLong nMaxTableCnts = pChild->m_nMax; + sal_uLong nAbsMinTableCnts = pChild->m_nMin; + + // A fixed table width is taken over as minimum and + // maximum at the same time + if( !pChild->m_bPercentWidthOption && pChild->m_nWidthOption ) + { + sal_uLong nTabWidth = pChild->m_nWidthOption; + if( nTabWidth >= nAbsMinTableCnts ) + { + nMaxTableCnts = nTabWidth; + nAbsMinTableCnts = nTabWidth; + } + else + { + nMaxTableCnts = nAbsMinTableCnts; + } + } + + if( nMaxTableCnts > nMaxTableCell ) + nMaxTableCell = nMaxTableCnts; + if( nAbsMinTableCnts > nAbsMinTableCell ) + nAbsMinTableCell = nAbsMinTableCnts; + } + nIdx = pTabNd->EndOfSectionNode()->GetIndex(); + } + } + nIdx++; + } + } + else if (SwHTMLTableLayout *pChild = pCnts->GetTable()) + { + OSL_ENSURE( false, "Sub tables in HTML import?" ); + pChild->AutoLayoutPass1(); + sal_uLong nMaxTableCnts = pChild->m_nMax; + sal_uLong nAbsMinTableCnts = pChild->m_nMin; + + // A fixed table width is taken over as minimum and + // maximum at the same time + if( !pChild->m_bPercentWidthOption && pChild->m_nWidthOption ) + { + sal_uLong nTabWidth = pChild->m_nWidthOption; + if( nTabWidth >= nAbsMinTableCnts ) + { + nMaxTableCnts = nTabWidth; + nAbsMinTableCnts = nTabWidth; + } + else + { + nMaxTableCnts = nAbsMinTableCnts; + } + } + + if( nMaxTableCnts > nMaxTableCell ) + nMaxTableCell = nMaxTableCnts; + if( nAbsMinTableCnts > nAbsMinTableCell ) + nAbsMinTableCell = nAbsMinTableCnts; + } + pCnts->SetPass1Done( m_nPass1Done ); + pCnts = pCnts->GetNext().get(); + } + +// This code previously came after AddBorderWidth + // If a table's width is wider in a cell than what we've calculated + // for the other content we need to use the table's width. + if( nMaxTableCell > nMaxNoAlignCell ) + nMaxNoAlignCell = nMaxTableCell; + if( nAbsMinTableCell > nAbsMinNoAlignCell ) + { + nAbsMinNoAlignCell = nAbsMinTableCell; + if( nMinNoAlignCell < nAbsMinNoAlignCell ) + nMinNoAlignCell = nAbsMinNoAlignCell; + if( nMaxNoAlignCell < nMinNoAlignCell ) + nMaxNoAlignCell = nMinNoAlignCell; + } +// This code previously came after AddBorderWidth + + bool bRelWidth = pCell->IsPercentWidthOption(); + sal_uInt16 nWidth = pCell->GetWidthOption(); + + // A NOWRAP option applies to text and tables, but is + // not applied for fixed cell width. + // Instead, the stated cell width behaves like a minimal + // width. + if( pCell->HasNoWrapOption() ) + { + if( nWidth==0 || bRelWidth ) + { + nMinNoAlignCell = nMaxNoAlignCell; + nAbsMinNoAlignCell = nMaxNoAlignCell; + } + else + { + if( nWidth>nMinNoAlignCell ) + nMinNoAlignCell = nWidth; + if( nWidth>nAbsMinNoAlignCell ) + nAbsMinNoAlignCell = nWidth; + } + } + + // Respect minimum width for content + if( nMinNoAlignCell < MINLAY ) + nMinNoAlignCell = MINLAY; + if( nMaxNoAlignCell < MINLAY ) + nMaxNoAlignCell = MINLAY; + if( nAbsMinNoAlignCell < MINLAY ) + nAbsMinNoAlignCell = MINLAY; + + // Respect the border and distance to the content + AddBorderWidth( nMinNoAlignCell, nMaxNoAlignCell, + nAbsMinNoAlignCell, i, nColSpan ); + + if( 1==nColSpan ) + { + // take over the values directly + pColumn->MergeMinMaxNoAlign( nMinNoAlignCell, + nMaxNoAlignCell, + nAbsMinNoAlignCell ); + + // the widest WIDTH wins + if( !HasColTags() ) + pColumn->MergeCellWidthOption( nWidth, bRelWidth ); + } + else + { + // Process the data line by line from left to right at the end + + // When which values is taken over will be explained further down. + if( !HasColTags() && nWidth && !bRelWidth ) + { + sal_uLong nAbsWidth = nWidth, nDummy = 0, nDummy2 = 0; + AddBorderWidth( nAbsWidth, nDummy, nDummy2, + i, nColSpan, false ); + + if( nAbsWidth >= nMinNoAlignCell ) + { + nMaxNoAlignCell = nAbsWidth; + if( HasColsOption() ) + nMinNoAlignCell = nAbsWidth; + } + else if( nAbsWidth >= nAbsMinNoAlignCell ) + { + nMaxNoAlignCell = nAbsWidth; + nMinNoAlignCell = nAbsWidth; + } + else + { + nMaxNoAlignCell = nAbsMinNoAlignCell; + nMinNoAlignCell = nAbsMinNoAlignCell; + } + } + else if( HasColsOption() || HasColTags() ) + nMinNoAlignCell = nAbsMinNoAlignCell; + + SwHTMLTableLayoutConstraints *pConstr = + new SwHTMLTableLayoutConstraints( nMinNoAlignCell, + nMaxNoAlignCell, j, i, nColSpan ); + if (xConstraints) + { + SwHTMLTableLayoutConstraints* pConstraints = xConstraints->InsertNext(pConstr); + xConstraints.release(); + xConstraints.reset(pConstraints); + } + else + xConstraints.reset(pConstr); + } + } + } + + OSL_ENSURE( nMinColSpan>0 && nColSkip>0 && nColSkip <= nMinColSpan, + "Layout pass 1: Columns are being forgotten!" ); + OSL_ENSURE( nMinColSpan!=USHRT_MAX, + "Layout pass 1: unnecessary pass through the loop or a bug" ); + + if( 1==nMinColSpan ) + { + // There are cells with COLSPAN 1 and therefore also useful + // values in pColumn + + // Take over values according to the following table (Netscape 4.0 pv 3): + + // WIDTH: no COLS COLS + + // none min = min min = absmin + // max = max max = max + + // >= min min = min min = width + // max = width max = width + + // >= absmin min = width(*) min = width + // max = width max = width + + // < absmin min = absmin min = absmin + // max = absmin max = absmin + + // (*) Netscape uses the minimum width without a break before + // the last graphic here. We don't have that (yet?), + // so we leave it set to width. + + if( pColumn->GetWidthOption() && !pColumn->IsRelWidthOption() ) + { + // Take over absolute widths as minimal and maximal widths. + sal_uLong nAbsWidth = pColumn->GetWidthOption(); + sal_uLong nDummy = 0, nDummy2 = 0; + AddBorderWidth( nAbsWidth, nDummy, nDummy2, i, 1, false ); + + if( nAbsWidth >= pColumn->GetMinNoAlign() ) + { + pColumn->SetMinMax( HasColsOption() ? nAbsWidth + : pColumn->GetMinNoAlign(), + nAbsWidth ); + } + else if( nAbsWidth >= pColumn->GetAbsMinNoAlign() ) + { + pColumn->SetMinMax( nAbsWidth, nAbsWidth ); + } + else + { + pColumn->SetMinMax( pColumn->GetAbsMinNoAlign(), + pColumn->GetAbsMinNoAlign() ); + } + } + else + { + pColumn->SetMinMax( HasColsOption() ? pColumn->GetAbsMinNoAlign() + : pColumn->GetMinNoAlign(), + pColumn->GetMaxNoAlign() ); + } + } + else if( USHRT_MAX!=nMinColSpan ) + { + // Can be anything != 0, because it is altered by the constraints. + pColumn->SetMinMax( MINLAY, MINLAY ); + + // the next columns need not to be processed + i += (nColSkip-1); + } + + m_nMin += pColumn->GetMin(); + m_nMax += pColumn->GetMax(); + if (pColumn->IsRelWidthOption()) bFixRelWidths = true; + } + + // Now process the constraints + SwHTMLTableLayoutConstraints *pConstr = xConstraints.get(); + while( pConstr ) + { + // At first we need to process the width in the same way + // as the column widths + sal_uInt16 nCol = pConstr->GetColumn(); + sal_uInt16 nColSpan = pConstr->GetColSpan(); + sal_uLong nConstrMin = pConstr->GetMinNoAlign(); + sal_uLong nConstrMax = pConstr->GetMaxNoAlign(); + + // We get the hitherto width of the spanned columns + sal_uLong nColsMin = 0; + sal_uLong nColsMax = 0; + for( sal_uInt16 j=nCol; j<nCol+nColSpan; j++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( j ); + nColsMin += pColumn->GetMin(); + nColsMax += pColumn->GetMax(); + } + + if( nColsMin<nConstrMin ) + { + // Proportionately distribute the minimum value to the columns + sal_uLong nMinD = nConstrMin-nColsMin; + + if( nConstrMin > nColsMax ) + { + // Proportional according to the minimum widths + sal_uInt16 nEndCol = nCol+nColSpan; + sal_uLong nDiff = nMinD; + for( sal_uInt16 ic=nCol; ic<nEndCol; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + sal_uLong nColMin = pColumn->GetMin(); + sal_uLong nColMax = pColumn->GetMax(); + + m_nMin -= nColMin; + sal_uLong nAdd; + if (ic < nEndCol-1) + { + if (nColsMin == 0) + throw o3tl::divide_by_zero(); + nAdd = (nColMin * nMinD) / nColsMin; + } + else + { + nAdd = nDiff; + } + nColMin += nAdd; + m_nMin += nColMin; + OSL_ENSURE( nDiff >= nAdd, "Ooops: nDiff is not correct anymore" ); + nDiff -= nAdd; + + if( nColMax < nColMin ) + { + m_nMax -= nColMax; + nColsMax -= nColMax; + nColMax = nColMin; + m_nMax += nColMax; + nColsMax += nColMax; + } + + pColumn->SetMinMax( nColMin, nColMax ); + } + } + else + { + // Proportional according to the difference of max and min + for( sal_uInt16 ic=nCol; ic<nCol+nColSpan; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + sal_uLong nDiff = pColumn->GetMax()-pColumn->GetMin(); + if( nMinD < nDiff ) + nDiff = nMinD; + + pColumn->AddToMin( nDiff ); + + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Why is the Column suddenly too narrow?" ); + + m_nMin += nDiff; + nMinD -= nDiff; + } + } + } + + if( !HasColTags() && nColsMax<nConstrMax ) + { + sal_uLong nMaxD = nConstrMax-nColsMax; + + for( sal_uInt16 ic=nCol; ic<nCol+nColSpan; ic++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( ic ); + + m_nMax -= pColumn->GetMax(); + + pColumn->AddToMax( (pColumn->GetMax() * nMaxD) / nColsMax ); + + m_nMax += pColumn->GetMax(); + } + } + + pConstr = pConstr->GetNext(); + } + + if( bFixRelWidths ) + { + if( HasColTags() ) + { + // To adapt the relative widths, in a first step we multiply the + // minimum width of all affected cells with the relative width + // of the column. + // Thus, the width ratio among the columns is correct. + + // Furthermore, a factor is calculated that says by how much the + // cell has gotten wider than the minimum width. + + // In the second step the calculated widths are divided by this + // factor. Thereby a cell's width is preserved and serves as a + // basis for the other cells. + // We only change the maximum widths here! + + sal_uLong nAbsMin = 0; // absolute minimum width of all widths with relative width + sal_uLong nRel = 0; // sum of all relative widths of all columns + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + nAbsMin += pColumn->GetMin(); + nRel += pColumn->GetWidthOption(); + } + } + + sal_uLong nQuot = ULONG_MAX; + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() ) + { + m_nMax -= pColumn->GetMax(); + if( pColumn->GetWidthOption() && pColumn->GetMin() ) + { + pColumn->SetMax( nAbsMin * pColumn->GetWidthOption() ); + sal_uLong nColQuot = pColumn->GetMax() / pColumn->GetMin(); + if( nColQuot<nQuot ) + nQuot = nColQuot; + } + } + } + OSL_ENSURE( 0==nRel || nQuot!=ULONG_MAX, + "Where did the relative columns go?" ); + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() ) + { + if( pColumn->GetWidthOption() ) + pColumn->SetMax( pColumn->GetMax() / nQuot ); + else + pColumn->SetMax( pColumn->GetMin() ); + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Maximum column width is lower than the minimum column width" ); + m_nMax += pColumn->GetMax(); + } + } + } + else + { + sal_uInt16 nRel = 0; // sum of the relative widths of all columns + sal_uInt16 nRelCols = 0; // count of the columns with a relative setting + sal_uLong nRelMax = 0; // fraction of the maximum of this column + for( i=0; i<m_nCols; i++ ) + { + OSL_ENSURE( nRel<=100, "relative width of all columns > 100%" ); + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + // Make sure that the relative widths don't go above 100% + sal_uInt16 nColWidth = pColumn->GetWidthOption(); + if( nRel+nColWidth > 100 ) + { + nColWidth = 100 - nRel; + pColumn->SetWidthOption( nColWidth ); + } + nRelMax += pColumn->GetMax(); + nRel = nRel + nColWidth; + nRelCols++; + } + else if( !pColumn->GetMin() ) + { + // The column is empty (so it was solely created by + // COLSPAN) and therefore must not be assigned a % width. + nRelCols++; + } + } + + // If there are percentages left we distribute them to the columns + // that don't have a width setting. Like in Netscape we distribute + // the remaining percentages according to the ratio of the maximum + // width of the affected columns. + // For the maximum widths we also take the fixed-width columns + // into account. Is that correct? + sal_uLong nFixMax = 0; + if( nRel < 100 && nRelCols < m_nCols ) + { + nFixMax = m_nMax - nRelMax; + SAL_WARN_IF(!nFixMax, "sw.core", "bad fixed width max"); + } + if (nFixMax) + { + sal_uInt16 nRelLeft = 100 - nRel; + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( !pColumn->IsRelWidthOption() && + !pColumn->GetWidthOption() && + pColumn->GetMin() ) + { + // the next column gets the rest + sal_uInt16 nColWidth = + static_cast<sal_uInt16>((pColumn->GetMax() * nRelLeft) / nFixMax); + pColumn->SetWidthOption( nColWidth ); + } + } + } + + // adjust the maximum widths now accordingly + sal_uLong nQuotMax = ULONG_MAX; + sal_uLong nOldMax = m_nMax; + m_nMax = 0; + for( i=0; i<m_nCols; i++ ) + { + // Columns with a % setting are adapted accordingly. + // Columns, that + // - do not have a % setting and are located within a tables + // with COLS and WIDTH, or + // - their width is 0% + // get set to the minimum width. + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() ) + { + sal_uLong nNewMax; + sal_uLong nColQuotMax; + if( !m_nWidthOption ) + { + nNewMax = nOldMax * pColumn->GetWidthOption(); + nColQuotMax = nNewMax / pColumn->GetMax(); + } + else + { + nNewMax = m_nMin * pColumn->GetWidthOption(); + nColQuotMax = nNewMax / pColumn->GetMin(); + } + pColumn->SetMax( nNewMax ); + if( nColQuotMax < nQuotMax ) + nQuotMax = nColQuotMax; + } + else if( HasColsOption() || m_nWidthOption || + (pColumn->IsRelWidthOption() && + !pColumn->GetWidthOption()) ) + pColumn->SetMax( pColumn->GetMin() ); + } + // and divide by the quotient + SAL_WARN_IF(!nQuotMax, "sw.core", "Where did the relative columns go?"); + for (i = 0; i < m_nCols; ++i) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if (pColumn->IsRelWidthOption() && pColumn->GetWidthOption() && nQuotMax) + { + pColumn->SetMax( pColumn->GetMax() / nQuotMax ); + OSL_ENSURE( pColumn->GetMax() >= pColumn->GetMin(), + "Minimum width is one column bigger than maximum" ); + if( pColumn->GetMax() < pColumn->GetMin() ) + pColumn->SetMax( pColumn->GetMin() ); + } + m_nMax += pColumn->GetMax(); + } + } + } +} + +//TODO: provide documentation +/** + + @param nAbsAvail available space in TWIPS. + @param nRelAvail available space related to USHRT_MAX or 0 + @param nAbsSpace fraction of nAbsAvail, which is reserved by the surrounding + cell for the border and the distance to the paragraph. +*/ +void SwHTMLTableLayout::AutoLayoutPass2( sal_uInt16 nAbsAvail, sal_uInt16 nRelAvail, + sal_uInt16 nAbsLeftSpace, + sal_uInt16 nAbsRightSpace, + sal_uInt16 nParentInhAbsSpace ) +{ + // For a start we do a lot of plausibility tests + + // An absolute width always has to be passed + OSL_ENSURE( nAbsAvail, "AutoLayout pass 2: No absolute width given" ); + + // A relative width must only be passed for tables within tables (?) + OSL_ENSURE( IsTopTable() == (nRelAvail==0), + "AutoLayout pass 2: Relative width at table in table or the other way around" ); + + // The table's minimum width must not be bigger than its maximum width + OSL_ENSURE( m_nMin<=m_nMax, "AutoLayout pass 2: nMin > nMax" ); + + // Remember the available width for which the table was calculated. + // This is a good place as we pass by here for the initial calculation + // of the table in the parser and for each Resize_ call. + m_nLastResizeAbsAvail = nAbsAvail; + + // Step 1: The available space is readjusted for the left/right border, + // possibly existing filler cells and distances. + + // Distance to the content and border + sal_uInt16 nAbsLeftFill = 0, nAbsRightFill = 0; + if( !IsTopTable() && + GetMin() + nAbsLeftSpace + nAbsRightSpace <= nAbsAvail ) + { + nAbsLeftFill = nAbsLeftSpace; + nAbsRightFill = nAbsRightSpace; + } + + // Left and right distance + if( m_nLeftMargin || m_nRightMargin ) + { + if( IsTopTable() ) + { + // For the top table we always respect the borders, because we + // never go below the table's minimum width. + nAbsAvail -= (m_nLeftMargin + m_nRightMargin); + } + else if( GetMin() + m_nLeftMargin + m_nRightMargin <= nAbsAvail ) + { + // Else, we only respect the borders if there's space available + // for them (nMin has already been calculated!) + nAbsLeftFill = nAbsLeftFill + m_nLeftMargin; + nAbsRightFill = nAbsRightFill + m_nRightMargin; + } + } + + // Read just the available space + m_nRelLeftFill = 0; + m_nRelRightFill = 0; + if( !IsTopTable() && (nAbsLeftFill>0 || nAbsRightFill) ) + { + sal_uLong nAbsLeftFillL = nAbsLeftFill, nAbsRightFillL = nAbsRightFill; + + m_nRelLeftFill = static_cast<sal_uInt16>((nAbsLeftFillL * nRelAvail) / nAbsAvail); + m_nRelRightFill = static_cast<sal_uInt16>((nAbsRightFillL * nRelAvail) / nAbsAvail); + + nAbsAvail -= (nAbsLeftFill + nAbsRightFill); + if( nRelAvail ) + nRelAvail -= (m_nRelLeftFill + m_nRelRightFill); + } + + // Step 2: Calculate the absolute table width. + sal_uInt16 nAbsTabWidth = 0; + m_bUseRelWidth = false; + if( m_nWidthOption ) + { + if( m_bPercentWidthOption ) + { + OSL_ENSURE( m_nWidthOption<=100, "Percentage value too high" ); + if( m_nWidthOption > 100 ) + m_nWidthOption = 100; + + // The absolute width is equal to the given percentage of + // the available width. + // Top tables only get a relative width if the available space + // is *strictly larger* than the minimum width. + + // CAUTION: We need the "strictly larger" because changing from a + // relative width to an absolute width by resizing would lead + // to an infinite loop. + + // Because we do not call resize for tables in frames if the + // frame has a non-relative width, we cannot play such games. + + // Let's play such games now anyway. We had a graphic in a 1% wide + // table and it didn't fit in of course. + nAbsTabWidth = static_cast<sal_uInt16>( (static_cast<sal_uLong>(nAbsAvail) * m_nWidthOption) / 100 ); + if( IsTopTable() && + ( /*MayBeInFlyFrame() ||*/ static_cast<sal_uLong>(nAbsTabWidth) > m_nMin ) ) + { + nRelAvail = USHRT_MAX; + m_bUseRelWidth = true; + } + } + else + { + nAbsTabWidth = m_nWidthOption; + if( nAbsTabWidth > MAX_TABWIDTH ) + nAbsTabWidth = MAX_TABWIDTH; + + // Tables within tables must never get wider than the available + // space. + if( !IsTopTable() && nAbsTabWidth > nAbsAvail ) + nAbsTabWidth = nAbsAvail; + } + } + + OSL_ENSURE( IsTopTable() || nAbsTabWidth<=nAbsAvail, + "AutoLayout pass 2: nAbsTabWidth > nAbsAvail for table in table" ); + OSL_ENSURE( !nRelAvail || nAbsTabWidth<=nAbsAvail, + "AutoLayout pass 2: nAbsTabWidth > nAbsAvail for relative width" ); + + // Catch for the two asserts above (we never know!) + if( (!IsTopTable() || nRelAvail>0) && nAbsTabWidth>nAbsAvail ) + nAbsTabWidth = nAbsAvail; + + // Step 3: Identify the column width and, if applicable, the absolute + // and relative table widths. + if( (!IsTopTable() && m_nMin > static_cast<sal_uLong>(nAbsAvail)) || + m_nMin > MAX_TABWIDTH ) + { + // If + // - an inner table's minimum is larger than the available space, or + // - a top table's minimum is larger than USHORT_MAX the table + // has to be adapted to the available space or USHORT_MAX. + // We preserve the widths' ratio amongst themselves, however. + + nAbsTabWidth = IsTopTable() ? MAX_TABWIDTH : nAbsAvail; + m_nRelTabWidth = (nRelAvail ? nRelAvail : nAbsTabWidth ); + + // First of all, we check whether we can fit the layout constrains, + // which are: Every cell's width excluding the borders must be at least + // MINLAY: + + sal_uLong nRealMin = 0; + for( sal_uInt16 i=0; i<m_nCols; i++ ) + { + sal_uLong nRealColMin = MINLAY, nDummy1 = 0, nDummy2 = 0; + AddBorderWidth( nRealColMin, nDummy1, nDummy2, i, 1 ); + nRealMin += nRealColMin; + } + if( (nRealMin >= nAbsTabWidth) || (nRealMin >= m_nMin) ) + { + // "Rien ne va plus": we cannot get the minimum column widths + // the layout wants to have. + + sal_uInt16 nAbs = 0, nRel = 0; + SwHTMLTableLayoutColumn *pColumn; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + pColumn = GetColumn( i ); + sal_uLong nColMin = pColumn->GetMin(); + if( nColMin <= USHRT_MAX ) + { + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMin * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMin * m_nRelTabWidth) / m_nMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMinD * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMinD * m_nRelTabWidth) / m_nMin) ); + } + + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( nAbsTabWidth - nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth - nRel ); + } + else + { + sal_uLong nDistAbs = nAbsTabWidth - nRealMin; + sal_uLong nDistRel = m_nRelTabWidth - nRealMin; + sal_uLong nDistMin = m_nMin - nRealMin; + sal_uInt16 nAbs = 0, nRel = 0; + SwHTMLTableLayoutColumn *pColumn; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + pColumn = GetColumn( i ); + sal_uLong nColMin = pColumn->GetMin(); + sal_uLong nRealColMin = MINLAY, nDummy1 = 0, nDummy2 = 0; + AddBorderWidth( nRealColMin, nDummy1, nDummy2, i, 1 ); + + if( nColMin <= USHRT_MAX ) + { + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((((nColMin-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((((nColMin-nRealColMin) * nDistRel) / nDistMin) + nRealColMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((((nColMinD-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((((nColMinD-nRealColMin) * nDistRel) / nDistMin) + nRealColMin) ); + } + + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( nAbsTabWidth - nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth - nRel ); + } + } + else if( m_nMax <= static_cast<sal_uLong>(nAbsTabWidth ? nAbsTabWidth : nAbsAvail) ) + { + // If + // - the table has a fixed width and the table's maximum is + // smaller, or + //- the maximum is smaller than the available space, + // we can take over the maximum as it is. Respectively + // the table can only be adapted to the fixed width by + // respecting the maximum. + + // No fixed width, use the maximum. + if( !nAbsTabWidth ) + nAbsTabWidth = static_cast<sal_uInt16>(m_nMax); + + // A top table may also get wider then the available space. + if( nAbsTabWidth > nAbsAvail ) + { + OSL_ENSURE( IsTopTable(), + "Table in table should get wider than the surrounding cell." ); + nAbsAvail = nAbsTabWidth; + } + + // Only use the relative widths' fraction, that is used for the + // absolute width. + sal_uLong nAbsTabWidthL = nAbsTabWidth; + if (nRelAvail) + { + if (nAbsAvail == 0) + throw o3tl::divide_by_zero(); + m_nRelTabWidth = static_cast<sal_uInt16>((nAbsTabWidthL * nRelAvail) / nAbsAvail); + } + else + m_nRelTabWidth = nAbsTabWidth; + + // Are there columns width a percentage setting and some without one? + sal_uLong nFixMax = m_nMax; + for( sal_uInt16 i=0; i<m_nCols; i++ ) + { + const SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption()>0 ) + nFixMax -= pColumn->GetMax(); + } + + if( nFixMax > 0 && nFixMax < m_nMax ) + { + // Yes, distribute the to-be-distributed space only to the + // columns with a percentage setting. + + // In this case (and in this case only) there are columns + // that exactly keep their maximum width, that is they neither + // get smaller nor wider. When calculating the absolute width + // from the relative width we can get rounding errors. + // To correct this, we first make the fixed widths compensate for + // this error. We then fix the relative widths the same way. + + sal_uInt16 nAbs = 0, nRel = 0; + sal_uInt16 nFixedCols = 0; + sal_uInt16 i; + + for( i = 0; i < m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( !pColumn->IsRelWidthOption() || !pColumn->GetWidthOption() ) + { + // The column keeps its width. + nFixedCols++; + sal_uLong nColMax = pColumn->GetMax(); + pColumn->SetAbsColWidth( static_cast<sal_uInt16>(nColMax) ); + + sal_uLong nRelColWidth = + (nColMax * m_nRelTabWidth) / nAbsTabWidth; + sal_uLong nChkWidth = + (nRelColWidth * nAbsTabWidth) / m_nRelTabWidth; + if( nChkWidth < nColMax ) + nRelColWidth++; + else if( nChkWidth > nColMax ) + nRelColWidth--; + pColumn->SetRelColWidth( static_cast<sal_uInt16>(nRelColWidth) ); + + nAbs = nAbs + static_cast<sal_uInt16>(nColMax); + nRel = nRel + static_cast<sal_uInt16>(nRelColWidth); + } + } + + // The to-be-distributed percentage of the maximum, the + // relative and absolute widths. Here, nFixMax corresponds + // to nAbs, so that we could've called it nAbs. + // The code is, however, more readable like that. + OSL_ENSURE( nFixMax == nAbs, "Two loops, two sums?" ); + sal_uLong nDistMax = m_nMax - nFixMax; + sal_uInt16 nDistAbsTabWidth = nAbsTabWidth - nAbs; + sal_uInt16 nDistRelTabWidth = m_nRelTabWidth - nRel; + + for( i=0; i<m_nCols; i++ ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( i ); + if( pColumn->IsRelWidthOption() && pColumn->GetWidthOption() > 0 ) + { + // The column gets proportionately wider. + nFixedCols++; + if( nFixedCols == m_nCols ) + { + pColumn->SetAbsColWidth( nAbsTabWidth-nAbs ); + pColumn->SetRelColWidth( m_nRelTabWidth-nRel ); + } + else + { + sal_uLong nColMax = pColumn->GetMax(); + pColumn->SetAbsColWidth( + static_cast<sal_uInt16>((nColMax * nDistAbsTabWidth) / nDistMax) ); + pColumn->SetRelColWidth( + static_cast<sal_uInt16>((nColMax * nDistRelTabWidth) / nDistMax) ); + } + nAbs = nAbs + pColumn->GetAbsColWidth(); + nRel = nRel + pColumn->GetRelColWidth(); + } + } + OSL_ENSURE( m_nCols==nFixedCols, "Missed a column!" ); + } + else if (m_nCols > 0) + { + if (m_nMax == 0) + throw o3tl::divide_by_zero(); + // No. So distribute the space regularly among all columns. + for (sal_uInt16 i=0; i < m_nCols; ++i) + { + sal_uLong nColMax = GetColumn( i )->GetMax(); + GetColumn( i )->SetAbsColWidth( + static_cast<sal_uInt16>((nColMax * nAbsTabWidth) / m_nMax) ); + GetColumn( i )->SetRelColWidth( + static_cast<sal_uInt16>((nColMax * m_nRelTabWidth) / m_nMax) ); + } + } + } + else + { + // Proportionately distribute the space that extends over the minimum + // width among the columns. + if( !nAbsTabWidth ) + nAbsTabWidth = nAbsAvail; + if( nAbsTabWidth < m_nMin ) + nAbsTabWidth = static_cast<sal_uInt16>(m_nMin); + + if( nAbsTabWidth > nAbsAvail ) + { + OSL_ENSURE( IsTopTable(), + "A nested table should become wider than the available space." ); + nAbsAvail = nAbsTabWidth; + } + + sal_uLong nAbsTabWidthL = nAbsTabWidth; + if (nRelAvail) + { + if (nAbsAvail == 0) + throw o3tl::divide_by_zero(); + m_nRelTabWidth = static_cast<sal_uInt16>((nAbsTabWidthL * nRelAvail) / nAbsAvail); + } + else + m_nRelTabWidth = nAbsTabWidth; + double nW = nAbsTabWidth - m_nMin; + double nD = (m_nMax==m_nMin ? 1 : m_nMax-m_nMin); + sal_uInt16 nAbs = 0, nRel = 0; + for( sal_uInt16 i=0; i<m_nCols-1; i++ ) + { + double nd = GetColumn( i )->GetMax() - GetColumn( i )->GetMin(); + sal_uLong nAbsColWidth = GetColumn( i )->GetMin() + static_cast<sal_uLong>((nd*nW)/nD); + sal_uLong nRelColWidth = nRelAvail + ? (nAbsColWidth * m_nRelTabWidth) / nAbsTabWidth + : nAbsColWidth; + + GetColumn( i )->SetAbsColWidth( static_cast<sal_uInt16>(nAbsColWidth) ); + GetColumn( i )->SetRelColWidth( static_cast<sal_uInt16>(nRelColWidth) ); + nAbs = nAbs + static_cast<sal_uInt16>(nAbsColWidth); + nRel = nRel + static_cast<sal_uInt16>(nRelColWidth); + } + GetColumn( m_nCols-1 )->SetAbsColWidth( nAbsTabWidth - nAbs ); + GetColumn( m_nCols-1 )->SetRelColWidth( m_nRelTabWidth - nRel ); + + } + + // Step 4: For nested tables we can have balancing cells on the + // left or right. Here we calculate their width. + m_nInhAbsLeftSpace = 0; + m_nInhAbsRightSpace = 0; + if( !IsTopTable() && (m_nRelLeftFill>0 || m_nRelRightFill>0 || + nAbsTabWidth<nAbsAvail) ) + { + // Calculate the width of additional cells we use for + // aligning inner tables. + sal_uInt16 nAbsDist = static_cast<sal_uInt16>(nAbsAvail-nAbsTabWidth); + sal_uInt16 nRelDist = static_cast<sal_uInt16>(nRelAvail-m_nRelTabWidth); + sal_uInt16 nParentInhAbsLeftSpace = 0, nParentInhAbsRightSpace = 0; + + // Calculate the size and position of the additional cells. + switch( m_eTableAdjust ) + { + case SvxAdjust::Right: + nAbsLeftFill = nAbsLeftFill + nAbsDist; + m_nRelLeftFill = m_nRelLeftFill + nRelDist; + nParentInhAbsLeftSpace = nParentInhAbsSpace; + break; + case SvxAdjust::Center: + { + sal_uInt16 nAbsLeftDist = nAbsDist / 2; + nAbsLeftFill = nAbsLeftFill + nAbsLeftDist; + nAbsRightFill += nAbsDist - nAbsLeftDist; + sal_uInt16 nRelLeftDist = nRelDist / 2; + m_nRelLeftFill = m_nRelLeftFill + nRelLeftDist; + m_nRelRightFill += nRelDist - nRelLeftDist; + nParentInhAbsLeftSpace = nParentInhAbsSpace / 2; + nParentInhAbsRightSpace = nParentInhAbsSpace - + nParentInhAbsLeftSpace; + } + break; + case SvxAdjust::Left: + default: + nAbsRightFill = nAbsRightFill + nAbsDist; + m_nRelRightFill = m_nRelRightFill + nRelDist; + nParentInhAbsRightSpace = nParentInhAbsSpace; + break; + } + + // Filler widths are added to the outer columns, if there are no boxes + // for them after the first pass (nWidth>0) or their width would become + // too small or if there are COL tags and the filler width corresponds + // to the border width. + // In the last case we probably exported the table ourselves. + if( m_nRelLeftFill && + ( m_nWidthSet>0 || nAbsLeftFill<MINLAY+m_nInhLeftBorderWidth || + (HasColTags() && nAbsLeftFill < nAbsLeftSpace+nParentInhAbsLeftSpace+20) ) ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( 0 ); + pColumn->SetAbsColWidth( pColumn->GetAbsColWidth()+nAbsLeftFill ); + pColumn->SetRelColWidth( pColumn->GetRelColWidth()+m_nRelLeftFill ); + m_nRelLeftFill = 0; + m_nInhAbsLeftSpace = nAbsLeftSpace + nParentInhAbsLeftSpace; + } + if( m_nRelRightFill && + ( m_nWidthSet>0 || nAbsRightFill<MINLAY+m_nInhRightBorderWidth || + (HasColTags() && nAbsRightFill < nAbsRightSpace+nParentInhAbsRightSpace+20) ) ) + { + SwHTMLTableLayoutColumn *pColumn = GetColumn( m_nCols-1 ); + pColumn->SetAbsColWidth( pColumn->GetAbsColWidth()+nAbsRightFill ); + pColumn->SetRelColWidth( pColumn->GetRelColWidth()+m_nRelRightFill ); + m_nRelRightFill = 0; + m_nInhAbsRightSpace = nAbsRightSpace + nParentInhAbsRightSpace; + } + } +} + +static void lcl_ResizeLine( const SwTableLine* pLine, SwTwips *pWidth ); + +static void lcl_ResizeBox( const SwTableBox* pBox, SwTwips* pWidth ) +{ + if( !pBox->GetSttNd() ) + { + SwTwips nWidth = 0; + for( const SwTableLine *pLine : pBox->GetTabLines() ) + lcl_ResizeLine( pLine, &nWidth ); + pBox->GetFrameFormat()->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth, 0 )); + *pWidth = *pWidth + nWidth; + } + else + { + *pWidth = *pWidth + pBox->GetFrameFormat()->GetFrameSize().GetSize().Width(); + } +} + +static void lcl_ResizeLine( const SwTableLine* pLine, SwTwips *pWidth ) +{ + SwTwips nOldWidth = *pWidth; + *pWidth = 0; + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + lcl_ResizeBox(pBox, pWidth ); + + SAL_WARN_IF( nOldWidth && std::abs(*pWidth-nOldWidth) >= COLFUZZY, "sw.core", + "A box's rows have all a different length" ); +} + +void SwHTMLTableLayout::SetWidths( bool bCallPass2, sal_uInt16 nAbsAvail, + sal_uInt16 nRelAvail, sal_uInt16 nAbsLeftSpace, + sal_uInt16 nAbsRightSpace, + sal_uInt16 nParentInhAbsSpace ) +{ + // SetWidth must have been passed through once more for every cell in the + // end. + m_nWidthSet++; + + // Step 0: If necessary, we call the layout algorithm of Pass2. + if( bCallPass2 ) + AutoLayoutPass2( nAbsAvail, nRelAvail, nAbsLeftSpace, nAbsRightSpace, + nParentInhAbsSpace ); + + // Step 1: Set the new width in all content boxes. + // Because the boxes don't know anything about the HTML table structure, + // we iterate over the HTML table structure. + // For tables in tables in tables we call SetWidth recursively. + for( sal_uInt16 i=0; i<m_nRows; i++ ) + { + for( sal_uInt16 j=0; j<m_nCols; j++ ) + { + SwHTMLTableLayoutCell *pCell = GetCell( i, j ); + + SwHTMLTableLayoutCnts* pContents = pCell->GetContents().get(); + while( pContents && !pContents->IsWidthSet(m_nWidthSet) ) + { + SwTableBox *pBox = pContents->GetTableBox(); + if( pBox ) + { + SetBoxWidth( pBox, j, pCell->GetColSpan() ); + } + else if (SwHTMLTableLayout *pTable = pContents->GetTable()) + { + sal_uInt16 nAbs = 0, nRel = 0, nLSpace = 0, nRSpace = 0, + nInhSpace = 0; + if( bCallPass2 ) + { + sal_uInt16 nColSpan = pCell->GetColSpan(); + GetAvail( j, nColSpan, nAbs, nRel ); + nLSpace = GetLeftCellSpace( j, nColSpan ); + nRSpace = GetRightCellSpace( j, nColSpan ); + nInhSpace = GetInhCellSpace( j, nColSpan ); + } + pTable->SetWidths( bCallPass2, nAbs, nRel, + nLSpace, nRSpace, + nInhSpace ); + } + + pContents->SetWidthSet( m_nWidthSet ); + pContents = pContents->GetNext().get(); + } + } + } + + // Step 2: If we have a top table, we adapt the formats of the + // non-content-boxes. Because they are not known in the HTML table + // due to garbage collection there, we need the iterate over the + // whole table. + // We also adapt the table frame format. For nested tables we set the + // filler cell's width instead. + if( IsTopTable() ) + { + SwTwips nCalcTabWidth = 0; + for( const SwTableLine *pLine : m_pSwTable->GetTabLines() ) + lcl_ResizeLine( pLine, &nCalcTabWidth ); + SAL_WARN_IF( std::abs( m_nRelTabWidth-nCalcTabWidth ) >= COLFUZZY, "sw.core", + "Table width is not equal to the row width" ); + + // Lock the table format when altering it, or else the box formats + // are altered again. + // Also, we need to preserve a percent setting if it exists. + SwFrameFormat *pFrameFormat = m_pSwTable->GetFrameFormat(); + const_cast<SwTable *>(m_pSwTable)->LockModify(); + SwFormatFrameSize aFrameSize( pFrameFormat->GetFrameSize() ); + aFrameSize.SetWidth( m_nRelTabWidth ); + bool bRel = m_bUseRelWidth && + text::HoriOrientation::FULL!=pFrameFormat->GetHoriOrient().GetHoriOrient(); + aFrameSize.SetWidthPercent( static_cast<sal_uInt8>(bRel ? m_nWidthOption : 0) ); + pFrameFormat->SetFormatAttr( aFrameSize ); + const_cast<SwTable *>(m_pSwTable)->UnlockModify(); + + // If the table is located in a frame, we also need to adapt the + // frame's width. + if( MayBeInFlyFrame() ) + { + SwFrameFormat *pFlyFrameFormat = FindFlyFrameFormat(); + if( pFlyFrameFormat ) + { + SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, m_nRelTabWidth, MINLAY ); + + if( m_bUseRelWidth ) + { + // For percentage settings we set the width to the minimum. + aFlyFrameSize.SetWidth( m_nMin > USHRT_MAX ? USHRT_MAX + : m_nMin ); + aFlyFrameSize.SetWidthPercent( static_cast<sal_uInt8>(m_nWidthOption) ); + } + pFlyFrameFormat->SetFormatAttr( aFlyFrameSize ); + } + } + +#ifdef DBG_UTIL + { + // check if the tables have correct widths + SwTwips nSize = m_pSwTable->GetFrameFormat()->GetFrameSize().GetWidth(); + const SwTableLines& rLines = m_pSwTable->GetTabLines(); + for (size_t n = 0; n < rLines.size(); ++n) + { + CheckBoxWidth( *rLines[ n ], nSize ); + } + } +#endif + + } +} + +void SwHTMLTableLayout::Resize_( sal_uInt16 nAbsAvail, bool bRecalc ) +{ + // If bRecalc is set, the table's content changed. + // We need to execute pass 1 again. + if( bRecalc ) + AutoLayoutPass1(); + + SwRootFrame *pRoot = GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()->GetLayout(); + if ( pRoot && pRoot->IsCallbackActionEnabled() ) + pRoot->StartAllAction(); + + // Else we can set the widths, in which we have to run Pass 2 in each case. + SetWidths( true, nAbsAvail ); + + if ( pRoot && pRoot->IsCallbackActionEnabled() ) + pRoot->EndAllAction( true ); //True per VirDev (browsing is calmer) +} + +IMPL_LINK_NOARG( SwHTMLTableLayout, DelayedResize_Impl, Timer*, void ) +{ + m_aResizeTimer.Stop(); + Resize_( m_nDelayedResizeAbsAvail, m_bDelayedResizeRecalc ); +} + +bool SwHTMLTableLayout::Resize( sal_uInt16 nAbsAvail, bool bRecalc, + bool bForce, sal_uLong nDelay ) +{ + if( 0 == nAbsAvail ) + return false; + OSL_ENSURE( IsTopTable(), "Resize must only be called for top tables!" ); + + // May the table be resized at all? Or is it forced? + if( m_bMustNotResize && !bForce ) + return false; + + // May the table be recalculated? Or is it forced? + if( m_bMustNotRecalc && !bForce ) + bRecalc = false; + + const SwDoc *pDoc = GetDoc(); + + // If there is a layout, the root frame's size instead of the + // VisArea's size was potentially passed. + // If we're not in a frame we need to calculate the table for the VisArea, + // because switching from relative to absolute wouldn't work. + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell()->GetViewOptions()->getBrowseMode() ) + { + const sal_uInt16 nVisAreaWidth = GetBrowseWidthByVisArea( *pDoc ); + if( nVisAreaWidth < nAbsAvail && !FindFlyFrameFormat() ) + nAbsAvail = nVisAreaWidth; + } + + if( nDelay==0 && m_aResizeTimer.IsActive() ) + { + m_nDelayedResizeAbsAvail = nAbsAvail; + return false; + } + + // Optimisation: + // If the minimum or maximum should not be recalculated and + // - the table's width never needs to be recalculated, or + // - the table was already calculated for the passed width, or + // - the available space is less or equal to the minimum width + // and the table already has the minimum width, or + // - the available space is larger than the maximum width and + // the table already has the maximum width + // nothing will happen to the table. + if( !bRecalc && ( !m_bMustResize || + (m_nLastResizeAbsAvail==nAbsAvail) || + (nAbsAvail<=m_nMin && m_nRelTabWidth==m_nMin) || + (!m_bPercentWidthOption && nAbsAvail>=m_nMax && m_nRelTabWidth==m_nMax) ) ) + return false; + + if( nDelay==HTMLTABLE_RESIZE_NOW ) + { + if( m_aResizeTimer.IsActive() ) + m_aResizeTimer.Stop(); + Resize_( nAbsAvail, bRecalc ); + } + else if( nDelay > 0 ) + { + m_nDelayedResizeAbsAvail = nAbsAvail; + m_bDelayedResizeRecalc = bRecalc; + m_aResizeTimer.SetTimeout( nDelay ); + m_aResizeTimer.Start(); + } + else + { + Resize_( nAbsAvail, bRecalc ); + } + + return true; +} + +void SwHTMLTableLayout::BordersChanged( sal_uInt16 nAbsAvail ) +{ + m_bBordersChanged = true; + + Resize( nAbsAvail, true/*bRecalc*/ ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/lineinfo.cxx b/sw/source/core/doc/lineinfo.cxx new file mode 100644 index 000000000..9922e3aa1 --- /dev/null +++ b/sw/source/core/doc/lineinfo.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> +#include <lineinfo.hxx> +#include <charfmt.hxx> +#include <poolfmt.hxx> +#include <rootfrm.hxx> +#include <set> + +void SwDoc::SetLineNumberInfo( const SwLineNumberInfo &rNew ) +{ + SwRootFrame* pTmpRoot = getIDocumentLayoutAccess().GetCurrentLayout(); + if ( pTmpRoot && + (rNew.IsCountBlankLines() != mpLineNumberInfo->IsCountBlankLines() || + rNew.IsRestartEachPage() != mpLineNumberInfo->IsRestartEachPage()) ) + { + pTmpRoot->StartAllAction(); + // FME 2007-08-14 #i80120# Invalidate size, because ChgThisLines() + // is only (and may only be) called by the formatting routines + //pTmpRoot->InvalidateAllContent( SwInvalidateFlags::LineNum | SwInvalidateFlags::Size ); + for( auto aLayout : GetAllLayouts() ) + aLayout->InvalidateAllContent( SwInvalidateFlags::LineNum | SwInvalidateFlags::Size ); + pTmpRoot->EndAllAction(); + } + *mpLineNumberInfo = rNew; + getIDocumentState().SetModified(); +} + +const SwLineNumberInfo& SwDoc::GetLineNumberInfo() const +{ + return *mpLineNumberInfo; +} + +SwLineNumberInfo::SwLineNumberInfo() : + m_nPosFromLeft( MM50 ), + m_nCountBy( 5 ), + m_nDividerCountBy( 3 ), + m_ePos( LINENUMBER_POS_LEFT ), + m_bPaintLineNumbers( false ), + m_bCountBlankLines( true ), + m_bCountInFlys( false ), + m_bRestartEachPage( false ) +{ +} + +SwLineNumberInfo::SwLineNumberInfo(const SwLineNumberInfo &rCpy ) : SwClient(), + m_aType( rCpy.GetNumType() ), + m_aDivider( rCpy.GetDivider() ), + m_nPosFromLeft( rCpy.GetPosFromLeft() ), + m_nCountBy( rCpy.GetCountBy() ), + m_nDividerCountBy( rCpy.GetDividerCountBy() ), + m_ePos( rCpy.GetPos() ), + m_bPaintLineNumbers( rCpy.IsPaintLineNumbers() ), + m_bCountBlankLines( rCpy.IsCountBlankLines() ), + m_bCountInFlys( rCpy.IsCountInFlys() ), + m_bRestartEachPage( rCpy.IsRestartEachPage() ) +{ + StartListeningToSameModifyAs(rCpy); +} + +SwLineNumberInfo& SwLineNumberInfo::operator=(const SwLineNumberInfo &rCpy) +{ + StartListeningToSameModifyAs(rCpy); + + m_aType = rCpy.GetNumType(); + m_aDivider = rCpy.GetDivider(); + m_nPosFromLeft = rCpy.GetPosFromLeft(); + m_nCountBy = rCpy.GetCountBy(); + m_nDividerCountBy = rCpy.GetDividerCountBy(); + m_ePos = rCpy.GetPos(); + m_bPaintLineNumbers = rCpy.IsPaintLineNumbers(); + m_bCountBlankLines = rCpy.IsCountBlankLines(); + m_bCountInFlys = rCpy.IsCountInFlys(); + m_bRestartEachPage = rCpy.IsRestartEachPage(); + + return *this; +} + +SwCharFormat* SwLineNumberInfo::GetCharFormat( IDocumentStylePoolAccess& rIDSPA ) const +{ + if ( !GetRegisteredIn() ) + { + SwCharFormat* pFormat = rIDSPA.GetCharFormatFromPool( RES_POOLCHR_LINENUM ); + pFormat->Add( const_cast<SwLineNumberInfo*>(this) ); + } + return const_cast<SwCharFormat*>(static_cast<const SwCharFormat*>(GetRegisteredIn())); +} + +void SwLineNumberInfo::SetCharFormat( SwCharFormat *pChFormat ) +{ + OSL_ENSURE( pChFormat, "SetCharFormat, 0 is not a valid pointer" ); + pChFormat->Add( this ); +} + +void SwLineNumberInfo::Modify( const SfxPoolItem* pOld, const SfxPoolItem* /*pNew*/ ) +{ + CheckRegistration( pOld ); + SwDoc *pDoc = static_cast<SwCharFormat*>(GetRegisteredIn())->GetDoc(); + SwRootFrame* pRoot = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRoot ) + { + pRoot->StartAllAction(); + for( auto aLayout : pDoc->GetAllLayouts() ) + aLayout->AllAddPaintRect(); + pRoot->EndAllAction(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/list.cxx b/sw/source/core/doc/list.cxx new file mode 100644 index 000000000..a87570131 --- /dev/null +++ b/sw/source/core/doc/list.cxx @@ -0,0 +1,274 @@ +/* -*- 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 <list.hxx> + +#include <vector> +#include <numrule.hxx> +#include <ndarr.hxx> +#include <node.hxx> +#include <pam.hxx> +#include <SwNodeNum.hxx> + +// implementation class for SwList +class SwListImpl +{ + public: + SwListImpl( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ); + ~SwListImpl() COVERITY_NOEXCEPT_FALSE; + + const OUString& GetListId() const { return msListId;} + + const OUString& GetDefaultListStyleName() const { return msDefaultListStyleName;} + + void InsertListItem( SwNodeNum& rNodeNum, bool isHiddenRedlines, + const int nLevel ); + static void RemoveListItem( SwNodeNum& rNodeNum ); + + void InvalidateListTree(); + void ValidateListTree(); + + void MarkListLevel( const int nListLevel, + const bool bValue ); + + bool IsListLevelMarked( const int nListLevel ) const; + + // unique identifier of the list + const OUString msListId; + // default list style for the list items, identified by the list style name + OUString msDefaultListStyleName; + + // list trees for certain document ranges + struct tListTreeForRange + { + /// tree always corresponds to document model + std::unique_ptr<SwNodeNum> pRoot; + /// Tree that is missing those nodes that are merged or hidden + /// by delete redlines; this is only used if there is a layout + /// that has IsHideRedlines() enabled. + /// A second tree is needed because not only are the numbers in + /// the nodes different, the structure of the tree may be different + /// as well, if a high-level node is hidden its children go under + /// the previous node on the same level. + /// The nodes of pRootRLHidden are a subset of the nodes of pRoot. + std::unique_ptr<SwNodeNum> pRootRLHidden; + /// top-level SwNodes section + std::unique_ptr<SwPaM> pSection; + tListTreeForRange(std::unique_ptr<SwNodeNum> p1, std::unique_ptr<SwNodeNum> p2, std::unique_ptr<SwPaM> p3) + : pRoot(std::move(p1)), pRootRLHidden(std::move(p2)), pSection(std::move(p3)) {} + }; + std::vector<tListTreeForRange> maListTrees; + + int mnMarkedListLevel; + + void NotifyItemsOnListLevel( const int nLevel ); +}; + +SwListImpl::SwListImpl( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ) + : msListId( sListId ), + msDefaultListStyleName( rDefaultListStyle.GetName() ), + maListTrees(), + mnMarkedListLevel( MAXLEVEL ) +{ + // create empty list trees for the document ranges + const SwNode* pNode = rNodes[0]; + do + { + SwPaM aPam( *pNode, *pNode->EndOfSectionNode() ); + + maListTrees.emplace_back( + std::make_unique<SwNodeNum>( &rDefaultListStyle ), + std::make_unique<SwNodeNum>( &rDefaultListStyle ), + std::make_unique<SwPaM>( *(aPam.Start()), *(aPam.End()) )); + + pNode = pNode->EndOfSectionNode(); + if (pNode != &rNodes.GetEndOfContent()) + { + sal_uLong nIndex = pNode->GetIndex(); + nIndex++; + pNode = rNodes[nIndex]; + } + } + while ( pNode != &rNodes.GetEndOfContent() ); +} + +SwListImpl::~SwListImpl() COVERITY_NOEXCEPT_FALSE +{ + for ( auto& rNumberTree : maListTrees ) + { + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRoot)); + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRootRLHidden)); + } +} + +void SwListImpl::InsertListItem( SwNodeNum& rNodeNum, bool const isHiddenRedlines, + const int nLevel ) +{ + const SwPosition aPosOfNodeNum( rNodeNum.GetPosition() ); + const SwNodes* pNodesOfNodeNum = &(aPosOfNodeNum.nNode.GetNode().GetNodes()); + + for ( const auto& rNumberTree : maListTrees ) + { + const SwPosition* pStart = rNumberTree.pSection->Start(); + const SwPosition* pEnd = rNumberTree.pSection->End(); + const SwNodes* pRangeNodes = &(pStart->nNode.GetNode().GetNodes()); + + if ( pRangeNodes == pNodesOfNodeNum && + *pStart <= aPosOfNodeNum && aPosOfNodeNum <= *pEnd) + { + auto const& pRoot(isHiddenRedlines + ? rNumberTree.pRootRLHidden + : rNumberTree.pRoot); + pRoot->AddChild(&rNodeNum, nLevel); + break; + } + } +} + +void SwListImpl::RemoveListItem( SwNodeNum& rNodeNum ) +{ + rNodeNum.RemoveMe(); +} + +void SwListImpl::InvalidateListTree() +{ + for ( const auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->InvalidateTree(); + rNumberTree.pRootRLHidden->InvalidateTree(); + } +} + +void SwListImpl::ValidateListTree() +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyInvalidChildren(); + rNumberTree.pRootRLHidden->NotifyInvalidChildren(); + } +} + +void SwListImpl::MarkListLevel( const int nListLevel, + const bool bValue ) +{ + if ( bValue ) + { + if ( nListLevel != mnMarkedListLevel ) + { + if ( mnMarkedListLevel != MAXLEVEL ) + { + // notify former marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + + mnMarkedListLevel = nListLevel; + + // notify new marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + } + else + { + if ( mnMarkedListLevel != MAXLEVEL ) + { + // notify former marked list nodes + NotifyItemsOnListLevel( mnMarkedListLevel ); + } + + mnMarkedListLevel = MAXLEVEL; + } +} + +bool SwListImpl::IsListLevelMarked( const int nListLevel ) const +{ + return nListLevel == mnMarkedListLevel; +} + +void SwListImpl::NotifyItemsOnListLevel( const int nLevel ) +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyNodesOnListLevel( nLevel ); + rNumberTree.pRootRLHidden->NotifyNodesOnListLevel( nLevel ); + } +} + +SwList::SwList( const OUString& sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ) + : mpListImpl( new SwListImpl( sListId, rDefaultListStyle, rNodes ) ) +{ +} + +SwList::~SwList() +{ +} + +const OUString & SwList::GetListId() const +{ + return mpListImpl->GetListId(); +} + +const OUString & SwList::GetDefaultListStyleName() const +{ + return mpListImpl->GetDefaultListStyleName(); +} + +void SwList::SetDefaultListStyleName(OUString const& rNew) +{ + mpListImpl->msDefaultListStyleName = rNew; +} + +void SwList::InsertListItem( SwNodeNum& rNodeNum, bool const isHiddenRedlines, + const int nLevel ) +{ + mpListImpl->InsertListItem( rNodeNum, isHiddenRedlines, nLevel ); +} + +void SwList::RemoveListItem( SwNodeNum& rNodeNum ) +{ + SwListImpl::RemoveListItem( rNodeNum ); +} + +void SwList::InvalidateListTree() +{ + mpListImpl->InvalidateListTree(); +} + +void SwList::ValidateListTree() +{ + mpListImpl->ValidateListTree(); +} + +void SwList::MarkListLevel( const int nListLevel, + const bool bValue ) +{ + mpListImpl->MarkListLevel( nListLevel, bValue ); +} + +bool SwList::IsListLevelMarked( const int nListLevel ) const +{ + return mpListImpl->IsListLevelMarked( nListLevel ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/notxtfrm.cxx b/sw/source/core/doc/notxtfrm.cxx new file mode 100644 index 000000000..550f0df50 --- /dev/null +++ b/sw/source/core/doc/notxtfrm.cxx @@ -0,0 +1,1549 @@ +/* -*- 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 <tools/urlobj.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/imap.hxx> +#include <svl/urihelper.hxx> +#include <sfx2/progress.hxx> +#include <sfx2/printer.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/boxitem.hxx> +#include <fmturl.hxx> +#include <fmtsrnd.hxx> +#include <frmfmt.hxx> +#include <swrect.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <flyfrm.hxx> +#include <flyfrms.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <viewimp.hxx> +#include <pam.hxx> +#include <hints.hxx> +#include <rootfrm.hxx> +#include <dflyobj.hxx> +#include <pagefrm.hxx> +#include <notxtfrm.hxx> +#include <grfatr.hxx> +#include <charatr.hxx> +#include <ndnotxt.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <swregion.hxx> +#include <poolfmt.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> +#include <accessibilityoptions.hxx> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <svtools/embedhlp.hxx> +#include <dview.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/b2dclipstate.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <txtfly.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> + +// MM02 needed for VOC mechanism and getting the OC - may be moved to an own file +#include <svx/sdrpagewindow.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/sdr/contact/displayinfo.hxx> + +using namespace com::sun::star; + +static bool GetRealURL( const SwGrfNode& rNd, OUString& rText ) +{ + bool bRet = rNd.GetFileFilterNms( &rText, nullptr ); + if( bRet ) + rText = URIHelper::removePassword( rText, INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::Unambiguous); + if (rText.startsWith("data:image")) rText = "inline image"; + + return bRet; +} + +static void lcl_PaintReplacement( const SwRect &rRect, const OUString &rText, + const SwViewShell &rSh, const SwNoTextFrame *pFrame, + bool bDefect ) +{ + static vcl::Font aFont = [&]() + { + vcl::Font tmp; + tmp.SetWeight( WEIGHT_BOLD ); + tmp.SetStyleName( OUString() ); + tmp.SetFamilyName("Arial Unicode"); + tmp.SetFamily( FAMILY_SWISS ); + tmp.SetTransparent( true ); + return tmp; + }(); + + Color aCol( COL_RED ); + FontLineStyle eUnderline = LINESTYLE_NONE; + const SwFormatURL &rURL = pFrame->FindFlyFrame()->GetFormat()->GetURL(); + if( !rURL.GetURL().isEmpty() || rURL.GetMap() ) + { + bool bVisited = false; + if ( rURL.GetMap() ) + { + ImageMap *pMap = const_cast<ImageMap*>(rURL.GetMap()); + for( size_t i = 0; i < pMap->GetIMapObjectCount(); ++i ) + { + IMapObject *pObj = pMap->GetIMapObject( i ); + if( rSh.GetDoc()->IsVisitedURL( pObj->GetURL() ) ) + { + bVisited = true; + break; + } + } + } + else if ( !rURL.GetURL().isEmpty() ) + bVisited = rSh.GetDoc()->IsVisitedURL( rURL.GetURL() ); + + SwFormat *pFormat = rSh.GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( static_cast<sal_uInt16> + (bVisited ? RES_POOLCHR_INET_VISIT : RES_POOLCHR_INET_NORMAL ) ); + aCol = pFormat->GetColor().GetValue(); + eUnderline = pFormat->GetUnderline().GetLineStyle(); + } + + aFont.SetUnderline( eUnderline ); + aFont.SetColor( aCol ); + + const BitmapEx& rBmp = const_cast<SwViewShell&>(rSh).GetReplacementBitmap(bDefect); + Graphic::DrawEx( rSh.GetOut(), rText, aFont, rBmp, rRect.Pos(), rRect.SSize() ); +} + +SwNoTextFrame::SwNoTextFrame(SwNoTextNode * const pNode, SwFrame* pSib ) +: SwContentFrame( pNode, pSib ), + // RotateFlyFrame3 + mpTransformableSwFrame(), + // MM02 + mpViewContact() +{ + mnFrameType = SwFrameType::NoTxt; +} + +SwContentFrame *SwNoTextNode::MakeFrame( SwFrame* pSib ) +{ + return new SwNoTextFrame(this, pSib); +} + +void SwNoTextFrame::DestroyImpl() +{ + StopAnimation(); + + SwContentFrame::DestroyImpl(); +} + +SwNoTextFrame::~SwNoTextFrame() +{ +} + +void SetOutDev( SwViewShell *pSh, OutputDevice *pOut ) +{ + pSh->mpOut = pOut; +} + +static void lcl_ClearArea( const SwFrame &rFrame, + vcl::RenderContext &rOut, const SwRect& rPtArea, + const SwRect &rGrfArea ) +{ + SwRegionRects aRegion( rPtArea, 4 ); + aRegion -= rGrfArea; + + if ( !aRegion.empty() ) + { + const SvxBrushItem *pItem; + const Color *pCol; + SwRect aOrigRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( rFrame.GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigRect, false, /*bConsiderTextBox=*/false ) ) + { + SwRegionRects const region(rPtArea); + basegfx::utils::B2DClipState aClipState; + const bool bDone(::DrawFillAttributes(aFillAttributes, aOrigRect, region, aClipState, rOut)); + + if(!bDone) + { + for( const auto &rRegion : aRegion ) + { + ::DrawGraphic( pItem, &rOut, aOrigRect, rRegion ); + } + } + } + else + { + rOut.Push( PushFlags::FILLCOLOR|PushFlags::LINECOLOR ); + rOut.SetFillColor( rFrame.getRootFrame()->GetCurrShell()->Imp()->GetRetoucheColor()); + rOut.SetLineColor(); + for( const auto &rRegion : aRegion ) + rOut.DrawRect( rRegion.SVRect() ); + rOut.Pop(); + } + } +} + +void SwNoTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + if ( getFrameArea().IsEmpty() ) + return; + + const SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( !pSh->GetViewOptions()->IsGraphic() ) + { + StopAnimation(); + // #i6467# - no paint of placeholder for page preview + if ( pSh->GetWin() && !pSh->IsPreview() ) + { + const SwNoTextNode* pNd = GetNode()->GetNoTextNode(); + OUString aText( pNd->GetTitle() ); + if ( aText.isEmpty() && pNd->IsGrfNode() ) + GetRealURL( *static_cast<const SwGrfNode*>(pNd), aText ); + if( aText.isEmpty() ) + aText = FindFlyFrame()->GetFormat()->GetName(); + lcl_PaintReplacement( getFrameArea(), aText, *pSh, this, false ); + } + return; + } + + if( pSh->GetAccessibilityOptions()->IsStopAnimatedGraphics() || + // #i9684# Stop animation during printing/pdf export + !pSh->GetWin() ) + StopAnimation(); + + SfxProgress::EnterLock(); // No progress reschedules in paint (SwapIn) + + rRenderContext.Push(); + bool bClip = true; + tools::PolyPolygon aPoly; + + SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode())); + SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); + if( pGrfNd ) + pGrfNd->SetFrameInPaint( true ); + + // #i13147# - add 2nd parameter with value <true> to + // method call <FindFlyFrame().GetContour(..)> to indicate that it is called + // for paint in order to avoid load of the intrinsic graphic. + if ( ( !rRenderContext.GetConnectMetaFile() || + !pSh->GetWin() ) && + FindFlyFrame()->GetContour( aPoly, true ) + ) + { + rRenderContext.SetClipRegion(vcl::Region(aPoly)); + bClip = false; + } + + SwRect aOrigPaint( rRect ); + if ( HasAnimation() && pSh->GetWin() ) + { + aOrigPaint = getFrameArea(); aOrigPaint += getFramePrintArea().Pos(); + } + + SwRect aGrfArea( getFrameArea() ); + SwRect aPaintArea( aGrfArea ); + + // In case the picture fly frm was clipped, render it with the origin + // size instead of scaling it + if ( pGrfNd && rNoTNd.getIDocumentSettingAccess()->get( DocumentSettingId::CLIPPED_PICTURES ) ) + { + const SwFlyFreeFrame *pFly = dynamic_cast< const SwFlyFreeFrame* >( FindFlyFrame() ); + if( pFly ) + { + bool bGetUnclippedFrame=true; + const SfxPoolItem* pItem; + if( pFly->GetFormat() && SfxItemState::SET == pFly->GetFormat()->GetItemState(RES_BOX, false, &pItem) ) + { + const SvxBoxItem& rBox = *static_cast<const SvxBoxItem*>(pItem); + if( rBox.HasBorder( /*bTreatPaddingAsBorder*/true) ) + bGetUnclippedFrame = false; + } + + if( bGetUnclippedFrame ) + aGrfArea = SwRect( getFrameArea().Pos( ), pFly->GetUnclippedFrame( ).SSize( ) ); + } + } + + aPaintArea.Intersection_( aOrigPaint ); + + SwRect aNormal( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + aNormal.Justify(); // Normalized rectangle for the comparisons + + if( aPaintArea.IsOver( aNormal ) ) + { + // Calculate the four to-be-deleted rectangles + if( pSh->GetWin() ) + ::lcl_ClearArea( *this, rRenderContext, aPaintArea, aNormal ); + + // The intersection of the PaintArea and the Bitmap contains the absolutely visible area of the Frame + aPaintArea.Intersection_( aNormal ); + + if ( bClip ) + rRenderContext.IntersectClipRegion( aPaintArea.SVRect() ); + /// delete unused 3rd parameter + PaintPicture( &rRenderContext, aGrfArea ); + } + else + // If it's not visible, simply delete the given Area + lcl_ClearArea( *this, rRenderContext, aPaintArea, SwRect() ); + if( pGrfNd ) + pGrfNd->SetFrameInPaint( false ); + + rRenderContext.Pop(); + SfxProgress::LeaveLock(); +} + +/** Calculate the position and the size of the graphic in the Frame, + corresponding to the current graphic attributes + + @param Point the position in the Frame (also returned) + @param Size the graphic's size (also returned) + @param nMirror the current mirror attribute +*/ +static void lcl_CalcRect( Point& rPt, Size& rDim, MirrorGraph nMirror ) +{ + if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both ) + { + rPt.setX(rPt.getX() + rDim.Width() -1); + rDim.setWidth( -rDim.Width() ); + } + + if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both ) + { + rPt.setY(rPt.getY() + rDim.Height() -1); + rDim.setHeight( -rDim.Height() ); + } +} + +/** Calculate the Bitmap's position and the size within the passed rectangle */ +void SwNoTextFrame::GetGrfArea( SwRect &rRect, SwRect* pOrigRect ) const +{ + // Currently only used for scaling, cropping and mirroring the contour of graphics! + // Everything else is handled by GraphicObject + // We put the graphic's visible rectangle into rRect. + // pOrigRect contains position and size of the whole graphic. + + // RotateFlyFrame3: SwFrame may be transformed. Get untransformed + // SwRect(s) as base of calculation + const TransformableSwFrame* pTransformableSwFrame(getTransformableSwFrame()); + const SwRect aFrameArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFrameArea() : getFrameArea()); + const SwRect aFramePrintArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFramePrintArea() : getFramePrintArea()); + + const SwAttrSet& rAttrSet = GetNode()->GetSwAttrSet(); + const SwCropGrf& rCrop = rAttrSet.GetCropGrf(); + MirrorGraph nMirror = rAttrSet.GetMirrorGrf().GetValue(); + + if( rAttrSet.GetMirrorGrf().IsGrfToggle() ) + { + if( !(FindPageFrame()->GetVirtPageNum() % 2) ) + { + switch ( nMirror ) + { + case MirrorGraph::Dont: nMirror = MirrorGraph::Vertical; break; + case MirrorGraph::Vertical: nMirror = MirrorGraph::Dont; break; + case MirrorGraph::Horizontal: nMirror = MirrorGraph::Both; break; + default: nMirror = MirrorGraph::Horizontal; break; + } + } + } + + // We read graphic from the Node, if needed. + // It may fail, however. + long nLeftCrop, nRightCrop, nTopCrop, nBottomCrop; + Size aOrigSz( static_cast<const SwNoTextNode*>(GetNode())->GetTwipSize() ); + if ( !aOrigSz.Width() ) + { + aOrigSz.setWidth( aFramePrintArea.Width() ); + nLeftCrop = -rCrop.GetLeft(); + nRightCrop = -rCrop.GetRight(); + } + else + { + nLeftCrop = std::max( aOrigSz.Width() - + (rCrop.GetRight() + rCrop.GetLeft()), long(1) ); + const double nScale = double(aFramePrintArea.Width()) / double(nLeftCrop); + nLeftCrop = long(nScale * -rCrop.GetLeft() ); + nRightCrop = long(nScale * -rCrop.GetRight() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both ) + { + long nTmpCrop = nLeftCrop; + nLeftCrop = nRightCrop; + nRightCrop= nTmpCrop; + } + + if( !aOrigSz.Height() ) + { + aOrigSz.setHeight( aFramePrintArea.Height() ); + nTopCrop = -rCrop.GetTop(); + nBottomCrop= -rCrop.GetBottom(); + } + else + { + nTopCrop = std::max( aOrigSz.Height() - (rCrop.GetTop() + rCrop.GetBottom()), long(1) ); + const double nScale = double(aFramePrintArea.Height()) / double(nTopCrop); + nTopCrop = long(nScale * -rCrop.GetTop() ); + nBottomCrop= long(nScale * -rCrop.GetBottom() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both ) + { + long nTmpCrop = nTopCrop; + nTopCrop = nBottomCrop; + nBottomCrop= nTmpCrop; + } + + Size aVisSz( aFramePrintArea.SSize() ); + Size aGrfSz( aVisSz ); + Point aVisPt( aFrameArea.Pos() + aFramePrintArea.Pos() ); + Point aGrfPt( aVisPt ); + + // Set the "visible" rectangle first + if ( nLeftCrop > 0 ) + { + aVisPt.setX(aVisPt.getX() + nLeftCrop); + aVisSz.AdjustWidth( -nLeftCrop ); + } + if ( nTopCrop > 0 ) + { + aVisPt.setY(aVisPt.getY() + nTopCrop); + aVisSz.AdjustHeight( -nTopCrop ); + } + if ( nRightCrop > 0 ) + aVisSz.AdjustWidth( -nRightCrop ); + if ( nBottomCrop > 0 ) + aVisSz.AdjustHeight( -nBottomCrop ); + + rRect.Pos ( aVisPt ); + rRect.SSize( aVisSz ); + + // Calculate the whole graphic if needed + if ( pOrigRect ) + { + Size aTmpSz( aGrfSz ); + aGrfPt.setX(aGrfPt.getX() + nLeftCrop); + aTmpSz.AdjustWidth( -(nLeftCrop + nRightCrop) ); + aGrfPt.setY(aGrfPt.getY() + nTopCrop); + aTmpSz.AdjustHeight( -(nTopCrop + nBottomCrop) ); + + if( MirrorGraph::Dont != nMirror ) + lcl_CalcRect( aGrfPt, aTmpSz, nMirror ); + + pOrigRect->Pos ( aGrfPt ); + pOrigRect->SSize( aTmpSz ); + } +} + +/** By returning the surrounding Fly's size which equals the graphic's size */ +const Size& SwNoTextFrame::GetSize() const +{ + // Return the Frame's size + const SwFrame *pFly = FindFlyFrame(); + if( !pFly ) + pFly = this; + return pFly->getFramePrintArea().SSize(); +} + +void SwNoTextFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + // RotateFlyFrame3 - inner frame. Get rotation and check if used + const double fRotation(getLocalFrameRotation()); + const bool bRotated(!basegfx::fTools::equalZero(fRotation)); + + if(bRotated) + { + SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper())); + + if(pUpperFly) + { + if(!pUpperFly->isFrameAreaDefinitionValid()) + { + // RotateFlyFrame3: outer frame *needs* to be layouted first, force this by calling + // it's ::Calc directly + pUpperFly->Calc(pRenderContext); + } + + // Reset outer frame to unrotated state. This is necessary to make the + // layouting below work as currently implemented in Writer. As expected + // using Transformations allows to do this on the fly due to all information + // being included there. + // The full solution would be to adapt the whole layouting + // process of Writer to take care of Transformations, but that + // is currently beyond scope + if(pUpperFly->isTransformableSwFrame()) + { + pUpperFly->getTransformableSwFrame()->restoreFrameAreas(); + } + } + + // Re-layout may be partially (see all isFrameAreaDefinitionValid() flags), + // so resetting the local SwFrame(s) in the local SwFrameAreaDefinition is also + // needed (e.g. for PrintPreview). + // Reset to BoundAreas will be done below automatically + if(isTransformableSwFrame()) + { + getTransformableSwFrame()->restoreFrameAreas(); + } + } + + SwContentNotify aNotify( this ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + MakePos(); + + if ( !isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( GetUpper()->getFramePrintArea().Width() ); + } + + MakePrtArea( rAttrs ); + + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + Format(getRootFrame()->GetCurrShell()->GetOut()); + } + } + + // RotateFlyFrame3 - inner frame + if(bRotated) + { + SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper())); + + if(pUpperFly) + { + // restore outer frame back to Transformed state, that means + // set the SwFrameAreaDefinition(s) back to BoundAreas of + // the transformed SwFrame. All needed information is part + // of the already correctly created Transformations of the + // upper frame, so it can be re-created on the fly + if(pUpperFly->isTransformableSwFrame()) + { + pUpperFly->getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + } + + // After the unrotated layout is finished, apply possible set rotation to it + // get center from outer frame (layout frame) to be on the safe side + const Point aCenter(GetUpper() ? GetUpper()->getFrameArea().Center() : getFrameArea().Center()); + const basegfx::B2DPoint aB2DCenter(aCenter.X(), aCenter.Y()); + + if(!mpTransformableSwFrame) + { + mpTransformableSwFrame.reset(new TransformableSwFrame(*this)); + } + + getTransformableSwFrame()->createFrameAreaTransformations( + fRotation, + aB2DCenter); + getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + else + { + // reset transformations to show that they are not used + mpTransformableSwFrame.reset(); + } +} + +// RotateFlyFrame3 - Support for Transformations - outer frame +basegfx::B2DHomMatrix SwNoTextFrame::getFrameAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFrameAreaTransformation(); + } + + // call parent + return SwContentFrame::getFrameAreaTransformation(); +} + +basegfx::B2DHomMatrix SwNoTextFrame::getFramePrintAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFramePrintAreaTransformation(); + } + + // call parent + return SwContentFrame::getFramePrintAreaTransformation(); +} + +// RotateFlyFrame3 - Support for Transformations +void SwNoTextFrame::transform_translate(const Point& rOffset) +{ + // call parent - this will do the basic transform for SwRect(s) + // in the SwFrameAreaDefinition + SwContentFrame::transform_translate(rOffset); + + // check if the Transformations need to be adapted + if(isTransformableSwFrame()) + { + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix( + rOffset.X(), rOffset.Y())); + + // transform using TransformableSwFrame + getTransformableSwFrame()->transform(aTransform); + } +} + +// RotateFlyFrame3 - inner frame +// Check if we contain a SwGrfNode and get possible rotation from it +double SwNoTextFrame::getLocalFrameRotation() const +{ + const SwNoTextNode* pSwNoTextNode(nullptr != GetNode() ? GetNode()->GetNoTextNode() : nullptr); + + if(nullptr != pSwNoTextNode) + { + const SwGrfNode* pSwGrfNode(pSwNoTextNode->GetGrfNode()); + + if(nullptr != pSwGrfNode) + { + const SwAttrSet& rSwAttrSet(pSwGrfNode->GetSwAttrSet()); + const SwRotationGrf& rSwRotationGrf(rSwAttrSet.GetRotationGrf()); + const double fRotate(static_cast< double >(-rSwRotationGrf.GetValue()) * (M_PI/1800.0)); + + return basegfx::normalizeToRange(fRotate, F_2PI); + } + } + + // no rotation + return 0.0; +} + +/** Calculate the Bitmap's site, if needed */ +void SwNoTextFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + const Size aNewSize( GetSize() ); + + // Did the height change? + SwTwips nChgHght = IsVertical() ? + static_cast<SwTwips>(aNewSize.Width() - getFramePrintArea().Width()) : + static_cast<SwTwips>(aNewSize.Height() - getFramePrintArea().Height()); + if( nChgHght > 0) + Grow( nChgHght ); + else if( nChgHght < 0) + Shrink( std::min(getFramePrintArea().Height(), -nChgHght) ); +} + +bool SwNoTextFrame::GetCharRect( SwRect &rRect, const SwPosition& rPos, + SwCursorMoveState *pCMS, bool /*bAllowFarAway*/ ) const +{ + if ( &rPos.nNode.GetNode() != static_cast<SwNode const *>(GetNode()) ) + return false; + + Calc(getRootFrame()->GetCurrShell()->GetOut()); + SwRect aFrameRect( getFrameArea() ); + rRect = aFrameRect; + rRect.Pos( getFrameArea().Pos() + getFramePrintArea().Pos() ); + rRect.SSize( getFramePrintArea().SSize() ); + + rRect.Justify(); + + // Is the Bitmap in the visible area at all? + if( !aFrameRect.IsOver( rRect ) ) + { + // If not, then the Cursor is on the Frame + rRect = aFrameRect; + rRect.Width( 1 ); + } + else + rRect.Intersection_( aFrameRect ); + + if ( pCMS && pCMS->m_bRealHeight ) + { + pCMS->m_aRealHeight.setY(rRect.Height()); + pCMS->m_aRealHeight.setX(0); + } + + return true; +} + +bool SwNoTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& , + SwCursorMoveState*, bool ) const +{ + SwContentNode* pCNd = const_cast<SwContentNode*>(GetNode()); + pPos->nNode = *pCNd; + pPos->nContent.Assign( pCNd, 0 ); + return true; +} + +void SwNoTextFrame::ClearCache() +{ + SwFlyFrame* pFly = FindFlyFrame(); + if( pFly && pFly->GetFormat()->GetSurround().IsContour() ) + { + ClrContourCache( pFly->GetVirtDrawObj() ); + pFly->NotifyBackground( FindPageFrame(), getFramePrintArea(), PrepareHint::FlyFrameAttributesChanged ); + } +} + +void SwNoTextFrame::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + sal_uInt16 nWhich = pNew ? pNew->Which() : pOld ? pOld->Which() : 0; + + // #i73788# + // no <SwContentFrame::Modify(..)> for RES_LINKED_GRAPHIC_STREAM_ARRIVED + if ( RES_GRAPHIC_PIECE_ARRIVED != nWhich && + RES_GRAPHIC_ARRIVED != nWhich && + RES_GRF_REREAD_AND_INCACHE != nWhich && + RES_LINKED_GRAPHIC_STREAM_ARRIVED != nWhich ) + { + SwContentFrame::Modify( pOld, pNew ); + } + + bool bComplete = true; + + switch( nWhich ) + { + case RES_OBJECTDYING: + break; + + case RES_GRF_REREAD_AND_INCACHE: + if( SwNodeType::Grf == GetNode()->GetNodeType() ) + { + // TODO: Remove - due to GraphicObject refactoring + bComplete = false; + } + break; + + case RES_UPDATE_ATTR: + if (GetNode()->GetNodeType() != SwNodeType::Grf) { + break; + } + [[fallthrough]]; + case RES_FMT_CHG: + ClearCache(); + break; + + case RES_ATTRSET_CHG: + { + sal_uInt16 n; + for( n = RES_GRFATR_BEGIN; n < RES_GRFATR_END; ++n ) + if( SfxItemState::SET == static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()-> + GetItemState( n, false )) + { + ClearCache(); + + if(RES_GRFATR_ROTATION == n) + { + // RotGrfFlyFrame: Update Handles in view, these may be rotation-dependent + // (e.g. crop handles) and need a visualisation update + if ( GetNode()->GetNodeType() == SwNodeType::Grf ) + { + SwGrfNode* pNd = static_cast<SwGrfNode*>( GetNode()); + SwViewShell *pVSh = pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + + if(pVSh) + { + SdrView* pDrawView = pVSh->GetDrawView(); + + if(pDrawView) + { + pDrawView->AdjustMarkHdl(nullptr); + } + } + + // RotateFlyFrame3 - invalidate needed for ContentFrame (inner, this) + // and LayoutFrame (outer, GetUpper). It is possible to only invalidate + // the outer frame, but that leads to an in-between state that gets + // potentially painted + if(GetUpper()) + { + GetUpper()->InvalidateAll_(); + } + + InvalidateAll_(); + } + } + break; + } + if( RES_GRFATR_END == n ) // not found + return ; + } + break; + + case RES_GRAPHIC_PIECE_ARRIVED: + case RES_GRAPHIC_ARRIVED: + // i73788# - handle RES_LINKED_GRAPHIC_STREAM_ARRIVED as RES_GRAPHIC_ARRIVED + case RES_LINKED_GRAPHIC_STREAM_ARRIVED: + if ( GetNode()->GetNodeType() == SwNodeType::Grf ) + { + bComplete = false; + SwGrfNode* pNd = static_cast<SwGrfNode*>( GetNode()); + + ClearCache(); + + SwRect aRect( getFrameArea() ); + + SwViewShell *pVSh = pNd->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( !pVSh ) + break; + + for(SwViewShell& rShell : pVSh->GetRingContainer()) + { + SET_CURR_SHELL( &rShell ); + if( rShell.IsPreview() ) + { + if( rShell.GetWin() ) + ::RepaintPagePreview( &rShell, aRect ); + } + else if ( rShell.VisArea().IsOver( aRect ) && + OUTDEV_WINDOW == rShell.GetOut()->GetOutDevType() ) + { + // invalidate instead of painting + rShell.GetWin()->Invalidate( aRect.SVRect() ); + } + } + } + break; + + default: + if ( !pNew || !isGRFATR(nWhich) ) + return; + } + + if( bComplete ) + { + InvalidatePrt(); + SetCompletePaint(); + } +} + +static void lcl_correctlyAlignRect( SwRect& rAlignedGrfArea, const SwRect& rInArea, vcl::RenderContext const * pOut ) +{ + + if(!pOut) + return; + tools::Rectangle aPxRect = pOut->LogicToPixel( rInArea.SVRect() ); + tools::Rectangle aNewPxRect( aPxRect ); + while( aNewPxRect.Left() < aPxRect.Left() ) + { + rAlignedGrfArea.AddLeft( 1 ); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Top() < aPxRect.Top() ) + { + rAlignedGrfArea.AddTop(+1); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Bottom() > aPxRect.Bottom() ) + { + rAlignedGrfArea.AddBottom( -1 ); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } + while( aNewPxRect.Right() > aPxRect.Right() ) + { + rAlignedGrfArea.AddRight(-1); + aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() ); + } +} + +static bool paintUsingPrimitivesHelper( + vcl::RenderContext& rOutputDevice, + const drawinglayer::primitive2d::Primitive2DContainer& rSequence, + const basegfx::B2DRange& rSourceRange, + const basegfx::B2DRange& rTargetRange) +{ + if(!rSequence.empty() && !basegfx::fTools::equalZero(rSourceRange.getWidth()) && !basegfx::fTools::equalZero(rSourceRange.getHeight())) + { + if(!basegfx::fTools::equalZero(rTargetRange.getWidth()) && !basegfx::fTools::equalZero(rTargetRange.getHeight())) + { + // map graphic range to target range. This will e.g. automatically include + // the mapping from 1/100th mm content to twips if needed when the target + // range is defined in twips + const basegfx::B2DHomMatrix aMappingTransform( + basegfx::utils::createSourceRangeTargetRangeTransform( + rSourceRange, + rTargetRange)); + + // Fill ViewInformation. Use MappingTransform here, so there is no need to + // embed the primitives to it. Use original TargetRange here so there is also + // no need to embed the primitives to a MaskPrimitive for cropping. This works + // only in this case where the graphic object cannot be rotated, though. + const drawinglayer::geometry::ViewInformation2D aViewInformation2D( + aMappingTransform, + rOutputDevice.GetViewTransformation(), + rTargetRange, + nullptr, + 0.0, + uno::Sequence< beans::PropertyValue >()); + + // get a primitive processor for rendering + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D( + drawinglayer::processor2d::createProcessor2DFromOutputDevice( + rOutputDevice, aViewInformation2D) ); + if(pProcessor2D) + { + // render and cleanup + pProcessor2D->process(rSequence); + return true; + } + } + } + + return false; +} + +// MM02 original using falölback to VOC and primitive-based version +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + GraphicObject const& rGrfObj, + GraphicAttr const& rGraphicAttr, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription) +{ + // RotGrfFlyFrame: unify using GraphicPrimitive2D + // -> the primitive handles all crop and mirror stuff + // -> the primitive renderer will create the needed pdf export data + // -> if bitmap content, it will be cached system-dependent + drawinglayer::primitive2d::Primitive2DContainer aContent(1); + aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D( + rGraphicTransform, + rGrfObj, + rGraphicAttr); + + // MM02 use primitive-based version for visualization + paintGraphicUsingPrimitivesHelper( + rOutputDevice, + aContent, + rGraphicTransform, + rName, + rTitle, + rDescription); +} + +// MM02 new VOC and primitive-based version +void paintGraphicUsingPrimitivesHelper( + vcl::RenderContext & rOutputDevice, + drawinglayer::primitive2d::Primitive2DContainer& rContent, + const basegfx::B2DHomMatrix& rGraphicTransform, + const OUString& rName, + const OUString& rTitle, + const OUString& rDescription) +{ + // RotateFlyFrame3: If ClipRegion is set at OutputDevice, we + // need to use that. Usually the renderer would be a VCL-based + // PrimitiveRenderer, but there are system-specific shortcuts that + // will *not* use the VCL-Paint of Bitmap and thus ignore this. + // Anyways, indirectly using a CLipRegion set at the target OutDev + // when using a PrimitiveRenderer is a non-valid implication. + // First tried only to use when HasPolyPolygonOrB2DPolyPolygon(), + // but there is an optimization at ClipRegion creation that detects + // a single Rectangle in a tools::PolyPolygon and forces to a simple + // RegionBand-based implementation, so cannot use it here. + if(rOutputDevice.IsClipRegion()) + { + const basegfx::B2DPolyPolygon aClip(rOutputDevice.GetClipRegion().GetAsB2DPolyPolygon()); + + if(0 != aClip.count()) + { + // tdf#114076: Expand ClipRange to next PixelBound + // Do this by going to basegfx::B2DRange, adding a + // single pixel size and using floor/ceil to go to + // full integer (as needed for pixels). Also need + // to go back to basegfx::B2DPolyPolygon for the + // creation of the needed MaskPrimitive2D. + // The general problem is that Writer is scrolling + // using blitting the unchanged parts, this forces + // this part of the scroll to pixel coordinate steps, + // while the ViewTransformation for paint nowadays has + // a sub-pixel precision. This results in an offset + // up to one pixel in radius. To solve this for now, + // we need to expand to the next outer pixel bound. + // Hopefully in the future we will someday be able to + // stay on the full available precision, but this + // will need a change in the repaint/scroll paradigm. + const basegfx::B2DRange aClipRange(aClip.getB2DRange()); + const basegfx::B2DVector aSinglePixelXY(rOutputDevice.GetInverseViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + const basegfx::B2DRange aExpandedClipRange( + floor(aClipRange.getMinX() - aSinglePixelXY.getX()), + floor(aClipRange.getMinY() - aSinglePixelXY.getY()), + ceil(aClipRange.getMaxX() + aSinglePixelXY.getX()), + ceil(aClipRange.getMaxY() + aSinglePixelXY.getY())); + + // create the enclosing rectangle as polygon + basegfx::B2DPolyPolygon aTarget(basegfx::utils::createPolygonFromRect(aExpandedClipRange)); + + // tdf#124272 the fix above (tdf#114076) was too rough - the + // clip region used may be a PolyPolygon. In that case that + // PolyPolygon would have to be scaled to mentioned PixelBounds. + // Since that is not really possible geometrically (would need + // more some 'grow in outside direction' but with unequal grow + // values in all directions - just maaany problems + // involved), use a graphical trick: The topology of the + // PolyPolygon uses the standard FillRule, so adding the now + // guaranteed to be bigger or equal bounding (enclosing) + // rectangle twice as polygon will expand the BoundRange, but + // not change the geometry visualization at all + if(!rOutputDevice.GetClipRegion().IsRectangle()) + { + // double the outer rectangle range polygon to have it + // included twice + aTarget.append(aTarget.getB2DPolygon(0)); + + // add the original clip 'inside' (due to being smaller + // or equal). That PolyPolygon may have an unknown number + // of polygons (>=1) + aTarget.append(aClip); + } + + drawinglayer::primitive2d::MaskPrimitive2D* pNew( + new drawinglayer::primitive2d::MaskPrimitive2D( + aTarget, + rContent)); + rContent.resize(1); + rContent[0] = pNew; + } + } + + if(!rName.isEmpty() || !rTitle.isEmpty() || !rDescription.isEmpty()) + { + // Embed to ObjectInfoPrimitive2D when we have Name/Title/Description + // information available + drawinglayer::primitive2d::ObjectInfoPrimitive2D* pNew( + new drawinglayer::primitive2d::ObjectInfoPrimitive2D( + rContent, + rName, + rTitle, + rDescription)); + rContent.resize(1); + rContent[0] = pNew; + } + + basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0); + aTargetRange.transform(rGraphicTransform); + + paintUsingPrimitivesHelper( + rOutputDevice, + rContent, + aTargetRange, + aTargetRange); +} + +// DrawContact section +namespace { // anonymous namespace +class ViewObjectContactOfSwNoTextFrame : public sdr::contact::ViewObjectContact +{ +protected: + virtual drawinglayer::primitive2d::Primitive2DContainer createPrimitive2DSequence( + const sdr::contact::DisplayInfo& rDisplayInfo) const override; + +public: + ViewObjectContactOfSwNoTextFrame( + sdr::contact::ObjectContact& rObjectContact, + sdr::contact::ViewContact& rViewContact); +}; + +class ViewContactOfSwNoTextFrame : public sdr::contact::ViewContact +{ +private: + // owner + const SwNoTextFrame& mrSwNoTextFrame; + +protected: + // Create an Object-Specific ViewObjectContact, set ViewContact and + // ObjectContact. Always needs to return something. + virtual sdr::contact::ViewObjectContact& CreateObjectSpecificViewObjectContact( + sdr::contact::ObjectContact& rObjectContact) override; + +public: + // read-access to owner + const SwNoTextFrame& getSwNoTextFrame() const { return mrSwNoTextFrame; } + + // basic constructor, used from SwNoTextFrame. + explicit ViewContactOfSwNoTextFrame(const SwNoTextFrame& rSwNoTextFrame); +}; + +drawinglayer::primitive2d::Primitive2DContainer ViewObjectContactOfSwNoTextFrame::createPrimitive2DSequence( + const sdr::contact::DisplayInfo& /*rDisplayInfo*/) const +{ + // MM02 get all the parameters formally used in paintGraphicUsingPrimitivesHelper + ViewContactOfSwNoTextFrame& rVCOfNTF(static_cast<ViewContactOfSwNoTextFrame&>(GetViewContact())); + const SwNoTextFrame& rSwNoTextFrame(rVCOfNTF.getSwNoTextFrame()); + SwNoTextNode& rNoTNd(const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(rSwNoTextFrame.GetNode()))); + SwGrfNode* pGrfNd(rNoTNd.GetGrfNode()); + + if(nullptr != pGrfNd) + { + const bool bPrn(GetObjectContact().isOutputToPrinter() || GetObjectContact().isOutputToRecordingMetaFile()); + const GraphicObject& rGrfObj(pGrfNd->GetGrfObj(bPrn)); + GraphicAttr aGraphicAttr; + pGrfNd->GetGraphicAttr(aGraphicAttr, &rSwNoTextFrame); + const basegfx::B2DHomMatrix aGraphicTransform(rSwNoTextFrame.getFrameAreaTransformation()); + + // MM02 this is the right place in the VOC-Mechanism to create + // the primitives for visualization - these will be automatically + // buffered and reused + drawinglayer::primitive2d::Primitive2DContainer aContent(1); + aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D( + aGraphicTransform, + rGrfObj, + aGraphicAttr); + + return aContent; + } + + return drawinglayer::primitive2d::Primitive2DContainer(); +} + +ViewObjectContactOfSwNoTextFrame::ViewObjectContactOfSwNoTextFrame( + sdr::contact::ObjectContact& rObjectContact, + sdr::contact::ViewContact& rViewContact) +: sdr::contact::ViewObjectContact(rObjectContact, rViewContact) +{ +} + +sdr::contact::ViewObjectContact& ViewContactOfSwNoTextFrame::CreateObjectSpecificViewObjectContact( + sdr::contact::ObjectContact& rObjectContact) +{ + sdr::contact::ViewObjectContact* pRetval = new ViewObjectContactOfSwNoTextFrame(rObjectContact, *this); + return *pRetval; +} + +ViewContactOfSwNoTextFrame::ViewContactOfSwNoTextFrame( + const SwNoTextFrame& rSwNoTextFrame +) +: sdr::contact::ViewContact(), + mrSwNoTextFrame(rSwNoTextFrame) +{ +} +} // end of anonymous namespace + +sdr::contact::ViewContact& SwNoTextFrame::GetViewContact() const +{ + if(!mpViewContact) + { + const_cast< SwNoTextFrame* >(this)->mpViewContact = + std::make_unique<ViewContactOfSwNoTextFrame>(*this); + } + + return *mpViewContact; +} + +/** Paint the graphic. + + We require either a QuickDraw-Bitmap or a graphic here. If we do not have + either, we return a replacement. + + @todo use aligned rectangle for drawing graphic. + @todo pixel-align coordinations for drawing graphic. */ +void SwNoTextFrame::PaintPicture( vcl::RenderContext* pOut, const SwRect &rGrfArea ) const +{ + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + + SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode())); + SwGrfNode* pGrfNd = rNoTNd.GetGrfNode(); + SwOLENode* pOLENd = rNoTNd.GetOLENode(); + + const bool bPrn = pOut == rNoTNd.getIDocumentDeviceAccess().getPrinter( false ) || + pOut->GetConnectMetaFile(); + + const bool bIsChart = pOLENd && pOLENd->GetOLEObj().GetObject().IsChart(); + + // calculate aligned rectangle from parameter <rGrfArea>. + // Use aligned rectangle <aAlignedGrfArea> instead of <rGrfArea> in + // the following code. + SwRect aAlignedGrfArea = rGrfArea; + ::SwAlignRect( aAlignedGrfArea, pShell, pOut ); + + if( !bIsChart ) + { + // Because for drawing a graphic left-top-corner and size coordinations are + // used, these coordinations have to be determined on pixel level. + ::SwAlignGrfRect( &aAlignedGrfArea, *pOut ); + } + else //if( bIsChart ) + { + // #i78025# charts own borders are not completely visible + // the above pixel correction is not correct - at least not for charts + // so a different pixel correction is chosen here + // this might be a good idea for all other OLE objects also, + // but as I cannot oversee the consequences I fix it only for charts for now + lcl_correctlyAlignRect( aAlignedGrfArea, rGrfArea, pOut ); + } + + if( pGrfNd ) + { + // Fix for bug fdo#33781 + const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() ); + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + pOut->SetAntialiasing( nFormerAntialiasingAtOutput | AntialiasingFlags::EnableB2dDraw ); + } + + bool bContinue = true; + const GraphicObject& rGrfObj = pGrfNd->GetGrfObj(bPrn); + + GraphicAttr aGrfAttr; + pGrfNd->GetGraphicAttr( aGrfAttr, this ); + + if( !bPrn ) + { + // #i73788# + if ( pGrfNd->IsLinkedInputStreamReady() ) + { + pGrfNd->UpdateLinkWithInputStream(); + } + // #i85717#, #i90395# - check, if asynchronous retrieval + // if input stream for the graphic is possible + else if ( ( rGrfObj.GetType() == GraphicType::Default || + rGrfObj.GetType() == GraphicType::NONE ) && + pGrfNd->IsLinkedFile() && + pGrfNd->IsAsyncRetrieveInputStreamPossible() ) + { + Size aTmpSz; + ::sfx2::SvLinkSource* pGrfObj = pGrfNd->GetLink()->GetObj(); + if( !pGrfObj || + !pGrfObj->IsDataComplete() || + !(aTmpSz = pGrfNd->GetTwipSize()).Width() || + !aTmpSz.Height()) + { + pGrfNd->TriggerAsyncRetrieveInputStream(); // #i73788# + } + OUString aText( pGrfNd->GetTitle() ); + if ( aText.isEmpty() ) + GetRealURL( *pGrfNd, aText ); + ::lcl_PaintReplacement( aAlignedGrfArea, aText, *pShell, this, false ); + bContinue = false; + } + } + + if( bContinue ) + { + if( rGrfObj.GetGraphic().IsSupportedGraphic()) + { + const bool bAnimate = rGrfObj.IsAnimated() && + !pShell->IsPreview() && + !pShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() && + // #i9684# Stop animation during printing/pdf export + pShell->GetWin(); + + if( bAnimate && + FindFlyFrame() != ::GetFlyFromMarked( nullptr, pShell )) + { + OutputDevice* pVout; + if( pOut == pShell->GetOut() && SwRootFrame::FlushVout() ) + { + pVout = pOut; + pOut = pShell->GetOut(); + } + else if( pShell->GetWin() && pOut->IsVirtual() ) + { + pVout = pOut; + pOut = pShell->GetWin(); + } + else + pVout = nullptr; + + OSL_ENSURE( !pOut->IsVirtual() || + pShell->GetViewOptions()->IsPDFExport() || pShell->isOutputToWindow(), + "pOut should not be a virtual device" ); + + pGrfNd->StartGraphicAnimation(pOut, aAlignedGrfArea.Pos(), + aAlignedGrfArea.SSize(), reinterpret_cast<sal_IntPtr>(this), + pVout ); + } + else + { + // MM02 To allow system-dependent buffering of the involved + // bitmaps it is necessary to re-use the involved primitives + // and their already executed decomposition (also for + // performance reasons). This is usually done in DrawingLayer + // by using the VOC-Mechanism (see descriptions elsewhere). + // To get that here, make the involved SwNoTextFrame (this) + // a sdr::contact::ViewContact supplier by supporting + // a GetViewContact() - call. For ObjectContact we can use + // the already existing ObjectContact from the involved + // DrawingLayer. For this, the helper classes + // ViewObjectContactOfSwNoTextFrame + // ViewContactOfSwNoTextFrame + // are created which support the VOC-mechanism in its minimal + // form. This allows automatic and view-dependent (multiple edit + // windows, print, etc.) re-use of the created primitives. + // Also: Will be very useful when completely changing the Writer + // repaint to VOC and Primitives, too. + static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES")); + static bool bUseViewObjectContactMechanism(nullptr == pDisableMM02Goodies); + // tdf#130951 for safety reasons use fallback if ViewObjectContactMechanism + // fails for some reason - usually could only be not to find the correct + // SdrPageWindow + bool bSucceeded(false); + + if(bUseViewObjectContactMechanism) + { + // MM02 use VOC-mechanism and buffer primitives + SwViewShellImp* pImp(pShell->Imp()); + SdrPageView* pPageView(nullptr != pImp + ? pImp->GetPageView() + : nullptr); + // tdf#130951 caution - target may be Window, use the correct OutputDevice + OutputDevice* pTarget(pShell->isOutputToWindow() + ? pShell->GetWin() + : pShell->GetOut()); + SdrPageWindow* pPageWindow(nullptr != pPageView && nullptr != pTarget + ? pPageView->FindPageWindow(*pTarget) + : nullptr); + + if(nullptr != pPageWindow) + { + sdr::contact::ObjectContact& rOC(pPageWindow->GetObjectContact()); + sdr::contact::ViewContact& rVC(GetViewContact()); + sdr::contact::ViewObjectContact& rVOC(rVC.GetViewObjectContact(rOC)); + sdr::contact::DisplayInfo aDisplayInfo; + + drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rVOC.getPrimitive2DSequence(aDisplayInfo)); + const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation()); + + paintGraphicUsingPrimitivesHelper( + *pOut, + aPrimitives, + aGraphicTransform, + nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(), + rNoTNd.GetTitle(), + rNoTNd.GetDescription()); + bSucceeded = true; + } + } + + if(!bSucceeded) + { + // MM02 fallback to direct paint with primitive-recreation + // which will block reusage of system-dependent bitmap data + const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation()); + + paintGraphicUsingPrimitivesHelper( + *pOut, + rGrfObj, + aGrfAttr, + aGraphicTransform, + nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(), + rNoTNd.GetTitle(), + rNoTNd.GetDescription()); + } + } + } + else + { + const char* pResId = nullptr; + + if( GraphicType::NONE == rGrfObj.GetType() ) + pResId = STR_COMCORE_READERROR; + else if ( !rGrfObj.GetGraphic().IsSupportedGraphic() ) + pResId = STR_COMCORE_CANT_SHOW; + + OUString aText; + if ( !pResId && + (aText = pGrfNd->GetTitle()).isEmpty() && + (!GetRealURL( *pGrfNd, aText ) || aText.isEmpty())) + { + pResId = STR_COMCORE_READERROR; + } + if (pResId) + aText = SwResId(pResId); + + ::lcl_PaintReplacement( aAlignedGrfArea, aText, *pShell, this, true ); + } + } + + if ( pShell->Imp()->GetDrawView()->IsAntiAliasing() ) + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + else // bIsChart || pOLENd + { + // Fix for bug fdo#33781 + const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() ); + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + AntialiasingFlags nNewAntialiasingAtOutput = nFormerAntialiasingAtOutput | AntialiasingFlags::EnableB2dDraw; + + // #i99665# + // Adjust AntiAliasing mode at output device for chart OLE + if ( pOLENd->IsChart() ) + nNewAntialiasingAtOutput |= AntialiasingFlags::PixelSnapHairline; + + pOut->SetAntialiasing( nNewAntialiasingAtOutput ); + } + + bool bDone(false); + + if(bIsChart) + { + basegfx::B2DRange aSourceRange; + const drawinglayer::primitive2d::Primitive2DContainer aSequence( + pOLENd->GetOLEObj().tryToGetChartContentAsPrimitive2DSequence( + aSourceRange, + bPrn)); + + if(!aSequence.empty() && !aSourceRange.isEmpty()) + { + const basegfx::B2DRange aTargetRange( + aAlignedGrfArea.Left(), aAlignedGrfArea.Top(), + aAlignedGrfArea.Right(), aAlignedGrfArea.Bottom()); + + bDone = paintUsingPrimitivesHelper( + *pOut, + aSequence, + aSourceRange, + aTargetRange); + } + } + + if(!bDone && pOLENd) + { + // SwOLENode does not have a known GraphicObject, need to + // work with Graphic instead + const Graphic* pGraphic = pOLENd->GetGraphic(); + const Point aPosition(aAlignedGrfArea.Pos()); + const Size aSize(aAlignedGrfArea.SSize()); + + if ( pGraphic && pGraphic->GetType() != GraphicType::NONE ) + { + pGraphic->Draw( pOut, aPosition, aSize ); + + // shade the representation if the object is activated outplace + uno::Reference < embed::XEmbeddedObject > xObj = pOLENd->GetOLEObj().GetOleRef(); + if ( xObj.is() && xObj->getCurrentState() == embed::EmbedStates::ACTIVE ) + { + + ::svt::EmbeddedObjectRef::DrawShading( + tools::Rectangle( + aPosition, + aSize), + pOut); + } + } + else + { + ::svt::EmbeddedObjectRef::DrawPaintReplacement( + tools::Rectangle(aPosition, aSize), + pOLENd->GetOLEObj().GetCurrentPersistName(), + pOut); + } + + sal_Int64 nMiscStatus = pOLENd->GetOLEObj().GetOleRef()->getStatus( pOLENd->GetAspect() ); + if ( !bPrn && dynamic_cast< const SwCursorShell *>( pShell ) != nullptr && + (nMiscStatus & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE)) + { + const SwFlyFrame *pFly = FindFlyFrame(); + assert( pFly != nullptr ); + static_cast<SwFEShell*>(pShell)->ConnectObj( pOLENd->GetOLEObj().GetObject(), pFly->getFramePrintArea(), pFly->getFrameArea()); + } + } + + // see #i99665# + if (pShell->Imp()->GetDrawView()->IsAntiAliasing()) + { + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + } +} + +bool SwNoTextFrame::IsTransparent() const +{ + const SwViewShell* pSh = getRootFrame()->GetCurrShell(); + + if ( !pSh || !pSh->GetViewOptions()->IsGraphic() ) + { + return true; + } + + const SwGrfNode *pNd; + + if( nullptr != (pNd = GetNode()->GetGrfNode()) ) + { + if(pNd->IsTransparent()) + { + return true; + } + } + + // RotateFlyFrame3: If we are transformed, there are 'free' areas between + // the Graphic and the Border/Padding stuff - at least as long as those + // (Border and Padding) are not transformed, too + if(isTransformableSwFrame()) + { + // we can be more specific - rotations of multiples of + // 90 degrees will leave no gaps. Go from [0.0 .. F_2PI] + // to [0 .. 360] and check modulo 90 + const long nRot(static_cast<long>(basegfx::rad2deg(getLocalFrameRotation()))); + const bool bMultipleOf90(0 == (nRot % 90)); + + if(!bMultipleOf90) + { + return true; + } + } + + //#29381# OLE are always transparent + if(nullptr != GetNode()->GetOLENode()) + { + return true; + } + + // return false by default to avoid background paint + return false; +} + +void SwNoTextFrame::StopAnimation( OutputDevice* pOut ) const +{ + // Stop animated graphics + const SwGrfNode* pGrfNd = GetNode()->GetGrfNode(); + + if( pGrfNd && pGrfNd->IsAnimated() ) + { + const_cast< SwGrfNode* >(pGrfNd)->StopGraphicAnimation( pOut, reinterpret_cast<sal_IntPtr>(this) ); + } +} + +bool SwNoTextFrame::HasAnimation() const +{ + const SwGrfNode* pGrfNd = GetNode()->GetGrfNode(); + return pGrfNd && pGrfNd->IsAnimated(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/number.cxx b/sw/source/core/doc/number.cxx new file mode 100644 index 000000000..1de08ae9f --- /dev/null +++ b/sw/source/core/doc/number.cxx @@ -0,0 +1,1475 @@ +/* -*- 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 <memory> +#include <hintids.hxx> + +#include <vcl/font.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/numitem.hxx> +#include <svl/grabbagitem.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> +#include <docary.hxx> +#include <SwStyleNameMapper.hxx> + +// Needed to load default bullet list configuration +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> + +#include <numrule.hxx> +#include <SwNodeNum.hxx> + +#include <list.hxx> + +#include <algorithm> +#include <unordered_map> +#include <libxml/xmlwriter.h> + +#include <rtl/ustrbuf.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/saveopt.hxx> +#include <osl/diagnose.h> + +#include <IDocumentListsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentState.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace ::com::sun::star; + +sal_uInt16 SwNumRule::mnRefCount = 0; +SwNumFormat* SwNumRule::maBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +SwNumFormat* SwNumRule::maLabelAlignmentBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +const sal_uInt16 SwNumRule::maDefNumIndents[ MAXLEVEL ] = { +//inch: 0,5 1,0 1,5 2,0 2,5 3,0 3,5 4,0 4,5 5,0 + 1440/4, 1440/2, 1440*3/4, 1440, 1440*5/4, 1440*3/2, 1440*7/4, 1440*2, + 1440*9/4, 1440*5/2 +}; + +OUString SwNumRule::GetOutlineRuleName() +{ + return "Outline"; +} + +const SwNumFormat& SwNumRule::Get( sal_uInt16 i ) const +{ + assert( i < MAXLEVEL && meRuleType < RULE_END ); + return maFormats[ i ] + ? *maFormats[ i ] + : ( meDefaultNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION + ? *maBaseFormats[ meRuleType ][ i ] + : *maLabelAlignmentBaseFormats[ meRuleType ][ i ] ); +} + +const SwNumFormat* SwNumRule::GetNumFormat( sal_uInt16 i ) const +{ + const SwNumFormat * pResult = nullptr; + + assert( i < MAXLEVEL && meRuleType < RULE_END ); + if ( i < MAXLEVEL && meRuleType < RULE_END) + { + pResult = maFormats[ i ].get(); + } + + return pResult; +} + +// #i91400# +void SwNumRule::SetName( const OUString & rName, + IDocumentListsAccess& rDocListAccess) +{ + if ( msName != rName ) + { + if (mpNumRuleMap) + { + mpNumRuleMap->erase(msName); + (*mpNumRuleMap)[rName] = this; + + if ( !GetDefaultListId().isEmpty() ) + { + rDocListAccess.trackChangeOfListStyleName( msName, rName ); + } + } + + msName = rName; + } +} + +void SwNumRule::GetTextNodeList( SwNumRule::tTextNodeList& rTextNodeList ) const +{ + rTextNodeList = maTextNodeList; +} + +SwNumRule::tTextNodeList::size_type SwNumRule::GetTextNodeListSize() const +{ + return maTextNodeList.size(); +} + +void SwNumRule::AddTextNode( SwTextNode& rTextNode ) +{ + tTextNodeList::iterator aIter = + std::find( maTextNodeList.begin(), maTextNodeList.end(), &rTextNode ); + + if ( aIter == maTextNodeList.end() ) + { + maTextNodeList.push_back( &rTextNode ); + } +} + +void SwNumRule::RemoveTextNode( SwTextNode& rTextNode ) +{ + tTextNodeList::iterator aIter = + std::find( maTextNodeList.begin(), maTextNodeList.end(), &rTextNode ); + + if ( aIter != maTextNodeList.end() ) + { + maTextNodeList.erase( aIter ); + } +} + +void SwNumRule::SetNumRuleMap(std::unordered_map<OUString, SwNumRule *> * + pNumRuleMap) +{ + mpNumRuleMap = pNumRuleMap; +} + +sal_uInt16 SwNumRule::GetNumIndent( sal_uInt8 nLvl ) +{ + OSL_ENSURE( MAXLEVEL > nLvl, "NumLevel is out of range" ); + return maDefNumIndents[ nLvl ]; +} + +sal_uInt16 SwNumRule::GetBullIndent( sal_uInt8 nLvl ) +{ + OSL_ENSURE( MAXLEVEL > nLvl, "NumLevel is out of range" ); + return maDefNumIndents[ nLvl ]; +} + +static void lcl_SetRuleChgd( SwTextNode& rNd, sal_uInt8 nLevel ) +{ + if( rNd.GetActualListLevel() == nLevel ) + rNd.NumRuleChgd(); +} + +SwNumFormat::SwNumFormat() : + SvxNumberFormat(SVX_NUM_ARABIC), + SwClient( nullptr ), + m_pVertOrient(new SwFormatVertOrient( 0, text::VertOrientation::NONE)) + ,m_cGrfBulletCP(USHRT_MAX)//For i120928,record the cp info of graphic within bullet +{ +} + +SwNumFormat::SwNumFormat( const SwNumFormat& rFormat) : + SvxNumberFormat(rFormat), + SwClient( rFormat.GetRegisteredInNonConst() ), + m_pVertOrient(new SwFormatVertOrient( 0, rFormat.GetVertOrient())) + ,m_cGrfBulletCP(rFormat.m_cGrfBulletCP)//For i120928,record the cp info of graphic within bullet +{ + sal_Int16 eMyVertOrient = rFormat.GetVertOrient(); + SetGraphicBrush( rFormat.GetBrush(), &rFormat.GetGraphicSize(), + &eMyVertOrient); +} + +SwNumFormat::SwNumFormat(const SvxNumberFormat& rNumFormat, SwDoc* pDoc) + : SvxNumberFormat(rNumFormat) + , m_pVertOrient(new SwFormatVertOrient( 0, rNumFormat.GetVertOrient())) + , m_cGrfBulletCP(USHRT_MAX) +{ + sal_Int16 eMyVertOrient = rNumFormat.GetVertOrient(); + SetGraphicBrush( rNumFormat.GetBrush(), &rNumFormat.GetGraphicSize(), + &eMyVertOrient); + const OUString rCharStyleName = rNumFormat.SvxNumberFormat::GetCharFormatName(); + if( !rCharStyleName.isEmpty() ) + { + SwCharFormat* pCFormat = pDoc->FindCharFormatByName( rCharStyleName ); + if( !pCFormat ) + { + sal_uInt16 nId = SwStyleNameMapper::GetPoolIdFromUIName( rCharStyleName, + SwGetPoolIdFromName::ChrFmt ); + pCFormat = nId != USHRT_MAX + ? pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( nId ) + : pDoc->MakeCharFormat( rCharStyleName, nullptr ); + } + pCFormat->Add( this ); + } + else + EndListeningAll(); +} + +SwNumFormat::~SwNumFormat() +{ +} + +// #i22362# +bool SwNumFormat::IsEnumeration() const +{ + // #i30655# native numbering did not work any longer + // using this code. Therefore HBRINKM and I agreed upon defining + // IsEnumeration() as !IsItemize() + return !IsItemize(); +} + +bool SwNumFormat::IsItemize() const +{ + bool bResult; + + switch(GetNumberingType()) + { + case SVX_NUM_CHAR_SPECIAL: + case SVX_NUM_BITMAP: + bResult = true; + + break; + + default: + bResult = false; + } + + return bResult; + +} + +SwNumFormat& SwNumFormat::operator=( const SwNumFormat& rNumFormat) +{ + SvxNumberFormat::operator=(rNumFormat); + StartListeningToSameModifyAs(rNumFormat); + //For i120928,record the cp info of graphic within bullet + m_cGrfBulletCP = rNumFormat.m_cGrfBulletCP; + return *this; +} + +bool SwNumFormat::operator==( const SwNumFormat& rNumFormat) const +{ + bool bRet = SvxNumberFormat::operator==(rNumFormat) && + GetRegisteredIn() == rNumFormat.GetRegisteredIn(); + return bRet; +} + +void SwNumFormat::SetCharFormat( SwCharFormat* pChFormat) +{ + if( pChFormat ) + pChFormat->Add( this ); + else + EndListeningAll(); +} + +void SwNumFormat::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + // Look for the NumRules object in the Doc where this NumFormat is set. + // The format does not need to exist! + const SwCharFormat* pFormat = nullptr; + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_ATTRSET_CHG: + case RES_FMT_CHG: + pFormat = GetCharFormat(); + break; + } + + if( pFormat && !pFormat->GetDoc()->IsInDtor() ) + UpdateNumNodes( const_cast<SwDoc*>(pFormat->GetDoc()) ); + else + CheckRegistration( pOld ); +} + +OUString SwNumFormat::GetCharFormatName() const +{ + if(static_cast<const SwCharFormat*>(GetRegisteredIn())) + return static_cast<const SwCharFormat*>(GetRegisteredIn())->GetName(); + + return OUString(); +} + +void SwNumFormat::SetGraphicBrush( const SvxBrushItem* pBrushItem, const Size* pSize, + const sal_Int16* pOrient) +{ + if(pOrient) + m_pVertOrient->SetVertOrient( *pOrient ); + SvxNumberFormat::SetGraphicBrush( pBrushItem, pSize, pOrient); +} + +void SwNumFormat::UpdateNumNodes( SwDoc* pDoc ) +{ + bool bDocIsModified = pDoc->getIDocumentState().IsModified(); + bool bFnd = false; + for( SwNumRuleTable::size_type n = pDoc->GetNumRuleTable().size(); !bFnd && n; ) + { + const SwNumRule* pRule = pDoc->GetNumRuleTable()[ --n ]; + for( sal_uInt8 i = 0; i < MAXLEVEL; ++i ) + if( pRule->GetNumFormat( i ) == this ) + { + SwNumRule::tTextNodeList aTextNodeList; + pRule->GetTextNodeList( aTextNodeList ); + for ( auto& rpTextNode : aTextNodeList ) + { + lcl_SetRuleChgd( *rpTextNode, i ); + } + bFnd = true; + break; + } + } + + if( bFnd && !bDocIsModified ) + pDoc->getIDocumentState().ResetModified(); +} + +const SwFormatVertOrient* SwNumFormat::GetGraphicOrientation() const +{ + sal_Int16 eOrient = SvxNumberFormat::GetVertOrient(); + if(text::VertOrientation::NONE == eOrient) + return nullptr; + else + { + m_pVertOrient->SetVertOrient(eOrient); + return m_pVertOrient.get(); + } +} + +SwNumRule::SwNumRule( const OUString& rNm, + const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode, + SwNumRuleType eType ) + : maTextNodeList(), + maParagraphStyleList(), + mpNumRuleMap(nullptr), + msName( rNm ), + meRuleType( eType ), + mnPoolFormatId( USHRT_MAX ), + mnPoolHelpId( USHRT_MAX ), + mnPoolHlpFileId( UCHAR_MAX ), + mbAutoRuleFlag( true ), + mbInvalidRuleFlag( true ), + mbContinusNum( false ), + mbAbsSpaces( false ), + mbHidden( false ), + mbCountPhantoms( true ), + mbUsedByRedline( false ), + meDefaultNumberFormatPositionAndSpaceMode( eDefaultNumberFormatPositionAndSpaceMode ), + msDefaultListId() +{ + if( !mnRefCount++ ) // for the first time, initialize + { + SwNumFormat* pFormat; + sal_uInt8 n; + + // numbering: + // position-and-space mode LABEL_WIDTH_AND_POSITION: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetIncludeUpperLevels( 1 ); + pFormat->SetStart( 1 ); + pFormat->SetAbsLSpace( lNumberIndent + SwNumRule::GetNumIndent( n ) ); + pFormat->SetFirstLineOffset( lNumberFirstLineOffset ); + pFormat->SetSuffix( "." ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maBaseFormats[ NUM_RULE ][ n ] = pFormat; + } + // position-and-space mode LABEL_ALIGNMENT + // first line indent of general numbering in inch: -0,25 inch + const long cFirstLineIndent = -1440/4; + // indent values of general numbering in inch: + // 0,5 0,75 1,0 1,25 1,5 + // 1,75 2,0 2,25 2,5 2,75 + const long cIndentAt[ MAXLEVEL ] = { + 1440/2, 1440*3/4, 1440, 1440*5/4, 1440*3/2, + 1440*7/4, 1440*2, 1440*9/4, 1440*5/2, 1440*11/4 }; + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetIncludeUpperLevels( 1 ); + pFormat->SetStart( 1 ); + pFormat->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + pFormat->SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + pFormat->SetListtabPos( cIndentAt[ n ] ); + pFormat->SetFirstLineIndent( cFirstLineIndent ); + pFormat->SetIndentAt( cIndentAt[ n ] ); + pFormat->SetSuffix( "." ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maLabelAlignmentBaseFormats[ NUM_RULE ][ n ] = pFormat; + } + + // outline: + // position-and-space mode LABEL_WIDTH_AND_POSITION: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetNumberingType(SVX_NUM_NUMBER_NONE); + pFormat->SetIncludeUpperLevels( MAXLEVEL ); + pFormat->SetStart( 1 ); + pFormat->SetCharTextDistance( lOutlineMinTextDistance ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maBaseFormats[ OUTLINE_RULE ][ n ] = pFormat; + } + // position-and-space mode LABEL_ALIGNMENT: + for( n = 0; n < MAXLEVEL; ++n ) + { + pFormat = new SwNumFormat; + pFormat->SetNumberingType(SVX_NUM_NUMBER_NONE); + pFormat->SetIncludeUpperLevels( MAXLEVEL ); + pFormat->SetStart( 1 ); + pFormat->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::maLabelAlignmentBaseFormats[ OUTLINE_RULE ][ n ] = pFormat; + } + } + OSL_ENSURE( !msName.isEmpty(), "NumRule without a name!" ); +} + +SwNumRule::SwNumRule( const SwNumRule& rNumRule ) + : maTextNodeList(), + maParagraphStyleList(), + mpNumRuleMap(nullptr), + msName( rNumRule.msName ), + meRuleType( rNumRule.meRuleType ), + mnPoolFormatId( rNumRule.GetPoolFormatId() ), + mnPoolHelpId( rNumRule.GetPoolHelpId() ), + mnPoolHlpFileId( rNumRule.GetPoolHlpFileId() ), + mbAutoRuleFlag( rNumRule.mbAutoRuleFlag ), + mbInvalidRuleFlag( true ), + mbContinusNum( rNumRule.mbContinusNum ), + mbAbsSpaces( rNumRule.mbAbsSpaces ), + mbHidden( rNumRule.mbHidden ), + mbCountPhantoms( true ), + mbUsedByRedline( false ), + meDefaultNumberFormatPositionAndSpaceMode( rNumRule.meDefaultNumberFormatPositionAndSpaceMode ), + msDefaultListId( rNumRule.msDefaultListId ) +{ + ++mnRefCount; + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + if( rNumRule.maFormats[ n ] ) + Set( n, *rNumRule.maFormats[ n ] ); +} + +SwNumRule::~SwNumRule() +{ + for (auto & i : maFormats) + i.reset(); + + if (mpNumRuleMap) + { + mpNumRuleMap->erase(GetName()); + } + + if( !--mnRefCount ) // the last one closes the door (?) + { + // Numbering: + SwNumFormat** ppFormats = &SwNumRule::maBaseFormats[0][0]; + int n; + + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + + // Outline: + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + + ppFormats = &SwNumRule::maLabelAlignmentBaseFormats[0][0]; + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + for( n = 0; n < MAXLEVEL; ++n, ++ppFormats ) + { + delete *ppFormats; + *ppFormats = nullptr; + } + } + + maTextNodeList.clear(); + maParagraphStyleList.clear(); +} + +void SwNumRule::CheckCharFormats( SwDoc* pDoc ) +{ + for(auto& rpNumFormat : maFormats) + { + if( rpNumFormat ) + { + SwCharFormat* pFormat = rpNumFormat->GetCharFormat(); + if( pFormat && pFormat->GetDoc() != pDoc ) + { + // copy + SwNumFormat* pNew = new SwNumFormat( *rpNumFormat ); + pNew->SetCharFormat( pDoc->CopyCharFormat( *pFormat ) ); + rpNumFormat.reset(pNew); + } + } + } +} + +SwNumRule& SwNumRule::operator=( const SwNumRule& rNumRule ) +{ + if( this != &rNumRule ) + { + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + Set( n, rNumRule.maFormats[ n ].get() ); + + meRuleType = rNumRule.meRuleType; + msName = rNumRule.msName; + mbAutoRuleFlag = rNumRule.mbAutoRuleFlag; + mbInvalidRuleFlag = true; + mbContinusNum = rNumRule.mbContinusNum; + mbAbsSpaces = rNumRule.mbAbsSpaces; + mbHidden = rNumRule.mbHidden; + mnPoolFormatId = rNumRule.GetPoolFormatId(); + mnPoolHelpId = rNumRule.GetPoolHelpId(); + mnPoolHlpFileId = rNumRule.GetPoolHlpFileId(); + } + return *this; +} + +bool SwNumRule::operator==( const SwNumRule& rRule ) const +{ + bool bRet = meRuleType == rRule.meRuleType && + msName == rRule.msName && + mbAutoRuleFlag == rRule.mbAutoRuleFlag && + mbContinusNum == rRule.mbContinusNum && + mbAbsSpaces == rRule.mbAbsSpaces && + mnPoolFormatId == rRule.GetPoolFormatId() && + mnPoolHelpId == rRule.GetPoolHelpId() && + mnPoolHlpFileId == rRule.GetPoolHlpFileId(); + if( bRet ) + { + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + if( rRule.Get( n ) != Get( n ) ) + { + bRet = false; + break; + } + } + return bRet; +} + +void SwNumRule::Set( sal_uInt16 i, const SwNumFormat& rNumFormat ) +{ + OSL_ENSURE( i < MAXLEVEL, "Serious defect" ); + if( i < MAXLEVEL ) + { + if( !maFormats[ i ] || (rNumFormat != Get( i )) ) + { + maFormats[ i ].reset(new SwNumFormat( rNumFormat )); + mbInvalidRuleFlag = true; + } + } +} + +void SwNumRule::Set( sal_uInt16 i, const SwNumFormat* pNumFormat ) +{ + OSL_ENSURE( i < MAXLEVEL, "Serious defect" ); + if( i >= MAXLEVEL ) + return; + if( !maFormats[ i ] ) + { + if( pNumFormat ) + { + maFormats[ i ].reset(new SwNumFormat( *pNumFormat )); + mbInvalidRuleFlag = true; + } + } + else if( !pNumFormat ) + { + maFormats[ i ].reset(); + mbInvalidRuleFlag = true; + } + else if( *maFormats[i] != *pNumFormat ) + { + *maFormats[ i ] = *pNumFormat; + mbInvalidRuleFlag = true; + } +} + +OUString SwNumRule::MakeNumString( const SwNodeNum& rNum, bool bInclStrings ) const +{ + if (rNum.IsCounted()) + return MakeNumString(rNum.GetNumberVector(), bInclStrings); + + return OUString(); +} + +OUString SwNumRule::MakeNumString( const SwNumberTree::tNumberVector & rNumVector, + const bool bInclStrings, + const bool bOnlyArabic, + const unsigned int _nRestrictToThisLevel, + SwNumRule::Extremities* pExtremities, + LanguageType nLang ) const +{ + OUStringBuffer aStr; + + SwNumberTree::tNumberVector::size_type nLevel = rNumVector.size() - 1; + + if ( pExtremities ) + pExtremities->nPrefixChars = pExtremities->nSuffixChars = 0; + + if ( nLevel > _nRestrictToThisLevel ) + { + nLevel = _nRestrictToThisLevel; + } + + if (nLevel < MAXLEVEL) + { + const SwNumFormat& rMyNFormat = Get( static_cast<sal_uInt16>(nLevel) ); + + { + css::lang::Locale aLocale( LanguageTag::convertToLocale(nLang)); + + if (rMyNFormat.HasListFormat()) + { + OUString sLevelFormat = rMyNFormat.GetListFormat(); + // In this case we are ignoring GetIncludeUpperLevels: we put all + // level numbers requested by level format + for (SwNumberTree::tNumberVector::size_type i=0; i <= nLevel; ++i) + { + OUString sReplacement; + if (rNumVector[i]) + { + if (bOnlyArabic) + sReplacement = OUString::number(rNumVector[i]); + else + sReplacement = Get(i).GetNumStr(rNumVector[i], aLocale); + } + else + sReplacement = "0"; // all 0 level are a 0 + + OUString sFind("%" + OUString::number(i + 1)); + sal_Int32 nPosition = sLevelFormat.indexOf(sFind); + if (nPosition >= 0) + sLevelFormat = sLevelFormat.replaceAt(nPosition, sFind.getLength(), sReplacement); + } + + // As a fallback: caller code expects nonempty string as a result. + // But if we have empty string (and had no errors before) this is valid result. + // So use classical hack with zero-width-space as a string filling. + if (sLevelFormat.isEmpty()) + sLevelFormat = OUStringChar(CHAR_ZWSP); + + aStr = sLevelFormat; + } + else + { + // Fallback case: level format is not defined + // So use old way with levels joining by dot "." + SwNumberTree::tNumberVector::size_type i = nLevel; + + if (!IsContinusNum() && + // - do not include upper levels, if level isn't numbered. + rMyNFormat.GetNumberingType() != SVX_NUM_NUMBER_NONE && + rMyNFormat.GetIncludeUpperLevels()) // Just the own level? + { + sal_uInt8 n = rMyNFormat.GetIncludeUpperLevels(); + if (1 < n) + { + if (i + 1 >= n) + i -= n - 1; + else + i = 0; + } + } + + for (; i <= nLevel; ++i) + { + const SwNumFormat& rNFormat = Get(i); + if (SVX_NUM_NUMBER_NONE == rNFormat.GetNumberingType()) + { + // Should 1.1.1 --> 2. NoNum --> 1..1 or 1.1 ?? + // if( i != rNum.nMyLevel ) + // aStr += "."; + continue; + } + + if (rNumVector[i]) + { + if (bOnlyArabic) + aStr.append(OUString::number(rNumVector[i])); + else + aStr.append(rNFormat.GetNumStr(rNumVector[i], aLocale)); + } + else + aStr.append("0"); // all 0 level are a 0 + if (i != nLevel && !aStr.isEmpty()) + aStr.append("."); + } + + // The type doesn't have any number, so don't append + // the post-/prefix string + if (bInclStrings && !bOnlyArabic && + SVX_NUM_CHAR_SPECIAL != rMyNFormat.GetNumberingType() && + SVX_NUM_BITMAP != rMyNFormat.GetNumberingType()) + { + const OUString& sPrefix = rMyNFormat.GetPrefix(); + const OUString& sSuffix = rMyNFormat.GetSuffix(); + + aStr.insert(0, sPrefix); + aStr.append(sSuffix); + if (pExtremities) + { + pExtremities->nPrefixChars = sPrefix.getLength(); + pExtremities->nSuffixChars = sSuffix.getLength(); + } + } + } + } + } + + return aStr.makeStringAndClear(); +} + +OUString SwNumRule::MakeRefNumString( const SwNodeNum& rNodeNum, + const bool bInclSuperiorNumLabels, + const int nRestrictInclToThisLevel ) const +{ + OUString aRefNumStr; + + if ( rNodeNum.GetLevelInListTree() >= 0 ) + { + bool bOldHadPrefix = true; + + const SwNodeNum* pWorkingNodeNum( &rNodeNum ); + do + { + bool bMakeNumStringForPhantom( false ); + if ( pWorkingNodeNum->IsPhantom() ) + { + int nListLevel = pWorkingNodeNum->GetLevelInListTree(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + SwNumFormat aFormat( Get( static_cast<sal_uInt16>(nListLevel) ) ); + bMakeNumStringForPhantom = aFormat.IsEnumeration() && + SVX_NUM_NUMBER_NONE != aFormat.GetNumberingType(); + + } + if ( bMakeNumStringForPhantom || + ( !pWorkingNodeNum->IsPhantom() && + pWorkingNodeNum->GetTextNode() && + pWorkingNodeNum->GetTextNode()->HasNumber() ) ) + { + Extremities aExtremities; + OUString aPrevStr = MakeNumString( pWorkingNodeNum->GetNumberVector(), + true, false, MAXLEVEL, + &aExtremities); + sal_Int32 nStrip = 0; + while ( nStrip < aExtremities.nPrefixChars ) + { + const sal_Unicode c = aPrevStr[nStrip]; + if ( c!='\t' && c!=' ') + break; + ++nStrip; + } + + if (nStrip) + { + aPrevStr = aPrevStr.copy( nStrip ); + aExtremities.nPrefixChars -= nStrip; + } + + if (bOldHadPrefix && + aExtremities.nSuffixChars && + !aExtremities.nPrefixChars + ) + { + aPrevStr = aPrevStr.copy(0, + aPrevStr.getLength() - aExtremities.nSuffixChars); + } + + bOldHadPrefix = ( aExtremities.nPrefixChars > 0); + + aRefNumStr = aPrevStr + aRefNumStr; + } + + if ( bInclSuperiorNumLabels && pWorkingNodeNum->GetLevelInListTree() > 0 ) + { + sal_uInt8 n = Get( static_cast<sal_uInt16>(pWorkingNodeNum->GetLevelInListTree()) ).GetIncludeUpperLevels(); + pWorkingNodeNum = dynamic_cast<SwNodeNum*>(pWorkingNodeNum->GetParent()); + // skip parents, whose list label is already contained in the actual list label. + while ( pWorkingNodeNum && n > 1 ) + { + pWorkingNodeNum = dynamic_cast<SwNodeNum*>(pWorkingNodeNum->GetParent()); + --n; + } + } + else + { + break; + } + } while ( pWorkingNodeNum && + pWorkingNodeNum->GetLevelInListTree() >= 0 && + pWorkingNodeNum->GetLevelInListTree() >= nRestrictInclToThisLevel ); + } + + return aRefNumStr; +} + +OUString SwNumRule::MakeParagraphStyleListString() const +{ + OUString aParagraphStyleListString; + for (const auto& rParagraphStyle : maParagraphStyleList) + { + if (!aParagraphStyleListString.isEmpty()) + aParagraphStyleListString += ", "; + aParagraphStyleListString += rParagraphStyle->GetName(); + } + return aParagraphStyleListString; +} + +/** Copy method of SwNumRule + + A kind of copy constructor, so that the num formats are attached to the + right CharFormats of a Document. + Copies the NumFormats and returns itself. */ +SwNumRule& SwNumRule::CopyNumRule( SwDoc* pDoc, const SwNumRule& rNumRule ) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + Set( n, rNumRule.maFormats[ n ].get() ); + if( maFormats[ n ] && maFormats[ n ]->GetCharFormat() && + !pDoc->GetCharFormats()->IsAlive(maFormats[n]->GetCharFormat())) + { + // If we copy across different Documents, then copy the + // corresponding CharFormat into the new Document. + maFormats[n]->SetCharFormat( pDoc->CopyCharFormat( *maFormats[n]-> + GetCharFormat() ) ); + } + } + meRuleType = rNumRule.meRuleType; + msName = rNumRule.msName; + mbAutoRuleFlag = rNumRule.mbAutoRuleFlag; + mnPoolFormatId = rNumRule.GetPoolFormatId(); + mnPoolHelpId = rNumRule.GetPoolHelpId(); + mnPoolHlpFileId = rNumRule.GetPoolHlpFileId(); + mbInvalidRuleFlag = true; + return *this; +} + +void SwNumRule::SetSvxRule(const SvxNumRule& rNumRule, SwDoc* pDoc) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + const SvxNumberFormat* pSvxFormat = rNumRule.Get(n); + maFormats[n].reset( pSvxFormat ? new SwNumFormat(*pSvxFormat, pDoc) : nullptr ); + } + + mbInvalidRuleFlag = true; + mbContinusNum = rNumRule.IsContinuousNumbering(); +} + +SvxNumRule SwNumRule::MakeSvxNumRule() const +{ + SvxNumRule aRule(SvxNumRuleFlags::CONTINUOUS | SvxNumRuleFlags::CHAR_STYLE | + SvxNumRuleFlags::ENABLE_LINKED_BMP | SvxNumRuleFlags::ENABLE_EMBEDDED_BMP, + MAXLEVEL, mbContinusNum, + meRuleType == NUM_RULE ? SvxNumRuleType::NUMBERING : SvxNumRuleType::OUTLINE_NUMBERING ); + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + SwNumFormat aNumFormat = Get(n); + if(aNumFormat.GetCharFormat()) + aNumFormat.SetCharFormatName(aNumFormat.GetCharFormat()->GetName()); + aRule.SetLevel(n, aNumFormat, maFormats[n] != nullptr); + } + return aRule; +} + +void SwNumRule::SetInvalidRule(bool bFlag) +{ + if (bFlag) + { + o3tl::sorted_vector< SwList* > aLists; + for ( const SwTextNode* pTextNode : maTextNodeList ) + { + // #i111681# - applying patch from cmc + SwList* pList = pTextNode->GetDoc()->getIDocumentListsAccess().getListByName( pTextNode->GetListId() ); + OSL_ENSURE( pList, "<SwNumRule::SetInvalidRule(..)> - list at which the text node is registered at does not exist. This is a serious issue."); + if ( pList ) + { + aLists.insert( pList ); + } + } + for ( auto aList : aLists ) + aList->InvalidateListTree(); + } + + mbInvalidRuleFlag = bFlag; +} + +/// change indent of all list levels by given difference +void SwNumRule::ChangeIndent( const sal_Int32 nDiff ) +{ + for ( sal_uInt16 i = 0; i < MAXLEVEL; ++i ) + { + SwNumFormat aTmpNumFormat( Get(i) ); + + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + auto nNewIndent = nDiff + + aTmpNumFormat.GetAbsLSpace(); + if ( nNewIndent < 0 ) + { + nNewIndent = 0; + } + aTmpNumFormat.SetAbsLSpace( nNewIndent ); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // adjust also the list tab position, if a list tab stop is applied + if ( aTmpNumFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB ) + { + const long nNewListTab = aTmpNumFormat.GetListtabPos() + nDiff; + aTmpNumFormat.SetListtabPos( nNewListTab ); + } + + const long nNewIndent = nDiff + + aTmpNumFormat.GetIndentAt(); + aTmpNumFormat.SetIndentAt( nNewIndent ); + } + + Set( i, aTmpNumFormat ); + } + + SetInvalidRule( true ); +} + +/// set indent of certain list level to given value +void SwNumRule::SetIndent( const short nNewIndent, + const sal_uInt16 nListLevel ) +{ + SwNumFormat aTmpNumFormat( Get(nListLevel) ); + + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aTmpNumFormat.SetAbsLSpace( nNewIndent ); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + // adjust also the list tab position, if a list tab stop is applied + if ( aTmpNumFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB ) + { + const long nNewListTab = aTmpNumFormat.GetListtabPos() + + ( nNewIndent - aTmpNumFormat.GetIndentAt() ); + aTmpNumFormat.SetListtabPos( nNewListTab ); + } + + aTmpNumFormat.SetIndentAt( nNewIndent ); + } + + SetInvalidRule( true ); +} + +/// set indent of first list level to given value and change other list level's +/// indents accordingly +void SwNumRule::SetIndentOfFirstListLevelAndChangeOthers( const short nNewIndent ) +{ + SwNumFormat aTmpNumFormat( Get(0) ); + + sal_Int32 nDiff( 0 ); + const SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode( + aTmpNumFormat.GetPositionAndSpaceMode() ); + if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + nDiff = nNewIndent + - aTmpNumFormat.GetFirstLineOffset() + - aTmpNumFormat.GetAbsLSpace(); + } + else if ( ePosAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + nDiff = nNewIndent - aTmpNumFormat.GetIndentAt(); + } + if ( nDiff != 0 ) + { + ChangeIndent( nDiff ); + } +} + +void SwNumRule::Validate() +{ + o3tl::sorted_vector< SwList* > aLists; + for ( const SwTextNode* pTextNode : maTextNodeList ) + { + aLists.insert( pTextNode->GetDoc()->getIDocumentListsAccess().getListByName( pTextNode->GetListId() ) ); + } + for ( auto aList : aLists ) + aList->ValidateListTree(); + + SetInvalidRule(false); +} + +void SwNumRule::SetCountPhantoms(bool bCountPhantoms) +{ + mbCountPhantoms = bCountPhantoms; +} + +SwNumRule::tParagraphStyleList::size_type SwNumRule::GetParagraphStyleListSize() const +{ + return maParagraphStyleList.size(); +} + +void SwNumRule::AddParagraphStyle( SwTextFormatColl& rTextFormatColl ) +{ + tParagraphStyleList::iterator aIter = + std::find( maParagraphStyleList.begin(), maParagraphStyleList.end(), &rTextFormatColl ); + + if ( aIter == maParagraphStyleList.end() ) + { + maParagraphStyleList.push_back( &rTextFormatColl ); + } +} + +void SwNumRule::RemoveParagraphStyle( SwTextFormatColl& rTextFormatColl ) +{ + tParagraphStyleList::iterator aIter = + std::find( maParagraphStyleList.begin(), maParagraphStyleList.end(), &rTextFormatColl ); + + if ( aIter != maParagraphStyleList.end() ) + { + maParagraphStyleList.erase( aIter ); + } +} + +void SwNumRule::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRule")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("msName"), BAD_CAST(msName.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mnPoolFormatId"), BAD_CAST(OString::number(mnPoolFormatId).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mbAutoRuleFlag"), BAD_CAST(OString::boolean(mbAutoRuleFlag).getStr())); + + for (const auto& pFormat : maFormats) + { + if (!pFormat) + { + continue; + } + + pFormat->dumpAsXml(pWriter); + } + + xmlTextWriterEndElement(pWriter); +} + +void SwNumRule::GetGrabBagItem(uno::Any& rVal) const +{ + if (mpGrabBagItem) + mpGrabBagItem->QueryValue(rVal); + else + rVal <<= uno::Sequence<beans::PropertyValue>(); +} + +void SwNumRule::SetGrabBagItem(const uno::Any& rVal) +{ + if (!mpGrabBagItem) + mpGrabBagItem = std::make_shared<SfxGrabBagItem>(); + + mpGrabBagItem->PutValue(rVal, 0); +} + +namespace numfunc +{ + namespace { + + /** class containing default bullet list configuration data */ + class SwDefBulletConfig : private utl::ConfigItem + { + public: + static SwDefBulletConfig& getInstance(); + + const OUString& GetFontname() const + { + return msFontname; + } + + bool IsFontnameUserDefined() const + { + return mbUserDefinedFontname; + } + + const vcl::Font& GetFont() const + { + return *mpFont; + } + + sal_Unicode GetChar( sal_uInt8 p_nListLevel ) const + { + if (p_nListLevel >= MAXLEVEL) + { + p_nListLevel = MAXLEVEL - 1; + } + + return mnLevelChars[p_nListLevel]; + } + + SwDefBulletConfig(); + + private: + /** sets internal default bullet configuration data to default values */ + void SetToDefault(); + + /** returns sequence of default bullet configuration property names */ + static uno::Sequence<OUString> GetPropNames(); + + /** loads default bullet configuration properties and applies + values to internal data */ + void LoadConfig(); + + /** initialize font instance for default bullet list */ + void InitFont(); + + /** catches notification about changed default bullet configuration data */ + virtual void Notify( const uno::Sequence<OUString>& aPropertyNames ) override; + virtual void ImplCommit() override; + + // default bullet list configuration data + OUString msFontname; + bool mbUserDefinedFontname; + FontWeight meFontWeight; + FontItalic meFontItalic; + sal_Unicode mnLevelChars[MAXLEVEL]; + + // default bullet list font instance + std::unique_ptr<vcl::Font> mpFont; + }; + + class theSwDefBulletConfig + : public rtl::Static<SwDefBulletConfig, theSwDefBulletConfig>{}; + } + + SwDefBulletConfig& SwDefBulletConfig::getInstance() + { + return theSwDefBulletConfig::get(); + } + + SwDefBulletConfig::SwDefBulletConfig() + : ConfigItem( "Office.Writer/Numbering/DefaultBulletList" ), + // default bullet font is now OpenSymbol + msFontname( OUString("OpenSymbol") ), + mbUserDefinedFontname( false ), + meFontWeight( WEIGHT_DONTKNOW ), + meFontItalic( ITALIC_NONE ) + { + SetToDefault(); + LoadConfig(); + InitFont(); + + // enable notification for changes on default bullet configuration change + EnableNotification( GetPropNames() ); + } + + void SwDefBulletConfig::SetToDefault() + { + msFontname = "OpenSymbol"; + mbUserDefinedFontname = false; + meFontWeight = WEIGHT_DONTKNOW; + meFontItalic = ITALIC_NONE; + + mnLevelChars[0] = 0x2022; + mnLevelChars[1] = 0x25e6; + mnLevelChars[2] = 0x25aa; + mnLevelChars[3] = 0x2022; + mnLevelChars[4] = 0x25e6; + mnLevelChars[5] = 0x25aa; + mnLevelChars[6] = 0x2022; + mnLevelChars[7] = 0x25e6; + mnLevelChars[8] = 0x25aa; + mnLevelChars[9] = 0x2022; + } + + uno::Sequence<OUString> SwDefBulletConfig::GetPropNames() + { + uno::Sequence<OUString> aPropNames(13); + OUString* pNames = aPropNames.getArray(); + pNames[0] = "BulletFont/FontFamilyname"; + pNames[1] = "BulletFont/FontWeight"; + pNames[2] = "BulletFont/FontItalic"; + pNames[3] = "BulletCharLvl1"; + pNames[4] = "BulletCharLvl2"; + pNames[5] = "BulletCharLvl3"; + pNames[6] = "BulletCharLvl4"; + pNames[7] = "BulletCharLvl5"; + pNames[8] = "BulletCharLvl6"; + pNames[9] = "BulletCharLvl7"; + pNames[10] = "BulletCharLvl8"; + pNames[11] = "BulletCharLvl9"; + pNames[12] = "BulletCharLvl10"; + + return aPropNames; + } + + void SwDefBulletConfig::LoadConfig() + { + uno::Sequence<OUString> aPropNames = GetPropNames(); + uno::Sequence<uno::Any> aValues = + GetProperties( aPropNames ); + const uno::Any* pValues = aValues.getConstArray(); + OSL_ENSURE( aValues.getLength() == aPropNames.getLength(), + "<SwDefBulletConfig::SwDefBulletConfig()> - GetProperties failed"); + if ( aValues.getLength() == aPropNames.getLength() ) + { + for ( int nProp = 0; nProp < aPropNames.getLength(); ++nProp ) + { + if ( pValues[nProp].hasValue() ) + { + switch ( nProp ) + { + case 0: + { + OUString aStr; + pValues[nProp] >>= aStr; + msFontname = aStr; + mbUserDefinedFontname = true; + } + break; + case 1: + case 2: + { + sal_Int16 nTmp = 0; + pValues[nProp] >>= nTmp; + if ( nProp == 1 ) + meFontWeight = static_cast<FontWeight>(nTmp); + else if ( nProp == 2 ) + meFontItalic = static_cast<FontItalic>(nTmp); + } + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + { + sal_Unicode cChar = sal_Unicode(); + pValues[nProp] >>= cChar; + mnLevelChars[nProp-3] = cChar; + } + break; + } + } + } + } + + } + + void SwDefBulletConfig::InitFont() + { + mpFont.reset( new vcl::Font( msFontname, OUString(), Size( 0, 14 ) ) ); + mpFont->SetWeight( meFontWeight ); + mpFont->SetItalic( meFontItalic ); + mpFont->SetCharSet( RTL_TEXTENCODING_SYMBOL ); + } + + void SwDefBulletConfig::Notify( const uno::Sequence<OUString>& ) + { + SetToDefault(); + LoadConfig(); + InitFont(); + } + + void SwDefBulletConfig::ImplCommit() + { + } + + OUString const & GetDefBulletFontname() + { + return SwDefBulletConfig::getInstance().GetFontname(); + } + + bool IsDefBulletFontUserDefined() + { + return SwDefBulletConfig::getInstance().IsFontnameUserDefined(); + } + + const vcl::Font& GetDefBulletFont() + { + return SwDefBulletConfig::getInstance().GetFont(); + } + + sal_Unicode GetBulletChar( sal_uInt8 nLevel ) + { + return SwDefBulletConfig::getInstance().GetChar( nLevel ); + } + + namespace { + + /** class containing configuration data about user interface behavior + regarding lists and list items. + configuration item about behavior of <TAB>/<SHIFT-TAB>-key at first + position of first list item + */ + class SwNumberingUIBehaviorConfig : private utl::ConfigItem + { + public: + static SwNumberingUIBehaviorConfig& getInstance(); + + bool ChangeIndentOnTabAtFirstPosOfFirstListItem() const + { + return mbChangeIndentOnTabAtFirstPosOfFirstListItem; + } + + SwNumberingUIBehaviorConfig(); + + private: + + /** sets internal configuration data to default values */ + void SetToDefault(); + + /** returns sequence of configuration property names */ + static css::uno::Sequence<OUString> GetPropNames(); + + /** loads configuration properties and applies values to internal data */ + void LoadConfig(); + + /** catches notification about changed configuration data */ + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames ) override; + virtual void ImplCommit() override; + + // configuration data + bool mbChangeIndentOnTabAtFirstPosOfFirstListItem; + }; + + class theSwNumberingUIBehaviorConfig : public rtl::Static<SwNumberingUIBehaviorConfig, theSwNumberingUIBehaviorConfig>{}; + } + + SwNumberingUIBehaviorConfig& SwNumberingUIBehaviorConfig::getInstance() + { + return theSwNumberingUIBehaviorConfig::get(); + } + + SwNumberingUIBehaviorConfig::SwNumberingUIBehaviorConfig() + : ConfigItem( "Office.Writer/Numbering/UserInterfaceBehavior" ), + mbChangeIndentOnTabAtFirstPosOfFirstListItem( true ) + { + SetToDefault(); + LoadConfig(); + + // enable notification for changes on configuration change + EnableNotification( GetPropNames() ); + } + + void SwNumberingUIBehaviorConfig::SetToDefault() + { + mbChangeIndentOnTabAtFirstPosOfFirstListItem = true; + } + + css::uno::Sequence<OUString> SwNumberingUIBehaviorConfig::GetPropNames() + { + css::uno::Sequence<OUString> aPropNames { "ChangeIndentOnTabAtFirstPosOfFirstListItem" }; + + return aPropNames; + } + + void SwNumberingUIBehaviorConfig::ImplCommit() {} + + void SwNumberingUIBehaviorConfig::LoadConfig() + { + css::uno::Sequence<OUString> aPropNames = GetPropNames(); + css::uno::Sequence<css::uno::Any> aValues = GetProperties( aPropNames ); + const css::uno::Any* pValues = aValues.getConstArray(); + OSL_ENSURE( aValues.getLength() == aPropNames.getLength(), + "<SwNumberingUIBehaviorConfig::LoadConfig()> - GetProperties failed"); + if ( aValues.getLength() == aPropNames.getLength() ) + { + for ( int nProp = 0; nProp < aPropNames.getLength(); ++nProp ) + { + if ( pValues[nProp].hasValue() ) + { + switch ( nProp ) + { + case 0: + { + pValues[nProp] >>= mbChangeIndentOnTabAtFirstPosOfFirstListItem; + } + break; + default: + { + OSL_FAIL( "<SwNumberingUIBehaviorConfig::LoadConfig()> - unknown configuration property"); + } + } + } + } + } + } + + void SwNumberingUIBehaviorConfig::Notify( const css::uno::Sequence<OUString>& ) + { + SetToDefault(); + LoadConfig(); + } + + bool ChangeIndentOnTabAtFirstPosOfFirstListItem() + { + return SwNumberingUIBehaviorConfig::getInstance().ChangeIndentOnTabAtFirstPosOfFirstListItem(); + } + + SvxNumberFormat::SvxNumPositionAndSpaceMode GetDefaultPositionAndSpaceMode() + { + if (utl::ConfigManager::IsFuzzing()) + return SvxNumberFormat::LABEL_ALIGNMENT; + + SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode; + SvtSaveOptions aSaveOptions; + switch (aSaveOptions.GetODFSaneDefaultVersion()) + { + case SvtSaveOptions::ODFSVER_010: + case SvtSaveOptions::ODFSVER_011: + { + ePosAndSpaceMode = SvxNumberFormat::LABEL_WIDTH_AND_POSITION; + } + break; + default: // >= ODFSVER_012 + { + ePosAndSpaceMode = SvxNumberFormat::LABEL_ALIGNMENT; + } + } + + return ePosAndSpaceMode; + } +} + +void SwNumRuleTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRuleTable")); + for (SwNumRule* pNumRule : *this) + pNumRule->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/poolfmt.cxx b/sw/source/core/doc/poolfmt.cxx new file mode 100644 index 000000000..326e05eaf --- /dev/null +++ b/sw/source/core/doc/poolfmt.cxx @@ -0,0 +1,301 @@ +/* -*- 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 <i18nlangtag/mslangid.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <osl/diagnose.h> +#include <doc.hxx> +#include <IDocumentState.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <fmtcol.hxx> +#include <numrule.hxx> +#include <swtable.hxx> +#include <tblafmt.hxx> +#include <hints.hxx> + +using namespace ::editeng; +using namespace ::com::sun::star; + +void SetAllScriptItem( SfxItemSet& rSet, const SfxPoolItem& rItem ) +{ + rSet.Put( rItem ); + sal_uInt16 nWhCJK = 0, nWhCTL = 0; + switch( rItem.Which() ) + { + case RES_CHRATR_FONTSIZE: + nWhCJK = RES_CHRATR_CJK_FONTSIZE; + nWhCTL = RES_CHRATR_CTL_FONTSIZE; + break; + case RES_CHRATR_FONT: + nWhCJK = RES_CHRATR_CJK_FONT; + nWhCTL = RES_CHRATR_CTL_FONT; + break; + case RES_CHRATR_LANGUAGE: + nWhCJK = RES_CHRATR_CJK_LANGUAGE; + nWhCTL = RES_CHRATR_CTL_LANGUAGE; + break; + case RES_CHRATR_POSTURE: + nWhCJK = RES_CHRATR_CJK_POSTURE; + nWhCTL = RES_CHRATR_CTL_POSTURE; + break; + case RES_CHRATR_WEIGHT: + nWhCJK = RES_CHRATR_CJK_WEIGHT; + nWhCTL = RES_CHRATR_CTL_WEIGHT; + break; + } + + if( nWhCJK ) + rSet.Put( rItem.CloneSetWhich(nWhCJK) ); + if( nWhCTL ) + rSet.Put( rItem.CloneSetWhich(nWhCTL) ); +} + +/// Return the AutoCollection by its Id. If it doesn't +/// exist yet, create it. +/// If the String pointer is defined, then only query for +/// the Attribute descriptions. It doesn't create a style! +SvxFrameDirection GetDefaultFrameDirection(LanguageType nLanguage) +{ + return MsLangId::isRightToLeft(nLanguage) ? + SvxFrameDirection::Horizontal_RL_TB : SvxFrameDirection::Horizontal_LR_TB; +} + +// See if the Paragraph/Character/Frame/Page style is in use +bool SwDoc::IsUsed( const SwModify& rModify ) const +{ + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + SwAutoFormatGetDocNode aGetHt( &GetNodes() ); + return !rModify.GetInfo( aGetHt ); +} + +// See if Table style is in use +bool SwDoc::IsUsed( const SwTableAutoFormat& rTableAutoFormat) const +{ + 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() == rTableAutoFormat.GetName()) + return true; + } + return false; +} + +// See if the NumRule is used +bool SwDoc::IsUsed( const SwNumRule& rRule ) +{ + bool bUsed = rRule.GetTextNodeListSize() > 0 || + rRule.GetParagraphStyleListSize() > 0 || + rRule.IsUsedByRedline(); + + return bUsed; +} + +const OUString* SwDoc::GetDocPattern(size_t const nPos) const +{ + if (nPos >= m_PatternNames.size()) + return nullptr; + return &m_PatternNames[nPos]; +} + +// Look for the style name's position. If it doesn't exist, +// insert an anew +size_t SwDoc::SetDocPattern(const OUString& rPatternName) +{ + OSL_ENSURE( !rPatternName.isEmpty(), "no Document style name" ); + + auto const iter( + std::find(m_PatternNames.begin(), m_PatternNames.end(), rPatternName)); + if (iter != m_PatternNames.end()) + { + return std::distance(m_PatternNames.begin(), iter); + } + else + { + m_PatternNames.push_back(rPatternName); + getIDocumentState().SetModified(); + return m_PatternNames.size() - 1; + } +} + +sal_uInt16 GetPoolParent( sal_uInt16 nId ) +{ + sal_uInt16 nRet = USHRT_MAX; + if( POOLGRP_NOCOLLID & nId ) // 1 == Formats / 0 == Collections + { + switch( ( COLL_GET_RANGE_BITS | POOLGRP_NOCOLLID ) & nId ) + { + case POOLGRP_CHARFMT: + case POOLGRP_FRAMEFMT: + nRet = 0; // derived from the default + break; + case POOLGRP_PAGEDESC: + case POOLGRP_NUMRULE: + break; // there are no derivations + } + } + else + { + switch( COLL_GET_RANGE_BITS & nId ) + { + case COLL_TEXT_BITS: + switch( nId ) + { + case RES_POOLCOLL_STANDARD: + nRet = 0; break; + case RES_POOLCOLL_TEXT_IDENT: + case RES_POOLCOLL_TEXT_NEGIDENT: + case RES_POOLCOLL_TEXT_MOVE: + case RES_POOLCOLL_CONFRONTATION: + case RES_POOLCOLL_MARGINAL: + nRet = RES_POOLCOLL_TEXT; break; + + case RES_POOLCOLL_TEXT: + case RES_POOLCOLL_GREETING: + case RES_POOLCOLL_SIGNATURE: + case RES_POOLCOLL_HEADLINE_BASE: + nRet = RES_POOLCOLL_STANDARD; break; + + case RES_POOLCOLL_HEADLINE1: + case RES_POOLCOLL_HEADLINE2: + case RES_POOLCOLL_HEADLINE3: + case RES_POOLCOLL_HEADLINE4: + case RES_POOLCOLL_HEADLINE5: + case RES_POOLCOLL_HEADLINE6: + case RES_POOLCOLL_HEADLINE7: + case RES_POOLCOLL_HEADLINE8: + case RES_POOLCOLL_HEADLINE9: + case RES_POOLCOLL_HEADLINE10: + nRet = RES_POOLCOLL_HEADLINE_BASE; break; + } + break; + + case COLL_LISTS_BITS: + switch( nId ) + { + case RES_POOLCOLL_NUMBER_BULLET_BASE: + nRet = RES_POOLCOLL_TEXT; break; + + default: + nRet = RES_POOLCOLL_NUMBER_BULLET_BASE; break; + } + break; + + case COLL_EXTRA_BITS: + switch( nId ) + { + case RES_POOLCOLL_TABLE_HDLN: + nRet = RES_POOLCOLL_TABLE; break; + + case RES_POOLCOLL_FRAME: + case RES_POOLCOLL_TABLE: + case RES_POOLCOLL_FOOTNOTE: + case RES_POOLCOLL_ENDNOTE: + case RES_POOLCOLL_JAKETADRESS: + case RES_POOLCOLL_SENDADRESS: + case RES_POOLCOLL_HEADERFOOTER: + case RES_POOLCOLL_LABEL: + nRet = RES_POOLCOLL_STANDARD; break; + case RES_POOLCOLL_HEADER: + nRet = RES_POOLCOLL_HEADERFOOTER; break; + case RES_POOLCOLL_HEADERL: + case RES_POOLCOLL_HEADERR: + nRet = RES_POOLCOLL_HEADER; break; + case RES_POOLCOLL_FOOTER: + nRet = RES_POOLCOLL_HEADERFOOTER; break; + case RES_POOLCOLL_FOOTERL: + case RES_POOLCOLL_FOOTERR: + nRet = RES_POOLCOLL_FOOTER; break; + + case RES_POOLCOLL_LABEL_ABB: + case RES_POOLCOLL_LABEL_TABLE: + case RES_POOLCOLL_LABEL_FRAME: + case RES_POOLCOLL_LABEL_DRAWING: + case RES_POOLCOLL_LABEL_FIGURE: + nRet = RES_POOLCOLL_LABEL; break; + } + break; + + case COLL_REGISTER_BITS: + switch( nId ) + { + case RES_POOLCOLL_REGISTER_BASE: + nRet = RES_POOLCOLL_STANDARD; break; + + case RES_POOLCOLL_TOX_IDXH: + nRet = RES_POOLCOLL_HEADLINE_BASE; break; + + case RES_POOLCOLL_TOX_USERH: + case RES_POOLCOLL_TOX_CNTNTH: + case RES_POOLCOLL_TOX_ILLUSH: + case RES_POOLCOLL_TOX_OBJECTH: + case RES_POOLCOLL_TOX_TABLESH: + case RES_POOLCOLL_TOX_AUTHORITIESH: + nRet = RES_POOLCOLL_TOX_IDXH; break; + + default: + nRet = RES_POOLCOLL_REGISTER_BASE; break; + } + break; + + case COLL_DOC_BITS: + nRet = RES_POOLCOLL_HEADLINE_BASE; + break; + + case COLL_HTML_BITS: + nRet = RES_POOLCOLL_STANDARD; + break; + } + } + + return nRet; +} + +void SwDoc::RemoveAllFormatLanguageDependencies() +{ + /* Restore the language independent pool defaults and styles. */ + GetAttrPool().ResetPoolDefaultItem( RES_PARATR_ADJUST ); + + SwTextFormatColl * pTextFormatColl = getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ); + + pTextFormatColl->ResetFormatAttr( RES_PARATR_ADJUST ); + /* koreans do not like SvxScriptItem(TRUE) */ + pTextFormatColl->ResetFormatAttr( RES_PARATR_SCRIPTSPACE ); + + SvxFrameDirectionItem aFrameDir( SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR ); + + size_t nCount = GetPageDescCnt(); + for( size_t i=0; i<nCount; ++i ) + { + SwPageDesc& rDesc = GetPageDesc( i ); + rDesc.GetMaster().SetFormatAttr( aFrameDir ); + rDesc.GetLeft().SetFormatAttr( aFrameDir ); + } + + //#i16874# AutoKerning as default for new documents + GetAttrPool().ResetPoolDefaultItem( RES_CHRATR_AUTOKERN ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/rdfhelper.cxx b/sw/source/core/doc/rdfhelper.cxx new file mode 100644 index 000000000..d404b477e --- /dev/null +++ b/sw/source/core/doc/rdfhelper.cxx @@ -0,0 +1,264 @@ +/* -*- 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/. + */ + +#include <rdfhelper.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/rdf/Literal.hpp> +#include <com/sun/star/rdf/Statement.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> + +#include <comphelper/processfactory.hxx> + +#include <doc.hxx> +#include <docsh.hxx> +#include <ndtxt.hxx> +#include <unoparagraph.hxx> + +using namespace com::sun::star; + +css::uno::Sequence<css::uno::Reference<css::rdf::XURI>> SwRDFHelper::getGraphNames( + const css::uno::Reference<rdf::XDocumentMetadataAccess>& xDocumentMetadataAccess, + const css::uno::Reference<rdf::XURI>& xType) +{ + try + { + return xDocumentMetadataAccess->getMetadataGraphsWithType(xType); + } + catch (const uno::RuntimeException&) + { + return uno::Sequence<uno::Reference<rdf::XURI>>(); + } +} + +css::uno::Sequence<uno::Reference<css::rdf::XURI>> +SwRDFHelper::getGraphNames(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType) +{ + try + { + uno::Reference<uno::XComponentContext> xComponentContext( + comphelper::getProcessComponentContext()); + // rdf::URI::create may fail with type: com.sun.star.uno.DeploymentException + // message: component context fails to supply service com.sun.star.rdf.URI of type com.sun.star.rdf.XURI + // context: cppu::ComponentContext + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, + uno::UNO_QUERY); + return getGraphNames(xDocumentMetadataAccess, xType); + } + catch (const ::css::uno::Exception&) + { + return uno::Sequence<uno::Reference<rdf::XURI>>(); + } +} + +std::map<OUString, OUString> +SwRDFHelper::getStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const uno::Sequence<uno::Reference<css::rdf::XURI>>& rGraphNames, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + std::map<OUString, OUString> aRet; + if (!rGraphNames.hasElements()) + return aRet; + + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Reference<rdf::XRepository>& xRepo = xDocumentMetadataAccess->getRDFRepository(); + for (const uno::Reference<rdf::XURI>& xGraphName : rGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xRepo->getGraph(xGraphName); + if (!xGraph.is()) + continue; + + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements( + xSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + const rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + aRet[aStatement.Predicate->getStringValue()] = aStatement.Object->getStringValue(); + } + } + + return aRet; +} + +std::map<OUString, OUString> +SwRDFHelper::getStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + return getStatements(xModel, getGraphNames(xModel, rType), xSubject); +} + +void SwRDFHelper::addStatement(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, const OUString& rPath, + const css::uno::Reference<css::rdf::XResource>& xSubject, + const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + uno::Reference<rdf::XURI> xGraphName; + if (aGraphNames.hasElements()) + xGraphName = aGraphNames[0]; + else + { + uno::Sequence< uno::Reference<rdf::XURI> > xTypes = { xType }; + xGraphName = xDocumentMetadataAccess->addMetadataFile(rPath, xTypes); + } + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->addStatement(xSubject, xKey, xValue); +} + +bool SwRDFHelper::hasMetadataGraph(const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rType) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + return getGraphNames(xDocumentMetadataAccess, xType).hasElements(); +} + +void SwRDFHelper::removeStatement(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject, + const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(aGraphNames[0]); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->removeStatements(xSubject, xKey, xValue); +} + +void SwRDFHelper::clearStatements(const css::uno::Reference<css::frame::XModel>& xModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSubject) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + for (const uno::Reference<rdf::XURI>& xGraphName : aGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements(xSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, aStatement.Predicate->getStringValue()); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, aStatement.Object->getStringValue()); + xGraph->removeStatements(xSubject, xKey, xValue); + } + } +} + +void SwRDFHelper::cloneStatements(const css::uno::Reference<css::frame::XModel>& xSrcModel, + const css::uno::Reference<css::frame::XModel>& xDstModel, + const OUString& rType, + const css::uno::Reference<css::rdf::XResource>& xSrcSubject, + const css::uno::Reference<css::rdf::XResource>& xDstSubject) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(xSrcModel, uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + for (const uno::Reference<rdf::XURI>& xGraphName : aGraphNames) + { + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<container::XEnumeration> xStatements = xGraph->getStatements(xSrcSubject, uno::Reference<rdf::XURI>(), uno::Reference<rdf::XURI>()); + while (xStatements->hasMoreElements()) + { + const rdf::Statement aStatement = xStatements->nextElement().get<rdf::Statement>(); + + const OUString sKey = aStatement.Predicate->getStringValue(); + const OUString sValue = aStatement.Object->getStringValue(); + addStatement(xDstModel, rType, xGraphName->getLocalName(), xDstSubject, sKey, sValue); + } + } +} + +std::map<OUString, OUString> SwRDFHelper::getTextNodeStatements(const OUString& rType, SwTextNode& rTextNode) +{ + uno::Reference<rdf::XResource> xTextNode(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + return getStatements(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), rType, xTextNode); +} + +void SwRDFHelper::addTextNodeStatement(const OUString& rType, const OUString& rPath, SwTextNode& rTextNode, const OUString& rKey, const OUString& rValue) +{ + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + addStatement(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), rType, rPath, xSubject, rKey, rValue); +} + +void SwRDFHelper::removeTextNodeStatement(const OUString& rType, SwTextNode& rTextNode, const OUString& rKey, const OUString& rValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + if (!aGraphNames.hasElements()) + return; + + uno::Reference<rdf::XURI> xGraphName = aGraphNames[0]; + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + uno::Reference<rdf::XLiteral> xValue = rdf::Literal::create(xComponentContext, rValue); + xGraph->removeStatements(xSubject, xKey, xValue); +} + +void SwRDFHelper::updateTextNodeStatement(const OUString& rType, const OUString& rPath, SwTextNode& rTextNode, const OUString& rKey, const OUString& rOldValue, const OUString& rNewValue) +{ + uno::Reference<uno::XComponentContext> xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference<rdf::XURI> xType = rdf::URI::create(xComponentContext, rType); + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(rTextNode.GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + const uno::Sequence< uno::Reference<rdf::XURI> > aGraphNames = getGraphNames(xDocumentMetadataAccess, xType); + uno::Reference<rdf::XURI> xGraphName; + if (aGraphNames.hasElements()) + { + xGraphName = aGraphNames[0]; + } + else + { + uno::Sequence< uno::Reference<rdf::XURI> > xTypes = { xType }; + xGraphName = xDocumentMetadataAccess->addMetadataFile(rPath, xTypes); + } + + uno::Reference<rdf::XNamedGraph> xGraph = xDocumentMetadataAccess->getRDFRepository()->getGraph(xGraphName); + uno::Reference<rdf::XResource> xSubject(SwXParagraph::CreateXParagraph(*rTextNode.GetDoc(), &rTextNode), uno::UNO_QUERY); + uno::Reference<rdf::XURI> xKey = rdf::URI::create(xComponentContext, rKey); + + if (aGraphNames.hasElements()) + { + // Remove the old value. + uno::Reference<rdf::XLiteral> xOldValue = rdf::Literal::create(xComponentContext, rOldValue); + xGraph->removeStatements(xSubject, xKey, xOldValue); + } + + // Now add it with new value. + uno::Reference<rdf::XLiteral> xNewValue = rdf::Literal::create(xComponentContext, rNewValue); + xGraph->addStatement(xSubject, xKey, xNewValue); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/sortopt.cxx b/sw/source/core/doc/sortopt.cxx new file mode 100644 index 000000000..06ac05856 --- /dev/null +++ b/sw/source/core/doc/sortopt.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <i18nlangtag/lang.h> +#include <sortopt.hxx> + +SwSortKey::SwSortKey() : + eSortOrder( SwSortOrder::Ascending ), + nColumnId( 0 ), + bIsNumeric( true ) +{ +} + +SwSortKey::SwSortKey(sal_uInt16 nId, const OUString& rSrtType, SwSortOrder eOrder) : + sSortType( rSrtType ), + eSortOrder( eOrder ), + nColumnId( nId ), + bIsNumeric( rSrtType.isEmpty() ) +{ +} + +SwSortOptions::SwSortOptions() + : eDirection( SwSortDirection::Rows ), + cDeli( 9 ), + nLanguage( LANGUAGE_SYSTEM ), + bTable( false ), + bIgnoreCase( false ) +{ +} + +SwSortOptions::SwSortOptions(const SwSortOptions& rOpt) : + eDirection( rOpt.eDirection ), + cDeli( rOpt.cDeli ), + nLanguage( rOpt.nLanguage ), + bTable( rOpt.bTable ), + bIgnoreCase( rOpt.bIgnoreCase ) +{ + for(auto const & pKey : rOpt.aKeys) + { + aKeys.push_back( std::make_unique<SwSortKey>(*pKey) ); + } +} + +SwSortOptions::~SwSortOptions() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swserv.cxx b/sw/source/core/doc/swserv.cxx new file mode 100644 index 000000000..95b3982ea --- /dev/null +++ b/sw/source/core/doc/swserv.cxx @@ -0,0 +1,320 @@ +/* -*- 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 <sot/exchange.hxx> +#include <sfx2/linkmgr.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <doc.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swserv.hxx> +#include <swbaslnk.hxx> +#include <mvsave.hxx> +#include <IMark.hxx> +#include <bookmrk.hxx> +#include <pam.hxx> +#include <shellio.hxx> + +using namespace ::com::sun::star; + +SwServerObject::~SwServerObject() +{ +} + +bool SwServerObject::GetData( uno::Any & rData, + const OUString & rMimeType, bool ) +{ + bool bRet = false; + WriterRef xWrt; + switch( SotExchange::GetFormatIdFromMimeType( rMimeType ) ) + { + case SotClipboardFormatId::STRING: + ::GetASCWriter( OUString(), OUString(), xWrt ); + break; + + case SotClipboardFormatId::RTF: + case SotClipboardFormatId::RICHTEXT: + // mba: no BaseURL for data exchange + ::GetRTFWriter( OUString(), OUString(), xWrt ); + break; + default: break; + } + + if( xWrt.is() ) + { + SwPaM* pPam = nullptr; + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + // Span area + pPam = new SwPaM( m_CNTNT_TYPE.pBkmk->GetMarkPos(), + m_CNTNT_TYPE.pBkmk->GetOtherMarkPos() ); + } + break; + + case TABLE_SERVER: + pPam = new SwPaM( *m_CNTNT_TYPE.pTableNd, + *m_CNTNT_TYPE.pTableNd->EndOfSectionNode() ); + break; + + case SECTION_SERVER: + pPam = new SwPaM( SwPosition( *m_CNTNT_TYPE.pSectNd ) ); + pPam->Move( fnMoveForward ); + pPam->SetMark(); + pPam->GetPoint()->nNode = *m_CNTNT_TYPE.pSectNd->EndOfSectionNode(); + pPam->Move( fnMoveBackward ); + break; + case NONE_SERVER: break; + } + + if( pPam ) + { + // Create stream + SvMemoryStream aMemStm( 65535, 65535 ); + SwWriter aWrt( aMemStm, *pPam, false ); + if( !aWrt.Write( xWrt ).IsError() ) + { + aMemStm.WriteChar( '\0' ); // append a zero char + rData <<= uno::Sequence< sal_Int8 >( + static_cast<sal_Int8 const *>(aMemStm.GetData()), + aMemStm.Tell() ); + bRet = true; + } + delete pPam; + } + } + return bRet; +} + +void SwServerObject::SendDataChanged( const SwPosition& rPos ) +{ + // Is someone interested in our changes? + if( HasDataLinks() ) + { + bool bCall = false; + const SwStartNode* pNd = nullptr; + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + bCall = m_CNTNT_TYPE.pBkmk->GetMarkStart() <= rPos + && rPos < m_CNTNT_TYPE.pBkmk->GetMarkEnd(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + case NONE_SERVER: break; + } + if( pNd ) + { + sal_uLong nNd = rPos.nNode.GetIndex(); + bCall = pNd->GetIndex() < nNd && nNd < pNd->EndOfSectionIndex(); + } + + if( bCall ) + { + // Recognize recursions and flag them + IsLinkInServer( nullptr ); + SvLinkSource::NotifyDataChanged(); + } + } +} + +void SwServerObject::SendDataChanged( const SwPaM& rRange ) +{ + // Is someone interested in our changes? + if( HasDataLinks() ) + { + bool bCall = false; + const SwStartNode* pNd = nullptr; + const SwPosition* pStt = rRange.Start(), *pEnd = rRange.End(); + switch( m_eType ) + { + case BOOKMARK_SERVER: + if(m_CNTNT_TYPE.pBkmk->IsExpanded()) + { + bCall = *pStt <= m_CNTNT_TYPE.pBkmk->GetMarkEnd() + && *pEnd > m_CNTNT_TYPE.pBkmk->GetMarkStart(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + case NONE_SERVER: break; + } + if( pNd ) + { + // Is the start area within the node area? + bCall = pStt->nNode.GetIndex() < pNd->EndOfSectionIndex() && + pEnd->nNode.GetIndex() >= pNd->GetIndex(); + } + + if( bCall ) + { + // Recognize recursions and flag them + IsLinkInServer( nullptr ); + SvLinkSource::NotifyDataChanged(); + } + } +} + +bool SwServerObject::IsLinkInServer( const SwBaseLink* pChkLnk ) const +{ + sal_uLong nSttNd = 0, nEndNd = 0; + const SwNode* pNd = nullptr; + const SwNodes* pNds = nullptr; + + switch( m_eType ) + { + case BOOKMARK_SERVER: + if( m_CNTNT_TYPE.pBkmk->IsExpanded() ) + { + const SwPosition* pStt = &m_CNTNT_TYPE.pBkmk->GetMarkStart(), + * pEnd = &m_CNTNT_TYPE.pBkmk->GetMarkEnd(); + + nSttNd = pStt->nNode.GetIndex(); + nEndNd = pEnd->nNode.GetIndex(); + pNds = &pStt->nNode.GetNodes(); + } + break; + + case TABLE_SERVER: pNd = m_CNTNT_TYPE.pTableNd; break; + case SECTION_SERVER: pNd = m_CNTNT_TYPE.pSectNd; break; + + case SECTION_SERVER+1: + return true; + } + + if( pNd ) + { + nSttNd = pNd->GetIndex(); + nEndNd = pNd->EndOfSectionIndex(); + pNds = &pNd->GetNodes(); + } + + if( nSttNd && nEndNd ) + { + // Get LinkManager + const ::sfx2::SvBaseLinks& rLnks = pNds->GetDoc()->getIDocumentLinksAdministration().GetLinkManager().GetLinks(); + + // To avoid recursions: convert ServerType! + SwServerObject::ServerModes eSave = m_eType; + if( !pChkLnk ) + const_cast<SwServerObject*>(this)->m_eType = NONE_SERVER; + for( size_t n = rLnks.size(); n; ) + { + const ::sfx2::SvBaseLink* pLnk = &(*rLnks[ --n ]); + if (sfx2::SvBaseLinkObjectType::ClientGraphic != pLnk->GetObjType() && + dynamic_cast<const SwBaseLink*>( pLnk) != nullptr && + !static_cast<const SwBaseLink*>(pLnk)->IsNoDataFlag() && + static_cast<const SwBaseLink*>(pLnk)->IsInRange( nSttNd, nEndNd )) + { + if( pChkLnk ) + { + if( pLnk == pChkLnk || + static_cast<const SwBaseLink*>(pLnk)->IsRecursion( pChkLnk ) ) + return true; + } + else if( static_cast<const SwBaseLink*>(pLnk)->IsRecursion( static_cast<const SwBaseLink*>(pLnk) ) ) + const_cast<SwBaseLink*>(static_cast<const SwBaseLink*>(pLnk))->SetNoDataFlag(); + } + } + if( !pChkLnk ) + const_cast<SwServerObject*>(this)->m_eType = eSave; + } + + return false; +} + +void SwServerObject::SetNoServer() +{ + if(m_eType == BOOKMARK_SERVER && m_CNTNT_TYPE.pBkmk) + { + ::sw::mark::DdeBookmark* const pDdeBookmark = dynamic_cast< ::sw::mark::DdeBookmark* >(m_CNTNT_TYPE.pBkmk); + if(pDdeBookmark) + { + m_CNTNT_TYPE.pBkmk = nullptr; + m_eType = NONE_SERVER; + pDdeBookmark->SetRefObject(nullptr); + } + } +} + +void SwServerObject::SetDdeBookmark( ::sw::mark::IMark& rBookmark) +{ + ::sw::mark::DdeBookmark* const pDdeBookmark = dynamic_cast< ::sw::mark::DdeBookmark* >(&rBookmark); + if(pDdeBookmark) + { + m_eType = BOOKMARK_SERVER; + m_CNTNT_TYPE.pBkmk = &rBookmark; + pDdeBookmark->SetRefObject(this); + } + else + OSL_FAIL("SwServerObject::SetNoServer(..)" + " - setting a bookmark that is not DDE-capable"); +} + +SwDataChanged::SwDataChanged( const SwPaM& rPam ) + : m_pPam( &rPam ), m_pPos( nullptr ), m_pDoc( rPam.GetDoc() ) +{ + m_nContent = rPam.GetPoint()->nContent.GetIndex(); +} + +SwDataChanged::SwDataChanged( SwDoc* pDc, const SwPosition& rPos ) + : m_pPam( nullptr ), m_pPos( &rPos ), m_pDoc( pDc ) +{ + m_nContent = rPos.nContent.GetIndex(); +} + +SwDataChanged::~SwDataChanged() +{ + // JP 09.04.96: Only if the Layout is available (thus during input) + if( m_pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + const ::sfx2::SvLinkSources& rServers = m_pDoc->getIDocumentLinksAdministration().GetLinkManager().GetServers(); + + ::sfx2::SvLinkSources aTemp(rServers); + for( const auto& rpLinkSrc : aTemp ) + { + ::sfx2::SvLinkSourceRef refObj( rpLinkSrc ); + // Anyone else interested in the Object? + if( refObj->HasDataLinks() && dynamic_cast<const SwServerObject*>( refObj.get() ) != nullptr) + { + SwServerObject& rObj = *static_cast<SwServerObject*>( refObj.get() ); + if( m_pPos ) + rObj.SendDataChanged( *m_pPos ); + else + rObj.SendDataChanged( *m_pPam ); + } + + // We shouldn't have a connection anymore + if( !refObj->HasDataLinks() ) + { + // Then remove from the list + m_pDoc->getIDocumentLinksAdministration().GetLinkManager().RemoveServer( rpLinkSrc ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swstylemanager.cxx b/sw/source/core/doc/swstylemanager.cxx new file mode 100644 index 000000000..cfca5ee5b --- /dev/null +++ b/sw/source/core/doc/swstylemanager.cxx @@ -0,0 +1,158 @@ +/* -*- 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 "swstylemanager.hxx" +#include <svl/stylepool.hxx> +#include <istyleaccess.hxx> +#include <unordered_map> +#include <osl/diagnose.h> + +typedef std::unordered_map< OUString, + std::shared_ptr<SfxItemSet> > SwStyleNameCache; + +namespace { + +class SwStyleCache +{ + SwStyleNameCache mMap; +public: + SwStyleCache() {} + void addStyleName( const std::shared_ptr<SfxItemSet>& pStyle ) + { mMap[ StylePool::nameOf(pStyle) ] = pStyle; } + void addCompletePool( StylePool& rPool ); + std::shared_ptr<SfxItemSet> getByName( const OUString& rName ) { return mMap[rName]; } +}; + +} + +void SwStyleCache::addCompletePool( StylePool& rPool ) +{ + std::unique_ptr<IStylePoolIteratorAccess> pIter = rPool.createIterator(); + std::shared_ptr<SfxItemSet> pStyle = pIter->getNext(); + while( pStyle ) + { + OUString aName( StylePool::nameOf(pStyle) ); + mMap[ aName ] = pStyle; + pStyle = pIter->getNext(); + } +} + +namespace { + +class SwStyleManager : public IStyleAccess +{ + StylePool aAutoCharPool; + StylePool aAutoParaPool; + std::unique_ptr<SwStyleCache> mpCharCache; + std::unique_ptr<SwStyleCache> mpParaCache; + +public: + // accept empty item set for ignorable paragraph items. + explicit SwStyleManager( SfxItemSet const * pIgnorableParagraphItems ) + : aAutoCharPool(), + aAutoParaPool( pIgnorableParagraphItems ) + {} + virtual std::shared_ptr<SfxItemSet> getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName = nullptr ) override; + virtual std::shared_ptr<SfxItemSet> getByName( const OUString& rName, + IStyleAccess::SwAutoStyleFamily eFamily ) override; + virtual void getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles, + IStyleAccess::SwAutoStyleFamily eFamily ) override; + virtual std::shared_ptr<SfxItemSet> cacheAutomaticStyle( const SfxItemSet& rSet, + SwAutoStyleFamily eFamily ) override; + virtual void clearCaches() override; +}; + +} + +std::unique_ptr<IStyleAccess> createStyleManager( SfxItemSet const * pIgnorableParagraphItems ) +{ + return std::make_unique<SwStyleManager>( pIgnorableParagraphItems ); +} + +void SwStyleManager::clearCaches() +{ + mpCharCache.reset(); + mpParaCache.reset(); +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + return rAutoPool.insertItemSet( rSet, pParentName ); +} + +std::shared_ptr<SfxItemSet> SwStyleManager::cacheAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + std::shared_ptr<SfxItemSet> pStyle = rAutoPool.insertItemSet( rSet ); + if (eFamily == IStyleAccess::AUTO_STYLE_CHAR) + { + if (!mpCharCache) + mpCharCache.reset(new SwStyleCache()); + mpCharCache->addStyleName( pStyle ); + } + else + { + if (!mpParaCache) + mpParaCache.reset(new SwStyleCache()); + mpParaCache->addStyleName( pStyle ); + } + return pStyle; +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getByName( const OUString& rName, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + std::unique_ptr<SwStyleCache> &rpCache = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? mpCharCache : mpParaCache; + if( !rpCache ) + rpCache.reset(new SwStyleCache()); + std::shared_ptr<SfxItemSet> pStyle = rpCache->getByName( rName ); + if( !pStyle ) + { + // Ok, ok, it's allowed to ask for uncached styles (from UNO) but it should not be done + // during loading a document + OSL_FAIL( "Don't ask for uncached styles" ); + rpCache->addCompletePool( rAutoPool ); + pStyle = rpCache->getByName( rName ); + } + return pStyle; +} + +void SwStyleManager::getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? aAutoCharPool : aAutoParaPool; + // setup <StylePool> iterator, which skips unused styles and ignorable items + std::unique_ptr<IStylePoolIteratorAccess> pIter = rAutoPool.createIterator( true, true ); + std::shared_ptr<SfxItemSet> pStyle = pIter->getNext(); + while( pStyle ) + { + rStyles.push_back( pStyle ); + + pStyle = pIter->getNext(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/swstylemanager.hxx b/sw/source/core/doc/swstylemanager.hxx new file mode 100644 index 000000000..b1301b2f1 --- /dev/null +++ b/sw/source/core/doc/swstylemanager.hxx @@ -0,0 +1,32 @@ +/* -*- 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_DOC_SWSTYLEMANAGER_HXX +#define INCLUDED_SW_SOURCE_CORE_DOC_SWSTYLEMANAGER_HXX + +#include <memory> + +class IStyleAccess; +class SfxItemSet; + +std::unique_ptr<IStyleAccess> createStyleManager( SfxItemSet const * pIgnorableParagraphItems ); + +#endif // INCLUDED_SW_SOURCE_CORE_DOC_SWSTYLEMANAGER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblafmt.cxx b/sw/source/core/doc/tblafmt.cxx new file mode 100644 index 000000000..293e87898 --- /dev/null +++ b/sw/source/core/doc/tblafmt.cxx @@ -0,0 +1,1251 @@ +/* -*- 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 <comphelper/fileformat.h> +#include <tools/stream.hxx> +#include <sfx2/docfile.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/pathoptions.hxx> +#include <swtable.hxx> +#include <swtblfmt.hxx> +#include <com/sun/star/text/VertOrientation.hpp> +#include <swtypes.hxx> +#include <doc.hxx> +#include <poolfmt.hxx> +#include <tblafmt.hxx> +#include <cellatr.hxx> +#include <SwStyleNameMapper.hxx> +#include <hintids.hxx> +#include <fmtornt.hxx> +#include <editsh.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include <editeng/adjustitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/legacyitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <svx/algitem.hxx> +#include <svx/rotmodit.hxx> +#include <legacyitem.hxx> + +#include <memory> +#include <vector> + +/* + * XXX: BIG RED NOTICE! Changes MUST be binary file format compatible and MUST + * be synchronized with Calc's ScAutoFormat sc/source/core/tool/autoform.cxx + */ + +using ::editeng::SvxBorderLine; + +// until SO5PF +const sal_uInt16 AUTOFORMAT_ID_X = 9501; +const sal_uInt16 AUTOFORMAT_ID_358 = 9601; +const sal_uInt16 AUTOFORMAT_DATA_ID_X = 9502; + +// from SO5 +//! In follow-up versions these IDs' values need to increase +const sal_uInt16 AUTOFORMAT_ID_504 = 9801; +const sal_uInt16 AUTOFORMAT_DATA_ID_504 = 9802; + +const sal_uInt16 AUTOFORMAT_DATA_ID_552 = 9902; + +// --- from 680/dr25 on: store strings as UTF-8 +const sal_uInt16 AUTOFORMAT_ID_680DR25 = 10021; + +// --- Bug fix to fdo#31005: Table Autoformats does not save/apply all properties (Writer and Calc) +const sal_uInt16 AUTOFORMAT_ID_31005 = 10041; +const sal_uInt16 AUTOFORMAT_DATA_ID_31005 = 10042; + +// current version +const sal_uInt16 AUTOFORMAT_ID = AUTOFORMAT_ID_31005; +const sal_uInt16 AUTOFORMAT_DATA_ID = AUTOFORMAT_DATA_ID_31005; +const sal_uInt16 AUTOFORMAT_FILE_VERSION= SOFFICE_FILEFORMAT_50; + +SwBoxAutoFormat* SwTableAutoFormat::pDfltBoxAutoFormat = nullptr; + +#define AUTOTABLE_FORMAT_NAME "autotbl.fmt" + +namespace +{ + /// Begins a writer-specific data block. Call before serializing any writer-specific properties. + sal_uInt64 BeginSwBlock(SvStream& rStream) + { + // We need to write down the offset of the end of the writer-specific data, so that + // calc can skip it. We'll only have that value after writing the data, so we + // write a placeholder value first, write the data, then jump back and write the + // real offset. + + // Note that we explicitly use sal_uInt64 instead of sal_Size (which can be 32 + // or 64 depending on platform) to ensure 64-bit portability on this front. I don't + // actually know if autotbl.fmt as a whole is portable, since that requires all serialization + // logic to be written with portability in mind. + sal_uInt64 whereToWriteEndOfSwBlock = rStream.Tell(); + + rStream.WriteUInt64( 0 ); // endOfSwBlock + + return whereToWriteEndOfSwBlock; + } + + /// Ends a writer-specific data block. Call after serializing writer-specific properties. + /// Closes a corresponding BeginSwBlock call. + void EndSwBlock(SvStream& rStream, sal_uInt64 whereToWriteEndOfSwBlock) + { + sal_uInt64 endOfSwBlock = rStream.Tell(); + rStream.Seek(whereToWriteEndOfSwBlock); + rStream.WriteUInt64( endOfSwBlock ); + rStream.Seek(endOfSwBlock); + } + + /** + Helper class for writer-specific blocks. Begins a writer-specific block on construction, + and closes it on destruction. + + See also: BeginSwBlock and EndSwBlock. + */ + class WriterSpecificAutoFormatBlock + { + public: + explicit WriterSpecificAutoFormatBlock(SvStream &rStream) : _rStream(rStream), _whereToWriteEndOfBlock(BeginSwBlock(rStream)) + { + } + + ~WriterSpecificAutoFormatBlock() + { + EndSwBlock(_rStream, _whereToWriteEndOfBlock); + } + + private: + WriterSpecificAutoFormatBlock(WriterSpecificAutoFormatBlock const&) = delete; + WriterSpecificAutoFormatBlock& operator=(WriterSpecificAutoFormatBlock const&) = delete; + + SvStream &_rStream; + sal_uInt64 _whereToWriteEndOfBlock; + }; + + /// Checks whether a writer-specific block exists (i.e. size is not zero) + sal_Int64 WriterSpecificBlockExists(SvStream &stream) + { + sal_uInt64 endOfSwBlock = 0; + stream.ReadUInt64( endOfSwBlock ); + + // end-of-block pointing to itself indicates a zero-size block. + return endOfSwBlock - stream.Tell(); + } +} + +// Struct with version numbers of the Items + +struct SwAfVersions : public AutoFormatVersions +{ +public: + sal_uInt16 m_nTextOrientationVersion; + sal_uInt16 m_nVerticalAlignmentVersion; + + SwAfVersions(); + void Load( SvStream& rStream, sal_uInt16 nVer ); + static void Write(SvStream& rStream, sal_uInt16 fileVersion); +}; + +SwAfVersions::SwAfVersions() +: AutoFormatVersions(), + m_nTextOrientationVersion(0), + m_nVerticalAlignmentVersion(0) +{ +} + +void SwAfVersions::Load( SvStream& rStream, sal_uInt16 nVer ) +{ + LoadBlockA(rStream, nVer); + if (nVer >= AUTOFORMAT_ID_31005 && WriterSpecificBlockExists(rStream)) + { + rStream.ReadUInt16( m_nTextOrientationVersion ); + rStream.ReadUInt16( m_nVerticalAlignmentVersion ); + } + LoadBlockB(rStream, nVer); +} + +void SwAfVersions::Write(SvStream& rStream, sal_uInt16 fileVersion) +{ + AutoFormatVersions::WriteBlockA(rStream, fileVersion); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriterSpecificAutoFormatBlock block(rStream); + + rStream.WriteUInt16(legacy::SvxFrameDirection::GetVersion(fileVersion)); + rStream.WriteUInt16(legacy::SwFormatVert::GetVersion(fileVersion)); + } + + AutoFormatVersions::WriteBlockB(rStream, fileVersion); +} + + + +SwBoxAutoFormat::SwBoxAutoFormat() +: AutoFormatBase(), + m_aTextOrientation(std::make_unique<SvxFrameDirectionItem>(SvxFrameDirection::Environment, RES_FRAMEDIR)), + m_aVerticalAlignment(std::make_unique<SwFormatVertOrient>(0, css::text::VertOrientation::NONE, css::text::RelOrientation::FRAME)), + m_sNumFormatString(), + m_eSysLanguage(::GetAppLanguage()), + m_eNumFormatLanguage(::GetAppLanguage()), + m_wXObject() +{ + // need to set default instances for base class AutoFormatBase here + // due to resource defines (e.g. RES_CHRATR_FONT) which are not available + // in svx and different in the different usages of derivations + m_aFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_FONT ) ); + m_aHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_FONTSIZE ); + m_aWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_WEIGHT ); + m_aPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_POSTURE ); + m_aCJKFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_CJK_FONT ) ); + m_aCJKHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_CJK_FONTSIZE ); + m_aCJKWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_CJK_WEIGHT ); + m_aCJKPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_CJK_POSTURE ); + m_aCTLFont = std::make_unique<SvxFontItem>(*GetDfltAttr( RES_CHRATR_CTL_FONT ) ); + m_aCTLHeight = std::make_unique<SvxFontHeightItem>(240, 100, RES_CHRATR_CTL_FONTSIZE ); + m_aCTLWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, RES_CHRATR_CTL_WEIGHT ); + m_aCTLPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, RES_CHRATR_CTL_POSTURE ); + m_aUnderline = std::make_unique<SvxUnderlineItem>(LINESTYLE_NONE, RES_CHRATR_UNDERLINE ); + m_aOverline = std::make_unique<SvxOverlineItem>(LINESTYLE_NONE, RES_CHRATR_OVERLINE ); + m_aCrossedOut = std::make_unique<SvxCrossedOutItem>(STRIKEOUT_NONE, RES_CHRATR_CROSSEDOUT ); + m_aContour = std::make_unique<SvxContourItem>(false, RES_CHRATR_CONTOUR ); + m_aShadowed = std::make_unique<SvxShadowedItem>(false, RES_CHRATR_SHADOWED ); + m_aColor = std::make_unique<SvxColorItem>(RES_CHRATR_COLOR ); + m_aBox = std::make_unique<SvxBoxItem>(RES_BOX ); + m_aTLBR = std::make_unique<SvxLineItem>(0 ); + m_aBLTR = std::make_unique<SvxLineItem>(0 ); + m_aBackground = std::make_unique<SvxBrushItem>(RES_BACKGROUND ); + m_aAdjust = std::make_unique<SvxAdjustItem>(SvxAdjust::Left, RES_PARATR_ADJUST ); + m_aHorJustify = std::make_unique<SvxHorJustifyItem>(SvxCellHorJustify::Standard, 0); + m_aVerJustify = std::make_unique<SvxVerJustifyItem>(SvxCellVerJustify::Standard, 0); + m_aStacked = std::make_unique<SfxBoolItem>(0 ); + m_aMargin = std::make_unique<SvxMarginItem>(0 ); + m_aLinebreak = std::make_unique<SfxBoolItem>(0 ); + m_aRotateAngle = std::make_unique<SfxInt32Item>(0 ); + m_aRotateMode = std::make_unique<SvxRotateModeItem>(SVX_ROTATE_MODE_STANDARD, 0 ); + +// FIXME - add attribute IDs for the diagonal line items +// aTLBR( RES_... ), +// aBLTR( RES_... ), + m_aBox->SetAllDistances(55); +} + +SwBoxAutoFormat::SwBoxAutoFormat( const SwBoxAutoFormat& rNew ) +: AutoFormatBase(rNew), + m_aTextOrientation(rNew.m_aTextOrientation->Clone()), + m_aVerticalAlignment(rNew.m_aVerticalAlignment->Clone()), + m_sNumFormatString( rNew.m_sNumFormatString ), + m_eSysLanguage( rNew.m_eSysLanguage ), + m_eNumFormatLanguage( rNew.m_eNumFormatLanguage ), + m_wXObject() +{ +} + +SwBoxAutoFormat::~SwBoxAutoFormat() +{ +} + +SwBoxAutoFormat& SwBoxAutoFormat::operator=(const SwBoxAutoFormat& rRef) +{ + // check self-assignment + if(this == &rRef) + { + return *this; + } + + // call baseclass implementation + AutoFormatBase::operator=(rRef); + + // copy local members - this will use ::Clone() on all involved Items + SetTextOrientation(rRef.GetTextOrientation()); + SetVerticalAlignment(rRef.GetVerticalAlignment()); + SetNumFormatString(rRef.GetNumFormatString()); + SetSysLanguage(rRef.GetSysLanguage()); + SetNumFormatLanguage(rRef.GetNumFormatLanguage()); + + // m_wXObject used to not be copied before 1e2682235cded9a7cd90e55f0bfc60a1285e9a46 + // "WIP: Further preparations for deeper Item changes" by this operator, so do not do it now, too + // rRef.SetXObject(GetXObject()); + + return *this; +} + +bool SwBoxAutoFormat::operator==(const SwBoxAutoFormat& rRight) const +{ + return GetBackground().GetColor() == rRight.GetBackground().GetColor(); +} + +bool SwBoxAutoFormat::Load( SvStream& rStream, const SwAfVersions& rVersions, sal_uInt16 nVer ) +{ + LoadBlockA( rStream, rVersions, nVer ); + + if (nVer >= AUTOFORMAT_DATA_ID_31005) + { + sal_Int64 const nSize(WriterSpecificBlockExists(rStream)); + if (0 < nSize && nSize < std::numeric_limits<sal_uInt16>::max()) + { + legacy::SvxFrameDirection::Create(*m_aTextOrientation, rStream, rVersions.m_nTextOrientationVersion); + // HORRIBLE HACK to read both 32-bit and 64-bit "long": abuse nSize + legacy::SwFormatVert::Create(*m_aVerticalAlignment, rStream, /*rVersions.m_nVerticalAlignmentVersion*/ nSize); + } + } + + LoadBlockB( rStream, rVersions, nVer ); + + if( 0 == rVersions.nNumFormatVersion ) + { + sal_uInt16 eSys, eLge; + // --- from 680/dr25 on: store strings as UTF-8 + rtl_TextEncoding eCharSet = (nVer >= AUTOFORMAT_ID_680DR25) ? RTL_TEXTENCODING_UTF8 : rStream.GetStreamCharSet(); + m_sNumFormatString = rStream.ReadUniOrByteString( eCharSet ); + rStream.ReadUInt16( eSys ).ReadUInt16( eLge ); + m_eSysLanguage = LanguageType(eSys); + m_eNumFormatLanguage = LanguageType(eLge); + if ( m_eSysLanguage == LANGUAGE_SYSTEM ) // from old versions (Calc) + m_eSysLanguage = ::GetAppLanguage(); + } + + return ERRCODE_NONE == rStream.GetError(); +} + +bool SwBoxAutoFormat::Save( SvStream& rStream, sal_uInt16 fileVersion ) const +{ + SaveBlockA( rStream, fileVersion ); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriterSpecificAutoFormatBlock block(rStream); + + legacy::SvxFrameDirection::Store(*m_aTextOrientation, rStream, legacy::SvxFrameDirection::GetVersion(fileVersion)); + legacy::SwFormatVert::Store(*m_aVerticalAlignment, rStream, legacy::SwFormatVert::GetVersion(fileVersion)); + } + + SaveBlockB( rStream, fileVersion ); + + // --- from 680/dr25 on: store strings as UTF-8 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rStream, m_sNumFormatString, + RTL_TEXTENCODING_UTF8); + rStream.WriteUInt16( static_cast<sal_uInt16>(m_eSysLanguage) ).WriteUInt16( static_cast<sal_uInt16>(m_eNumFormatLanguage) ); + + return ERRCODE_NONE == rStream.GetError(); +} + +SwTableAutoFormat::SwTableAutoFormat( const OUString& rName ) + : m_aName( rName ) + , m_nStrResId( USHRT_MAX ) + , m_aBreak(std::make_shared<SvxFormatBreakItem>(SvxBreak::NONE, RES_BREAK)) + , m_aKeepWithNextPara(std::make_shared<SvxFormatKeepItem>(false, RES_KEEP)) + , m_aRepeatHeading( 0 ) + , m_bLayoutSplit( true ) + , m_bRowSplit( true ) + , m_bCollapsingBorders(true) + , m_aShadow(std::make_shared<SvxShadowItem>(RES_SHADOW)) + , m_bHidden( false ) + , m_bUserDefined( true ) +{ + m_bInclFont = true; + m_bInclJustify = true; + m_bInclFrame = true; + m_bInclBackground = true; + m_bInclValueFormat = true; + m_bInclWidthHeight = true; +} + +SwTableAutoFormat::SwTableAutoFormat( const SwTableAutoFormat& rNew ) + : m_aBreak() + , m_aKeepWithNextPara() + , m_aShadow(std::make_shared<SvxShadowItem>(RES_SHADOW)) +{ + for(SwBoxAutoFormat* & rp : m_aBoxAutoFormat) + rp = nullptr; + *this = rNew; +} + +SwTableAutoFormat& SwTableAutoFormat::operator=( const SwTableAutoFormat& rNew ) +{ + if (&rNew == this) + return *this; + + for( sal_uInt8 n = 0; n < 16; ++n ) + { + if( m_aBoxAutoFormat[ n ] ) + delete m_aBoxAutoFormat[ n ]; + + SwBoxAutoFormat* pFormat = rNew.m_aBoxAutoFormat[ n ]; + if( pFormat ) // if is set -> copy + m_aBoxAutoFormat[ n ] = new SwBoxAutoFormat( *pFormat ); + else // else default + m_aBoxAutoFormat[ n ] = nullptr; + } + + m_aName = rNew.m_aName; + m_nStrResId = rNew.m_nStrResId; + m_bInclFont = rNew.m_bInclFont; + m_bInclJustify = rNew.m_bInclJustify; + m_bInclFrame = rNew.m_bInclFrame; + m_bInclBackground = rNew.m_bInclBackground; + m_bInclValueFormat = rNew.m_bInclValueFormat; + m_bInclWidthHeight = rNew.m_bInclWidthHeight; + + m_aBreak.reset(rNew.m_aBreak->Clone()); + m_aPageDesc = rNew.m_aPageDesc; + m_aKeepWithNextPara.reset(rNew.m_aKeepWithNextPara->Clone()); + m_aRepeatHeading = rNew.m_aRepeatHeading; + m_bLayoutSplit = rNew.m_bLayoutSplit; + m_bRowSplit = rNew.m_bRowSplit; + m_bCollapsingBorders = rNew.m_bCollapsingBorders; + m_aShadow.reset(rNew.m_aShadow->Clone()); + m_bHidden = rNew.m_bHidden; + m_bUserDefined = rNew.m_bUserDefined; + + return *this; +} + +SwTableAutoFormat::~SwTableAutoFormat() +{ + SwBoxAutoFormat** ppFormat = m_aBoxAutoFormat; + for( sal_uInt8 n = 0; n < 16; ++n, ++ppFormat ) + if( *ppFormat ) + delete *ppFormat; +} + +void SwTableAutoFormat::SetBoxFormat( const SwBoxAutoFormat& rNew, sal_uInt8 nPos ) +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( pFormat ) // if is set -> copy + *m_aBoxAutoFormat[ nPos ] = rNew; + else // else set anew + m_aBoxAutoFormat[ nPos ] = new SwBoxAutoFormat( rNew ); +} + +const SwBoxAutoFormat& SwTableAutoFormat::GetBoxFormat( sal_uInt8 nPos ) const +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( pFormat ) // if is set -> copy + return *pFormat; + else // else return the default + { + // If it doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat; + return *pDfltBoxAutoFormat; + } +} + +SwBoxAutoFormat& SwTableAutoFormat::GetBoxFormat( sal_uInt8 nPos ) +{ + SAL_WARN_IF(!(nPos < 16), "sw.core", "GetBoxFormat wrong area"); + + SwBoxAutoFormat** pFormat = &m_aBoxAutoFormat[ nPos ]; + if( !*pFormat ) + { + // If default doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat(); + *pFormat = new SwBoxAutoFormat(*pDfltBoxAutoFormat); + } + return **pFormat; +} + +const SwBoxAutoFormat& SwTableAutoFormat::GetDefaultBoxFormat() +{ + if(!pDfltBoxAutoFormat) + pDfltBoxAutoFormat = new SwBoxAutoFormat(); + + return *pDfltBoxAutoFormat; +} + +void SwTableAutoFormat::UpdateFromSet( sal_uInt8 nPos, + const SfxItemSet& rSet, + SwTableAutoFormatUpdateFlags eFlags, + SvNumberFormatter const * pNFormatr) +{ + OSL_ENSURE( nPos < 16, "wrong area" ); + + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ nPos ]; + if( !pFormat ) // if is set -> copy + { + pFormat = new SwBoxAutoFormat; + m_aBoxAutoFormat[ nPos ] = pFormat; + } + + if( SwTableAutoFormatUpdateFlags::Char & eFlags ) + { + pFormat->SetFont( rSet.Get( RES_CHRATR_FONT ) ); + pFormat->SetHeight( rSet.Get( RES_CHRATR_FONTSIZE ) ); + pFormat->SetWeight( rSet.Get( RES_CHRATR_WEIGHT ) ); + pFormat->SetPosture( rSet.Get( RES_CHRATR_POSTURE ) ); + pFormat->SetCJKFont( rSet.Get( RES_CHRATR_CJK_FONT ) ); + pFormat->SetCJKHeight( rSet.Get( RES_CHRATR_CJK_FONTSIZE ) ); + pFormat->SetCJKWeight( rSet.Get( RES_CHRATR_CJK_WEIGHT ) ); + pFormat->SetCJKPosture( rSet.Get( RES_CHRATR_CJK_POSTURE ) ); + pFormat->SetCTLFont( rSet.Get( RES_CHRATR_CTL_FONT ) ); + pFormat->SetCTLHeight( rSet.Get( RES_CHRATR_CTL_FONTSIZE ) ); + pFormat->SetCTLWeight( rSet.Get( RES_CHRATR_CTL_WEIGHT ) ); + pFormat->SetCTLPosture( rSet.Get( RES_CHRATR_CTL_POSTURE ) ); + pFormat->SetUnderline( rSet.Get( RES_CHRATR_UNDERLINE ) ); + pFormat->SetOverline( rSet.Get( RES_CHRATR_OVERLINE ) ); + pFormat->SetCrossedOut( rSet.Get( RES_CHRATR_CROSSEDOUT ) ); + pFormat->SetContour( rSet.Get( RES_CHRATR_CONTOUR ) ); + pFormat->SetShadowed( rSet.Get( RES_CHRATR_SHADOWED ) ); + pFormat->SetColor( rSet.Get( RES_CHRATR_COLOR ) ); + pFormat->SetAdjust( rSet.Get( RES_PARATR_ADJUST ) ); + } + if( SwTableAutoFormatUpdateFlags::Box & eFlags ) + { + pFormat->SetBox( rSet.Get( RES_BOX ) ); +// FIXME - add attribute IDs for the diagonal line items +// pFormat->SetTLBR( (SvxLineItem&)rSet.Get( RES_... ) ); +// pFormat->SetBLTR( (SvxLineItem&)rSet.Get( RES_... ) ); + pFormat->SetBackground( rSet.Get( RES_BACKGROUND ) ); + pFormat->SetTextOrientation(rSet.Get(RES_FRAMEDIR)); + pFormat->SetVerticalAlignment(rSet.Get(RES_VERT_ORIENT)); + + const SwTableBoxNumFormat* pNumFormatItem; + const SvNumberformat* pNumFormat = nullptr; + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMAT, true, + reinterpret_cast<const SfxPoolItem**>(&pNumFormatItem) ) && pNFormatr && + nullptr != (pNumFormat = pNFormatr->GetEntry( pNumFormatItem->GetValue() )) ) + pFormat->SetValueFormat( pNumFormat->GetFormatstring(), + pNumFormat->GetLanguage(), + ::GetAppLanguage()); + else + { + // default + pFormat->SetValueFormat( OUString(), LANGUAGE_SYSTEM, + ::GetAppLanguage() ); + } + } + + // we cannot handle the rest, that's specific to StarCalc +} + +void SwTableAutoFormat::UpdateToSet(const sal_uInt8 nPos, const bool bSingleRowTable, const bool bSingleColTable, SfxItemSet& rSet, + SwTableAutoFormatUpdateFlags eFlags, SvNumberFormatter* pNFormatr) const +{ + const SwBoxAutoFormat& rChg = GetBoxFormat( nPos ); + + if( SwTableAutoFormatUpdateFlags::Char & eFlags ) + { + if( IsFont() ) + { + rSet.Put( rChg.GetFont() ); + rSet.Put( rChg.GetHeight() ); + rSet.Put( rChg.GetWeight() ); + rSet.Put( rChg.GetPosture() ); + // do not insert empty CJK font + const SvxFontItem& rCJKFont = rChg.GetCJKFont(); + if (!rCJKFont.GetStyleName().isEmpty()) + { + rSet.Put( rChg.GetCJKFont() ); + rSet.Put( rChg.GetCJKHeight() ); + rSet.Put( rChg.GetCJKWeight() ); + rSet.Put( rChg.GetCJKPosture() ); + } + else + { + rSet.Put( rChg.GetHeight().CloneSetWhich(RES_CHRATR_CJK_FONTSIZE) ); + rSet.Put( rChg.GetWeight().CloneSetWhich(RES_CHRATR_CJK_WEIGHT) ); + rSet.Put( rChg.GetPosture().CloneSetWhich(RES_CHRATR_CJK_POSTURE) ); + } + // do not insert empty CTL font + const SvxFontItem& rCTLFont = rChg.GetCTLFont(); + if (!rCTLFont.GetStyleName().isEmpty()) + { + rSet.Put( rChg.GetCTLFont() ); + rSet.Put( rChg.GetCTLHeight() ); + rSet.Put( rChg.GetCTLWeight() ); + rSet.Put( rChg.GetCTLPosture() ); + } + else + { + rSet.Put( rChg.GetHeight().CloneSetWhich(RES_CHRATR_CTL_FONTSIZE) ); + rSet.Put( rChg.GetWeight().CloneSetWhich(RES_CHRATR_CTL_WEIGHT) ); + rSet.Put( rChg.GetPosture().CloneSetWhich(RES_CHRATR_CTL_POSTURE) ); + } + rSet.Put( rChg.GetUnderline() ); + rSet.Put( rChg.GetOverline() ); + rSet.Put( rChg.GetCrossedOut() ); + rSet.Put( rChg.GetContour() ); + rSet.Put( rChg.GetShadowed() ); + rSet.Put( rChg.GetColor() ); + } + if( IsJustify() ) + rSet.Put( rChg.GetAdjust() ); + } + + if( SwTableAutoFormatUpdateFlags::Box & eFlags ) + { + if( IsFrame() ) + { + SvxBoxItem aAutoFormatBox = rChg.GetBox(); + + // No format box is adequate to specify the borders of single column/row tables, so combine first/last. + if ( bSingleRowTable || bSingleColTable ) + { + sal_uInt8 nSingleRowOrColumnId = 15; //LAST_ROW_END_COLUMN + if ( !bSingleRowTable ) + nSingleRowOrColumnId = nPos + 3; //LAST COLUMN (3, 7, 11, 15) + else if ( !bSingleColTable ) + nSingleRowOrColumnId = nPos + 12; //LAST ROW (12, 13, 14, 15) + + assert( nSingleRowOrColumnId < 16 ); + const SvxBoxItem aLastAutoFormatBox( GetBoxFormat(nSingleRowOrColumnId).GetBox() ); + if ( bSingleRowTable ) + aAutoFormatBox.SetLine( aLastAutoFormatBox.GetLine(SvxBoxItemLine::BOTTOM), SvxBoxItemLine::BOTTOM ); + if ( bSingleColTable ) + aAutoFormatBox.SetLine( aLastAutoFormatBox.GetLine(SvxBoxItemLine::RIGHT), SvxBoxItemLine::RIGHT ); + } + + rSet.Put( aAutoFormatBox ); +// FIXME - uncomment the lines to put the diagonal line items +// rSet.Put( rChg.GetTLBR() ); +// rSet.Put( rChg.GetBLTR() ); + } + if( IsBackground() ) + rSet.Put( rChg.GetBackground() ); + + rSet.Put(rChg.GetTextOrientation()); + + // Do not put a VertAlign when it has default value. + // It prevents the export of default value by automatic cell-styles export. + if (rChg.GetVerticalAlignment().GetVertOrient() != GetDefaultBoxFormat().GetVerticalAlignment().GetVertOrient()) + rSet.Put(rChg.GetVerticalAlignment()); + + if( IsValueFormat() && pNFormatr ) + { + OUString sFormat; + LanguageType eLng, eSys; + rChg.GetValueFormat( sFormat, eLng, eSys ); + if( !sFormat.isEmpty() ) + { + SvNumFormatType nType; + bool bNew; + sal_Int32 nCheckPos; + sal_uInt32 nKey = pNFormatr->GetIndexPuttingAndConverting( sFormat, eLng, + eSys, nType, bNew, nCheckPos); + rSet.Put( SwTableBoxNumFormat( nKey )); + } + else + rSet.ClearItem( RES_BOXATR_FORMAT ); + } + } + + // we cannot handle the rest, that's specific to StarCalc +} + +void SwTableAutoFormat::RestoreTableProperties(SwTable &table) const +{ + SwTableFormat* pFormat = table.GetFrameFormat(); + if (!pFormat) + return; + + SwDoc *pDoc = pFormat->GetDoc(); + if (!pDoc) + return; + + SfxItemSet rSet(pDoc->GetAttrPool(), aTableSetRange); + + if ( m_aBreak->GetBreak() != SvxBreak::NONE ) + rSet.Put(*m_aBreak); + rSet.Put(m_aPageDesc); + rSet.Put(SwFormatLayoutSplit(m_bLayoutSplit)); + rSet.Put(SfxBoolItem(RES_COLLAPSING_BORDERS, m_bCollapsingBorders)); + if ( m_aKeepWithNextPara->GetValue() ) + rSet.Put(*m_aKeepWithNextPara); + rSet.Put(*m_aShadow); + + pFormat->SetFormatAttr(rSet); + + SwEditShell *pShell = pDoc->GetEditShell(); + pDoc->SetRowSplit(*pShell->getShellCursor(false), SwFormatRowSplit(m_bRowSplit)); + + table.SetRowsToRepeat(m_aRepeatHeading); +} + +void SwTableAutoFormat::StoreTableProperties(const SwTable &table) +{ + SwTableFormat* pFormat = table.GetFrameFormat(); + if (!pFormat) + return; + + SwDoc *pDoc = pFormat->GetDoc(); + if (!pDoc) + return; + + SwEditShell *pShell = pDoc->GetEditShell(); + std::unique_ptr<SwFormatRowSplit> pRowSplit = SwDoc::GetRowSplit(*pShell->getShellCursor(false)); + m_bRowSplit = pRowSplit && pRowSplit->GetValue(); + pRowSplit.reset(); + + const SfxItemSet &rSet = pFormat->GetAttrSet(); + + m_aBreak.reset(rSet.Get(RES_BREAK).Clone()); + m_aPageDesc = rSet.Get(RES_PAGEDESC); + const SwFormatLayoutSplit &layoutSplit = rSet.Get(RES_LAYOUT_SPLIT); + m_bLayoutSplit = layoutSplit.GetValue(); + m_bCollapsingBorders = rSet.Get(RES_COLLAPSING_BORDERS).GetValue(); + + m_aKeepWithNextPara.reset(rSet.Get(RES_KEEP).Clone()); + m_aRepeatHeading = table.GetRowsToRepeat(); + m_aShadow.reset(rSet.Get(RES_SHADOW).Clone()); +} + +bool SwTableAutoFormat::FirstRowEndColumnIsRow() +{ + return GetBoxFormat(3) == GetBoxFormat(2); +} +bool SwTableAutoFormat::FirstRowStartColumnIsRow() +{ + return GetBoxFormat(0) == GetBoxFormat(1); +} +bool SwTableAutoFormat::LastRowEndColumnIsRow() +{ + return GetBoxFormat(14) == GetBoxFormat(15); +} +bool SwTableAutoFormat::LastRowStartColumnIsRow() +{ + return GetBoxFormat(12) == GetBoxFormat(13); +} + +bool SwTableAutoFormat::Load( SvStream& rStream, const SwAfVersions& rVersions ) +{ + sal_uInt16 nVal = 0; + rStream.ReadUInt16( nVal ); + bool bRet = ERRCODE_NONE == rStream.GetError(); + + if( bRet && (nVal == AUTOFORMAT_DATA_ID_X || + (AUTOFORMAT_DATA_ID_504 <= nVal && nVal <= AUTOFORMAT_DATA_ID)) ) + { + bool b; + // --- from 680/dr25 on: store strings as UTF-8 + rtl_TextEncoding eCharSet = (nVal >= AUTOFORMAT_ID_680DR25) ? RTL_TEXTENCODING_UTF8 : rStream.GetStreamCharSet(); + m_aName = rStream.ReadUniOrByteString( eCharSet ); + if( AUTOFORMAT_DATA_ID_552 <= nVal ) + { + rStream.ReadUInt16( m_nStrResId ); + // start from 3d because default is added via constructor + if( m_nStrResId < RES_POOLTABLESTYLE_END - RES_POOLTABLESTYLE_3D ) + { + m_aName = SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_3D + m_nStrResId, m_aName); + } + else + m_nStrResId = USHRT_MAX; + } + rStream.ReadCharAsBool( b ); m_bInclFont = b; + rStream.ReadCharAsBool( b ); m_bInclJustify = b; + rStream.ReadCharAsBool( b ); m_bInclFrame = b; + rStream.ReadCharAsBool( b ); m_bInclBackground = b; + rStream.ReadCharAsBool( b ); m_bInclValueFormat = b; + rStream.ReadCharAsBool( b ); m_bInclWidthHeight = b; + + if (nVal >= AUTOFORMAT_DATA_ID_31005 && WriterSpecificBlockExists(rStream)) + { + legacy::SvxFormatBreak::Create(*m_aBreak, rStream, AUTOFORMAT_FILE_VERSION); +//unimplemented READ(m_aPageDesc, SwFormatPageDesc, AUTOFORMAT_FILE_VERSION); + legacy::SvxFormatKeep::Create(*m_aKeepWithNextPara, rStream, AUTOFORMAT_FILE_VERSION); + + rStream.ReadUInt16( m_aRepeatHeading ).ReadCharAsBool( m_bLayoutSplit ).ReadCharAsBool( m_bRowSplit ).ReadCharAsBool( m_bCollapsingBorders ); + + legacy::SvxShadow::Create(*m_aShadow, rStream, AUTOFORMAT_FILE_VERSION); + } + + bRet = ERRCODE_NONE== rStream.GetError(); + + for( sal_uInt8 i = 0; bRet && i < 16; ++i ) + { + SwBoxAutoFormat* pFormat = new SwBoxAutoFormat; + bRet = pFormat->Load( rStream, rVersions, nVal ); + if( bRet ) + m_aBoxAutoFormat[ i ] = pFormat; + else + { + delete pFormat; + break; + } + } + } + m_bUserDefined = false; + return bRet; +} + +bool SwTableAutoFormat::Save( SvStream& rStream, sal_uInt16 fileVersion ) const +{ + rStream.WriteUInt16( AUTOFORMAT_DATA_ID ); + // --- from 680/dr25 on: store strings as UTF-8 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rStream, m_aName, + RTL_TEXTENCODING_UTF8 ); + rStream.WriteUInt16( m_nStrResId ); + rStream.WriteBool( m_bInclFont ); + rStream.WriteBool( m_bInclJustify ); + rStream.WriteBool( m_bInclFrame ); + rStream.WriteBool( m_bInclBackground ); + rStream.WriteBool( m_bInclValueFormat ); + rStream.WriteBool( m_bInclWidthHeight ); + + { + WriterSpecificAutoFormatBlock block(rStream); + + legacy::SvxFormatBreak::Store(*m_aBreak, rStream, legacy::SvxFormatBreak::GetVersion(fileVersion)); +//unimplemented m_aPageDesc.Store(rStream, m_aPageDesc.GetVersion(fileVersion)); + legacy::SvxFormatKeep::Store(*m_aKeepWithNextPara, rStream, legacy::SvxFormatKeep::GetVersion(fileVersion)); + rStream.WriteUInt16( m_aRepeatHeading ).WriteBool( m_bLayoutSplit ).WriteBool( m_bRowSplit ).WriteBool( m_bCollapsingBorders ); + legacy::SvxShadow::Store(*m_aShadow, rStream, legacy::SvxShadow::GetVersion(fileVersion)); + } + + bool bRet = ERRCODE_NONE == rStream.GetError(); + + for( int i = 0; bRet && i < 16; ++i ) + { + SwBoxAutoFormat* pFormat = m_aBoxAutoFormat[ i ]; + if( !pFormat ) // if not set -> write default + { + // If it doesn't exist yet: + if( !pDfltBoxAutoFormat ) + pDfltBoxAutoFormat = new SwBoxAutoFormat; + pFormat = pDfltBoxAutoFormat; + } + bRet = pFormat->Save( rStream, fileVersion ); + } + return bRet; +} + +OUString SwTableAutoFormat::GetTableTemplateCellSubName(const SwBoxAutoFormat& rBoxFormat) const +{ + sal_Int32 nIndex = 0; + for (; nIndex < 16; ++nIndex) + if (m_aBoxAutoFormat[nIndex] == &rBoxFormat) break; + + // box format doesn't belong to this table format + if (16 <= nIndex) + return OUString(); + + const std::vector<sal_Int32> aTableTemplateMap = GetTableTemplateMap(); + for (size_t i=0; i < aTableTemplateMap.size(); ++i) + { + if (aTableTemplateMap[i] == nIndex) + return "." + OUString::number(i + 1); + } + + // box format doesn't belong to a table template + return OUString(); +} + +/* + * Mapping schema + * 0 1 2 3 4 5 + * +-----------------------------------------------------------------------+ + * 0 | FRSC | FR | FREC | | | FRENC | + * +-----------------------------------------------------------------------+ + * 1 | FC | ER | EC | | | LC | + * +-----------------------------------------------------------------------+ + * 2 | OR | OC | BODY | | | BCKG | + * +-----------------------------------------------------------------------+ + * 3 | | | | | | | + * +-----------------------------------------------------------------------+ + * 4 | | | | | | | + * +-----------------------------------------------------------------------+ + * 5 | LRSC | LR | LREC | | | LRENC | + * +-----------+-----------+-----------+-----------+-----------+-----------+ + * ODD = 1, 3, 5, ... + * EVEN = 2, 4, 6, ... + */ +const std::vector<sal_Int32> & SwTableAutoFormat::GetTableTemplateMap() +{ + static std::vector<sal_Int32> const aTableTemplateMap + { + 1 , // FIRST_ROW // FR + 13, // LAST_ROW // LR + 4 , // FIRST_COLUMN // FC + 7 , // LAST_COLUMN // LC + 5 , // EVEN_ROWS // ER + 8 , // ODD_ROWS // OR + 6 , // EVEN_COLUMNS // EC + 9 , // ODD_COLUMNS // OC + 10, // BODY + 11, // BACKGROUND // BCKG + 0 , // FIRST_ROW_START_COLUMN // FRSC + 3 , // FIRST_ROW_END_COLUMN // FRENC + 12, // LAST_ROW_START_COLUMN // LRSC + 15, // LAST_ROW_END_COLUMN // LRENC + 2 , // FIRST_ROW_EVEN_COLUMN // FREC + 14, // LAST_ROW_EVEN_COLUMN // LREC + }; + return aTableTemplateMap; +} + +sal_uInt8 SwTableAutoFormat::CountPos(sal_uInt32 nCol, sal_uInt32 nCols, sal_uInt32 nRow, + sal_uInt32 nRows) +{ + sal_uInt8 nRet = static_cast<sal_uInt8>( + !nRow ? 0 : ((nRow + 1 == nRows) ? 12 : (4 * (1 + ((nRow - 1) & 1))))); + nRet = nRet + + static_cast<sal_uInt8>(!nCol ? 0 : (nCol + 1 == nCols ? 3 : (1 + ((nCol - 1) & 1)))); + return nRet; +} + +struct SwTableAutoFormatTable::Impl +{ + std::vector<std::unique_ptr<SwTableAutoFormat>> m_AutoFormats; +}; + +size_t SwTableAutoFormatTable::size() const +{ + return m_pImpl->m_AutoFormats.size(); +} + +SwTableAutoFormat const& SwTableAutoFormatTable::operator[](size_t const i) const +{ + return *m_pImpl->m_AutoFormats[i]; +} +SwTableAutoFormat & SwTableAutoFormatTable::operator[](size_t const i) +{ + return *m_pImpl->m_AutoFormats[i]; +} + +void SwTableAutoFormatTable::AddAutoFormat(const SwTableAutoFormat& rTableStyle) +{ + // don't insert when we already have style of this name + if (FindAutoFormat(rTableStyle.GetName())) + return; + + InsertAutoFormat(size(), std::make_unique<SwTableAutoFormat>(rTableStyle)); +} + +void SwTableAutoFormatTable::InsertAutoFormat(size_t const i, std::unique_ptr<SwTableAutoFormat> pFormat) +{ + m_pImpl->m_AutoFormats.insert(m_pImpl->m_AutoFormats.begin() + i, std::move(pFormat)); +} + +void SwTableAutoFormatTable::EraseAutoFormat(size_t const i) +{ + m_pImpl->m_AutoFormats.erase(m_pImpl->m_AutoFormats.begin() + i); +} + +void SwTableAutoFormatTable::EraseAutoFormat(const OUString& rName) +{ + auto iter = std::find_if(m_pImpl->m_AutoFormats.begin(), m_pImpl->m_AutoFormats.end(), + [&rName](const std::unique_ptr<SwTableAutoFormat>& rpFormat) { return rpFormat->GetName() == rName; }); + if (iter != m_pImpl->m_AutoFormats.end()) + { + m_pImpl->m_AutoFormats.erase(iter); + return; + } + SAL_INFO("sw.core", "SwTableAutoFormatTable::EraseAutoFormat, SwTableAutoFormat with given name not found"); +} + +std::unique_ptr<SwTableAutoFormat> SwTableAutoFormatTable::ReleaseAutoFormat(size_t const i) +{ + auto const iter(m_pImpl->m_AutoFormats.begin() + i); + std::unique_ptr<SwTableAutoFormat> pRet(std::move(*iter)); + m_pImpl->m_AutoFormats.erase(iter); + return pRet; +} + +std::unique_ptr<SwTableAutoFormat> SwTableAutoFormatTable::ReleaseAutoFormat(const OUString& rName) +{ + std::unique_ptr<SwTableAutoFormat> pRet; + auto iter = std::find_if(m_pImpl->m_AutoFormats.begin(), m_pImpl->m_AutoFormats.end(), + [&rName](const std::unique_ptr<SwTableAutoFormat>& rpFormat) { return rpFormat->GetName() == rName; }); + if (iter != m_pImpl->m_AutoFormats.end()) + { + pRet = std::move(*iter); + m_pImpl->m_AutoFormats.erase(iter); + } + return pRet; +} + +SwTableAutoFormat* SwTableAutoFormatTable::FindAutoFormat(const OUString& rName) const +{ + for (const auto &rFormat : m_pImpl->m_AutoFormats) + { + if (rFormat->GetName() == rName) + return rFormat.get(); + } + + return nullptr; +} + +SwTableAutoFormatTable::~SwTableAutoFormatTable() +{ +} + +SwTableAutoFormatTable::SwTableAutoFormatTable() + : m_pImpl(new Impl) +{ + std::unique_ptr<SwTableAutoFormat> pNew(new SwTableAutoFormat( + SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_DEFAULT, OUString()))); + + sal_uInt8 i; + + Color aColor( COL_BLACK ); + SvxBoxItem aBox( RES_BOX ); + + aBox.SetAllDistances(55); + SvxBorderLine aLn( &aColor, DEF_LINE_WIDTH_5 ); + aBox.SetLine( &aLn, SvxBoxItemLine::LEFT ); + aBox.SetLine( &aLn, SvxBoxItemLine::BOTTOM ); + + for( i = 0; i <= 15; ++i ) + { + aBox.SetLine( i <= 3 ? &aLn : nullptr, SvxBoxItemLine::TOP ); + aBox.SetLine( (3 == ( i & 3 )) ? &aLn : nullptr, SvxBoxItemLine::RIGHT ); + pNew->GetBoxFormat( i ).SetBox( aBox ); + } + + pNew->SetUserDefined(false); + m_pImpl->m_AutoFormats.push_back(std::move(pNew)); +} + +void SwTableAutoFormatTable::Load() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + OUString sNm(AUTOTABLE_FORMAT_NAME); + SvtPathOptions aOpt; + if( aOpt.SearchFile( sNm )) + { + SfxMedium aStream( sNm, StreamMode::STD_READ ); + Load( *aStream.GetInStream() ); + } +} + +bool SwTableAutoFormatTable::Save() const +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + SvtPathOptions aPathOpt; + const OUString sNm( aPathOpt.GetUserConfigPath() + "/" AUTOTABLE_FORMAT_NAME ); + SfxMedium aStream(sNm, StreamMode::STD_WRITE ); + return Save( *aStream.GetOutStream() ) && aStream.Commit(); +} + +bool SwTableAutoFormatTable::Load( SvStream& rStream ) +{ + bool bRet = ERRCODE_NONE == rStream.GetError(); + if (bRet) + { + // Attention: We need to read a general Header here + sal_uInt16 nVal = 0; + rStream.ReadUInt16( nVal ); + bRet = ERRCODE_NONE == rStream.GetError(); + + if( bRet ) + { + SwAfVersions aVersions; + + // Default version is 5.0, unless we detect an old format ID. + sal_uInt16 nFileVers = SOFFICE_FILEFORMAT_50; + if(nVal < AUTOFORMAT_ID_31005) + nFileVers = SOFFICE_FILEFORMAT_40; + + if( nVal == AUTOFORMAT_ID_358 || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + sal_uInt8 nChrSet, nCnt; + long nPos = rStream.Tell(); + rStream.ReadUChar( nCnt ).ReadUChar( nChrSet ); + if( rStream.Tell() != sal_uLong(nPos + nCnt) ) + { + OSL_ENSURE( false, "The Header contains more or newer Data" ); + rStream.Seek( nPos + nCnt ); + } + rStream.SetStreamCharSet( static_cast<rtl_TextEncoding>(nChrSet) ); + rStream.SetVersion( nFileVers ); + } + + if( nVal == AUTOFORMAT_ID_358 || nVal == AUTOFORMAT_ID_X || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + aVersions.Load( rStream, nVal ); // Item versions + + sal_uInt16 nCount = 0; + rStream.ReadUInt16( nCount ); + + bRet = ERRCODE_NONE== rStream.GetError(); + if (bRet) + { + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize; + if (nCount > nMaxRecords) + { + SAL_WARN("sw.core", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + for (sal_uInt16 i = 0; i < nCount; ++i) + { + std::unique_ptr<SwTableAutoFormat> pNew( + new SwTableAutoFormat( OUString() )); + bRet = pNew->Load( rStream, aVersions ); + if( bRet ) + { + m_pImpl->m_AutoFormats.push_back(std::move(pNew)); + } + else + { + break; + } + } + } + } + else + { + bRet = false; + } + } + } + return bRet; +} + +bool SwTableAutoFormatTable::Save( SvStream& rStream ) const +{ + bool bRet = ERRCODE_NONE == rStream.GetError(); + if (bRet) + { + rStream.SetVersion(AUTOFORMAT_FILE_VERSION); + + // Attention: We need to save a general Header here + rStream.WriteUInt16( AUTOFORMAT_ID ) + .WriteUChar( 2 ) // Character count of the Header including this value + .WriteUChar( GetStoreCharSet( ::osl_getThreadTextEncoding() ) ); + + bRet = ERRCODE_NONE == rStream.GetError(); + if (!bRet) + return false; + + // Write this version number for all attributes + SwAfVersions::Write(rStream, AUTOFORMAT_FILE_VERSION); + + rStream.WriteUInt16( m_pImpl->m_AutoFormats.size() - 1 ); + bRet = ERRCODE_NONE == rStream.GetError(); + + for (size_t i = 1; bRet && i < m_pImpl->m_AutoFormats.size(); ++i) + { + SwTableAutoFormat const& rFormat = *m_pImpl->m_AutoFormats[i]; + bRet = rFormat.Save(rStream, AUTOFORMAT_FILE_VERSION); + } + } + rStream.Flush(); + return bRet; +} + +SwCellStyleTable::SwCellStyleTable() +{ } + +SwCellStyleTable::~SwCellStyleTable() +{ +} + +size_t SwCellStyleTable::size() const +{ + return m_aCellStyles.size(); +} + +void SwCellStyleTable::clear() +{ + m_aCellStyles.clear(); +} + +SwCellStyleDescriptor SwCellStyleTable::operator[](size_t i) const +{ + return SwCellStyleDescriptor(m_aCellStyles[i]); +} + +void SwCellStyleTable::AddBoxFormat(const SwBoxAutoFormat& rBoxFormat, const OUString& sName) +{ + m_aCellStyles.emplace_back(sName, std::make_unique<SwBoxAutoFormat>(rBoxFormat)); +} + +void SwCellStyleTable::RemoveBoxFormat(const OUString& sName) +{ + auto iter = std::find_if(m_aCellStyles.begin(), m_aCellStyles.end(), + [&sName](const std::pair<OUString, std::unique_ptr<SwBoxAutoFormat>>& rStyle) { return rStyle.first == sName; }); + if (iter != m_aCellStyles.end()) + { + m_aCellStyles.erase(iter); + return; + } + SAL_INFO("sw.core", "SwCellStyleTable::RemoveBoxFormat, format with given name doesn't exists"); +} + +OUString SwCellStyleTable::GetBoxFormatName(const SwBoxAutoFormat& rBoxFormat) const +{ + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].second.get() == &rBoxFormat) + return m_aCellStyles[i].first; + } + + // box format not found + return OUString(); +} + +SwBoxAutoFormat* SwCellStyleTable::GetBoxFormat(const OUString& sName) const +{ + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].first == sName) + return m_aCellStyles[i].second.get(); + } + + return nullptr; +} + +void SwCellStyleTable::ChangeBoxFormatName(const OUString& sFromName, const OUString& sToName) +{ + if (!GetBoxFormat(sToName)) + { + SAL_INFO("sw.core", "SwCellStyleTable::ChangeBoxName, box with given name already exists"); + return; + } + for (size_t i=0; i < m_aCellStyles.size(); ++i) + { + if (m_aCellStyles[i].first == sFromName) + { + m_aCellStyles[i].first = sToName; + // changed successfully + return; + } + } + SAL_INFO("sw.core", "SwCellStyleTable::ChangeBoxName, box with given name not found"); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblcpy.cxx b/sw/source/core/doc/tblcpy.cxx new file mode 100644 index 000000000..164a33ae5 --- /dev/null +++ b/sw/source/core/doc/tblcpy.cxx @@ -0,0 +1,1042 @@ +/* -*- 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 <svl/zforlist.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <tblsel.hxx> +#include <poolfmt.hxx> +#include <cellatr.hxx> +#include <mvsave.hxx> +#include <docary.hxx> +#include <fmtanchr.hxx> +#include <hints.hxx> +#include <UndoTable.hxx> +#include <fmtfsize.hxx> +#include <frameformats.hxx> +#include <deque> +#include <memory> +#include <numeric> + +static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, + SwTable& rDstTable, SwTableBox* pDstBox, + bool bDelContent, SwUndoTableCpyTable* pUndo ); + +// The following type will be used by table copy functions to describe +// the structure of tables (or parts of tables). +// It's for new table model only. + +namespace +{ + struct BoxSpanInfo + { + SwTableBox* mpBox; + SwTableBox* mpCopy; + sal_uInt16 mnColSpan; + bool mbSelected; + }; + + typedef std::vector< BoxSpanInfo > BoxStructure; + typedef std::vector< BoxStructure > LineStructure; + typedef std::deque< sal_uLong > ColumnStructure; + + struct SubBox + { + SwTableBox *mpBox; + bool mbCovered; + }; + + typedef std::vector< SubBox > SubLine; + typedef std::vector< SubLine > SubTable; + + class TableStructure + { + public: + LineStructure maLines; + ColumnStructure maCols; + sal_uInt16 mnStartCol; + sal_uInt16 mnAddLine; + void addLine( sal_uInt16 &rLine, const SwTableBoxes&, const SwSelBoxes*, + bool bNewModel ); + void addBox( sal_uInt16 nLine, const SwSelBoxes*, SwTableBox *pBox, + sal_uLong &rnB, sal_uInt16 &rnC, ColumnStructure::iterator& rpCl, + BoxStructure::iterator& rpSel, bool &rbSel, bool bCover ); + void incColSpan( sal_uInt16 nLine, sal_uInt16 nCol ); + explicit TableStructure( const SwTable& rTable ); + TableStructure( const SwTable& rTable, FndBox_ &rFndBox, + const SwSelBoxes& rSelBoxes, + LineStructure::size_type nMinSize ); + LineStructure::size_type getLineCount() const + { return maLines.size(); } + void moreLines( const SwTable& rTable ); + void assignBoxes( const TableStructure &rSource ); + void copyBoxes( const SwTable& rSource, SwTable& rDstTable, + SwUndoTableCpyTable* pUndo ) const; + }; + + SubTable::iterator insertSubLine( SubTable& rSubTable, SwTableLine& rLine, + const SubTable::iterator& pStartLn ); + + SubTable::iterator insertSubBox( SubTable& rSubTable, SwTableBox& rBox, + SubTable::iterator pStartLn, const SubTable::iterator& pEndLn ) + { + if( !rBox.GetTabLines().empty() ) + { + SubTable::size_type nSize = static_cast<SubTable::size_type>(std::distance( pStartLn, pEndLn )); + if( nSize < rBox.GetTabLines().size() ) + { + SubLine aSubLine; + for( const auto& rSubBox : *pStartLn ) + { + SubBox aSub; + aSub.mpBox = rSubBox.mpBox; + aSub.mbCovered = true; + aSubLine.push_back( aSub ); + } + do + { + rSubTable.insert( pEndLn, aSubLine ); + } while( ++nSize < rBox.GetTabLines().size() ); + } + for( auto pLine : rBox.GetTabLines() ) + pStartLn = insertSubLine( rSubTable, *pLine, pStartLn ); + OSL_ENSURE( pStartLn == pEndLn, "Sub line confusion" ); + } + else + { + SubBox aSub; + aSub.mpBox = &rBox; + aSub.mbCovered = false; + while( pStartLn != pEndLn ) + { + pStartLn->push_back( aSub ); + aSub.mbCovered = true; + ++pStartLn; + } + } + return pStartLn; + } + + SubTable::iterator insertSubLine( SubTable& rSubTable, SwTableLine& rLine, + const SubTable::iterator& pStartLn ) + { + SubTable::iterator pMax = pStartLn; + ++pMax; + SubTable::difference_type nMax = 1; + for( auto pBox : rLine.GetTabBoxes() ) + { + SubTable::iterator pTmp = insertSubBox( rSubTable, *pBox, pStartLn, pMax ); + SubTable::difference_type nTmp = std::distance( pStartLn, pTmp ); + if( nTmp > nMax ) + { + pMax = pTmp; + nMax = nTmp; + } + } + return pMax; + } + + TableStructure::TableStructure( const SwTable& rTable ) : + maLines( rTable.GetTabLines().size() ), mnStartCol(USHRT_MAX), + mnAddLine(0) + { + maCols.push_front(0); + sal_uInt16 nCnt = 0; + for( auto pLine : rTable.GetTabLines() ) + addLine( nCnt, pLine->GetTabBoxes(), nullptr, rTable.IsNewModel() ); + } + + TableStructure::TableStructure( const SwTable& rTable, + FndBox_ &rFndBox, const SwSelBoxes& rSelBoxes, + LineStructure::size_type nMinSize ) + : mnStartCol(USHRT_MAX), mnAddLine(0) + { + if( !rFndBox.GetLines().empty() ) + { + bool bNoSelection = rSelBoxes.size() < 2; + FndLines_t &rFndLines = rFndBox.GetLines(); + maCols.push_front(0); + const SwTableLine* pLine = rFndLines.front()->GetLine(); + const sal_uInt16 nStartLn = rTable.GetTabLines().GetPos( pLine ); + SwTableLines::size_type nEndLn = nStartLn; + if( rFndLines.size() > 1 ) + { + pLine = rFndLines.back()->GetLine(); + nEndLn = rTable.GetTabLines().GetPos( pLine ); + } + if( nStartLn < USHRT_MAX && nEndLn < USHRT_MAX ) + { + const SwTableLines &rLines = rTable.GetTabLines(); + if( bNoSelection && nMinSize > nEndLn - nStartLn + 1 ) + { + SwTableLines::size_type nNewEndLn = nStartLn + nMinSize - 1; + if( nNewEndLn >= rLines.size() ) + { + mnAddLine = nNewEndLn - rLines.size() + 1; + nNewEndLn = rLines.size() - 1; + } + while( nEndLn < nNewEndLn ) + { + SwTableLine *pLine2 = rLines[ ++nEndLn ]; + SwTableBox *pTmpBox = pLine2->GetTabBoxes()[0]; + FndLine_ *pInsLine = new FndLine_( pLine2, &rFndBox ); + pInsLine->GetBoxes().insert(pInsLine->GetBoxes().begin(), std::make_unique<FndBox_>(pTmpBox, pInsLine)); + rFndLines.push_back(std::unique_ptr<FndLine_>(pInsLine)); + } + } + maLines.resize( nEndLn - nStartLn + 1 ); + const SwSelBoxes* pSelBoxes = &rSelBoxes; + sal_uInt16 nCnt = 0; + for( SwTableLines::size_type nLine = nStartLn; nLine <= nEndLn; ++nLine ) + { + addLine( nCnt, rLines[nLine]->GetTabBoxes(), + pSelBoxes, rTable.IsNewModel() ); + if( bNoSelection ) + pSelBoxes = nullptr; + } + } + if( bNoSelection && mnStartCol < USHRT_MAX ) + { + sal_uInt16 nIdx = std::min(mnStartCol, static_cast<sal_uInt16>(maLines[0].size())); + mnStartCol = std::accumulate(maLines[0].begin(), maLines[0].begin() + nIdx, sal_uInt16(0), + [](sal_uInt16 sum, const BoxSpanInfo& rInfo) { return sum + rInfo.mnColSpan; }); + } + else + mnStartCol = USHRT_MAX; + } + } + + void TableStructure::addLine( sal_uInt16 &rLine, const SwTableBoxes& rBoxes, + const SwSelBoxes* pSelBoxes, bool bNewModel ) + { + bool bComplex = false; + if( !bNewModel ) + for( SwTableBoxes::size_type nBox = 0; !bComplex && nBox < rBoxes.size(); ++nBox ) + bComplex = !rBoxes[nBox]->GetTabLines().empty(); + if( bComplex ) + { + SubTable aSubTable; + SubLine aSubLine; + aSubTable.push_back( aSubLine ); + SubTable::iterator pStartLn = aSubTable.begin(); + SubTable::iterator pEndLn = aSubTable.end(); + for( auto pBox : rBoxes ) + insertSubBox( aSubTable, *pBox, pStartLn, pEndLn ); + SubTable::size_type nSize = aSubTable.size(); + if( nSize ) + { + maLines.resize( maLines.size() + nSize - 1 ); + while( pStartLn != pEndLn ) + { + bool bSelected = false; + sal_uLong nBorder = 0; + sal_uInt16 nCol = 0; + maLines[rLine].reserve( pStartLn->size() ); + BoxStructure::iterator pSel = maLines[rLine].end(); + ColumnStructure::iterator pCol = maCols.begin(); + for( const auto& rBox : *pStartLn ) + { + addBox( rLine, pSelBoxes, rBox.mpBox, nBorder, nCol, + pCol, pSel, bSelected, rBox.mbCovered ); + } + ++rLine; + ++pStartLn; + } + } + } + else + { + bool bSelected = false; + sal_uLong nBorder = 0; + sal_uInt16 nCol = 0; + maLines[rLine].reserve( rBoxes.size() ); + ColumnStructure::iterator pCol = maCols.begin(); + BoxStructure::iterator pSel = maLines[rLine].end(); + for( auto pBox : rBoxes ) + addBox( rLine, pSelBoxes, pBox, nBorder, nCol, + pCol, pSel, bSelected, false ); + ++rLine; + } + } + + void TableStructure::addBox( sal_uInt16 nLine, const SwSelBoxes* pSelBoxes, + SwTableBox *pBox, sal_uLong &rnBorder, sal_uInt16 &rnCol, + ColumnStructure::iterator& rpCol, BoxStructure::iterator& rpSel, + bool &rbSelected, bool bCovered ) + { + BoxSpanInfo aInfo; + if( pSelBoxes && + pSelBoxes->end() != pSelBoxes->find( pBox ) ) + { + aInfo.mbSelected = true; + if( mnStartCol == USHRT_MAX ) + { + mnStartCol = static_cast<sal_uInt16>(maLines[nLine].size()); + if( pSelBoxes->size() < 2 ) + { + pSelBoxes = nullptr; + aInfo.mbSelected = false; + } + } + } + else + aInfo.mbSelected = false; + rnBorder += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + const sal_uInt16 nLeftCol = rnCol; + while( rpCol != maCols.end() && *rpCol < rnBorder ) + { + ++rnCol; + ++rpCol; + } + if( rpCol == maCols.end() || *rpCol > rnBorder ) + { + rpCol = maCols.insert( rpCol, rnBorder ); + incColSpan( nLine, rnCol ); + } + aInfo.mnColSpan = rnCol - nLeftCol; + aInfo.mpCopy = nullptr; + aInfo.mpBox = bCovered ? nullptr : pBox; + maLines[nLine].push_back( aInfo ); + if( aInfo.mbSelected ) + { + if( rbSelected ) + { + while( rpSel != maLines[nLine].end() ) + { + rpSel->mbSelected = true; + ++rpSel; + } + } + else + { + rpSel = maLines[nLine].end(); + rbSelected = true; + } + --rpSel; + } + } + + void TableStructure::moreLines( const SwTable& rTable ) + { + if( mnAddLine ) + { + const SwTableLines &rLines = rTable.GetTabLines(); + const sal_uInt16 nLineCount = rLines.size(); + if( nLineCount < mnAddLine ) + mnAddLine = nLineCount; + sal_uInt16 nLine = static_cast<sal_uInt16>(maLines.size()); + maLines.resize( nLine + mnAddLine ); + while( mnAddLine ) + { + SwTableLine *pLine = rLines[ nLineCount - mnAddLine ]; + addLine( nLine, pLine->GetTabBoxes(), nullptr, rTable.IsNewModel() ); + --mnAddLine; + } + } + } + + void TableStructure::incColSpan( sal_uInt16 nLineMax, sal_uInt16 nNewCol ) + { + for( sal_uInt16 nLine = 0; nLine < nLineMax; ++nLine ) + { + BoxStructure::iterator pInfo = maLines[nLine].begin(); + BoxStructure::iterator pEnd = maLines[nLine].end(); + long nCol = pInfo->mnColSpan; + while( nNewCol > nCol && ++pInfo != pEnd ) + nCol += pInfo->mnColSpan; + if( pInfo != pEnd ) + ++(pInfo->mnColSpan); + } + } + + void TableStructure::assignBoxes( const TableStructure &rSource ) + { + LineStructure::const_iterator pFirstLine = rSource.maLines.begin(); + LineStructure::const_iterator pLastLine = rSource.maLines.end(); + if( pFirstLine == pLastLine ) + return; + LineStructure::const_iterator pCurrLine = pFirstLine; + LineStructure::size_type nLineCount = maLines.size(); + sal_uInt16 nFirstStartCol = 0; + { + BoxStructure::const_iterator pFirstBox = pFirstLine->begin(); + if( pFirstBox != pFirstLine->end() && pFirstBox->mpBox && + pFirstBox->mpBox->getDummyFlag() ) + nFirstStartCol = pFirstBox->mnColSpan; + } + for( LineStructure::size_type nLine = 0; nLine < nLineCount; ++nLine ) + { + BoxStructure::const_iterator pFirstBox = pCurrLine->begin(); + BoxStructure::const_iterator pLastBox = pCurrLine->end(); + sal_uInt16 nCurrStartCol = mnStartCol; + if( pFirstBox != pLastBox ) + { + BoxStructure::const_iterator pTmpBox = pLastBox; + --pTmpBox; + if( pTmpBox->mpBox && pTmpBox->mpBox->getDummyFlag() ) + --pLastBox; + if( pFirstBox != pLastBox && pFirstBox->mpBox && + pFirstBox->mpBox->getDummyFlag() ) + { + if( nCurrStartCol < USHRT_MAX ) + { + if( pFirstBox->mnColSpan > nFirstStartCol ) + nCurrStartCol += pFirstBox->mnColSpan - nFirstStartCol; + } + ++pFirstBox; + } + } + if( pFirstBox != pLastBox ) + { + BoxStructure::const_iterator pCurrBox = pFirstBox; + BoxStructure &rBox = maLines[nLine]; + BoxStructure::size_type nBoxCount = rBox.size(); + sal_uInt16 nCol = 0; + for( BoxStructure::size_type nBox = 0; nBox < nBoxCount; ++nBox ) + { + BoxSpanInfo& rInfo = rBox[nBox]; + nCol += rInfo.mnColSpan; + if( rInfo.mbSelected || nCol > nCurrStartCol ) + { + rInfo.mpCopy = pCurrBox->mpBox; + if( rInfo.mbSelected && rInfo.mpCopy->getDummyFlag() ) + { + ++pCurrBox; + if( pCurrBox == pLastBox ) + { + pCurrBox = pFirstBox; + if( pCurrBox->mpBox->getDummyFlag() ) + ++pCurrBox; + } + rInfo.mpCopy = pCurrBox->mpBox; + } + ++pCurrBox; + if( pCurrBox == pLastBox ) + { + if( rInfo.mbSelected ) + pCurrBox = pFirstBox; + else + { + rInfo.mbSelected = rInfo.mpCopy == nullptr; + break; + } + } + rInfo.mbSelected = rInfo.mpCopy == nullptr; + } + } + } + ++pCurrLine; + if( pCurrLine == pLastLine ) + pCurrLine = pFirstLine; + } + } + + void TableStructure::copyBoxes( const SwTable& rSource, SwTable& rDstTable, + SwUndoTableCpyTable* pUndo ) const + { + LineStructure::size_type nLineCount = maLines.size(); + for( LineStructure::size_type nLine = 0; nLine < nLineCount; ++nLine ) + { + const BoxStructure &rBox = maLines[nLine]; + BoxStructure::size_type nBoxCount = rBox.size(); + for( BoxStructure::size_type nBox = 0; nBox < nBoxCount; ++nBox ) + { + const BoxSpanInfo& rInfo = rBox[nBox]; + if( ( rInfo.mpCopy && !rInfo.mpCopy->getDummyFlag() ) + || rInfo.mbSelected ) + { + SwTableBox *pBox = rInfo.mpBox; + if( pBox && pBox->getRowSpan() > 0 ) + lcl_CpyBox( rSource, rInfo.mpCopy, rDstTable, pBox, + true, pUndo ); + } + } + } + } +} + +/** Copy Table into this Box. + Copy all Boxes of a Line into the corresponding Boxes. The old content + is deleted by doing this. + If no Box is left the remaining content goes to the Box of a "BaseLine". + If there's no Line anymore, put it also into the last Box of a "BaseLine". */ +static void lcl_CpyBox( const SwTable& rCpyTable, const SwTableBox* pCpyBox, + SwTable& rDstTable, SwTableBox* pDstBox, + bool bDelContent, SwUndoTableCpyTable* pUndo ) +{ + OSL_ENSURE( ( !pCpyBox || pCpyBox->GetSttNd() ) && pDstBox->GetSttNd(), + "No content in this Box" ); + + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + SwDoc* pDoc = rDstTable.GetFrameFormat()->GetDoc(); + + // First copy the new content and then delete the old one. + // Do not create empty Sections, otherwise they will be deleted! + std::unique_ptr< SwNodeRange > pRg( pCpyBox ? + new SwNodeRange ( *pCpyBox->GetSttNd(), 1, + *pCpyBox->GetSttNd()->EndOfSectionNode() ) : nullptr ); + + SwNodeIndex aInsIdx( *pDstBox->GetSttNd(), bDelContent ? 1 : + pDstBox->GetSttNd()->EndOfSectionIndex() - + pDstBox->GetSttIdx() ); + + if( pUndo ) + pUndo->AddBoxBefore( *pDstBox, bDelContent ); + + bool bUndoRedline = pUndo && pDoc->getIDocumentRedlineAccess().IsRedlineOn(); + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodeIndex aSavePos( aInsIdx, -1 ); + if (pRg) + pCpyDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(*pRg, aInsIdx, nullptr, false); + else + pDoc->GetNodes().MakeTextNode( aInsIdx, pDoc->GetDfltTextFormatColl() ); + ++aSavePos; + + SwTableLine* pLine = pDstBox->GetUpper(); + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + bool bReplaceColl = true; + if( bDelContent && !bUndoRedline ) + { + // Delete the Fly first, then the corresponding Nodes + SwNodeIndex aEndNdIdx( *aInsIdx.GetNode().EndOfSectionNode() ); + + // Move Bookmarks + { + SwPosition aMvPos( aInsIdx ); + SwContentNode* pCNd = SwNodes::GoPrevious( &aMvPos.nNode ); + aMvPos.nContent.Assign( pCNd, pCNd->Len() ); + SwDoc::CorrAbs( aInsIdx, aEndNdIdx, aMvPos ); + } + + // If we still have FlyFrames hanging around, delete them too + for( const auto pFly : *pDoc->GetSpzFrameFormats() ) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + SwPosition const*const pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aInsIdx <= pAPos->nNode && pAPos->nNode <= aEndNdIdx ) + { + pDoc->getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + } + } + + // If DestBox is a Headline Box and has Table style set, then + // DO NOT automatically set the TableHeadline style! + if( 1 < rDstTable.GetTabLines().size() && + pLine == rDstTable.GetTabLines().front() ) + { + SwContentNode* pCNd = aInsIdx.GetNode().GetContentNode(); + if( !pCNd ) + { + SwNodeIndex aTmp( aInsIdx ); + pCNd = pDoc->GetNodes().GoNext( &aTmp ); + } + + if( pCNd && + RES_POOLCOLL_TABLE_HDLN != + pCNd->GetFormatColl()->GetPoolFormatId() ) + bReplaceColl = false; + } + + pDoc->GetNodes().Delete( aInsIdx, aEndNdIdx.GetIndex() - aInsIdx.GetIndex() ); + } + + //b6341295: Table copy redlining will be managed by AddBoxAfter() + if( pUndo ) + pUndo->AddBoxAfter( *pDstBox, aInsIdx, bDelContent ); + + // heading + SwTextNode *const pTextNd = aSavePos.GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_uInt16 nPoolId = pTextNd->GetTextColl()->GetPoolFormatId(); + if( bReplaceColl && + (( 1 < rDstTable.GetTabLines().size() && + pLine == rDstTable.GetTabLines().front() ) + // Is the Table's content still valid? + ? RES_POOLCOLL_TABLE == nPoolId + : RES_POOLCOLL_TABLE_HDLN == nPoolId ) ) + { + SwTextFormatColl* pColl = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( + static_cast<sal_uInt16>( + RES_POOLCOLL_TABLE == nPoolId + ? RES_POOLCOLL_TABLE_HDLN + : RES_POOLCOLL_TABLE ) ); + if( pColl ) // Apply style + { + SwPaM aPam( aSavePos ); + aPam.SetMark(); + aPam.Move( fnMoveForward, GoInSection ); + pDoc->SetTextFormatColl( aPam, pColl ); + } + } + + // Delete the current Formula/Format/Value values + if( SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMAT ) || + SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_FORMULA ) || + SfxItemState::SET == pDstBox->GetFrameFormat()->GetItemState( RES_BOXATR_VALUE ) ) + { + pDstBox->ClaimFrameFormat()->ResetFormatAttr( RES_BOXATR_FORMAT, + RES_BOXATR_VALUE ); + } + + // Copy the TableBoxAttributes - Formula/Format/Value + if( pCpyBox ) + { + SfxItemSet aBoxAttrSet( pCpyDoc->GetAttrPool(), svl::Items<RES_BOXATR_FORMAT, + RES_BOXATR_VALUE>{} ); + aBoxAttrSet.Put( pCpyBox->GetFrameFormat()->GetAttrSet() ); + if( aBoxAttrSet.Count() ) + { + const SfxPoolItem* pItem; + SvNumberFormatter* pN = pDoc->GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && SfxItemState::SET == aBoxAttrSet. + GetItemState( RES_BOXATR_FORMAT, false, &pItem ) ) + { + sal_uLong nOldIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + aBoxAttrSet.Put( SwTableBoxNumFormat( nNewIdx )); + } + pDstBox->ClaimFrameFormat()->SetFormatAttr( aBoxAttrSet ); + } + } + } +} + +bool SwTable::InsNewTable( const SwTable& rCpyTable, const SwSelBoxes& rSelBoxes, + SwUndoTableCpyTable* pUndo ) +{ + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + // Analyze source structure + TableStructure aCopyStruct( rCpyTable ); + + // Analyze target structure (from start box) and selected substructure + FndBox_ aFndBox( nullptr, nullptr ); + { // get all boxes/lines + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + TableStructure aTarget( *this, aFndBox, rSelBoxes, aCopyStruct.getLineCount() ); + + bool bClear = false; + if( aTarget.mnAddLine && IsNewModel() ) + { + SwSelBoxes aBoxes; + aBoxes.insert( GetTabLines().back()->GetTabBoxes().front() ); + if( pUndo ) + pUndo->InsertRow( *this, aBoxes, aTarget.mnAddLine ); + else + InsertRow( pDoc, aBoxes, aTarget.mnAddLine, /*bBehind*/true ); + + aTarget.moreLines( *this ); + bClear = true; + } + + // Find mapping, if needed extend target table and/or selection + aTarget.assignBoxes( aCopyStruct ); + + { + // Change table formulas into relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // delete frames + aFndBox.SetTableLines( *this ); + if( bClear ) + aFndBox.ClearLineBehind(); + aFndBox.DelFrames( *this ); + + // copy boxes + aTarget.copyBoxes( rCpyTable, *this, pUndo ); + + // adjust row span attributes accordingly + + // make frames + aFndBox.MakeFrames( *this ); + + return true; +} + +/** Copy Table into this Box. + Copy all Boxes of a Line into the corresponding Boxes. The old content is + deleted by doing this. + If no Box is left the remaining content goes to the Box of a "BaseLine". + If there's no Line anymore, put it also into the last Box of a "BaseLine". */ +bool SwTable::InsTable( const SwTable& rCpyTable, const SwNodeIndex& rSttBox, + SwUndoTableCpyTable* pUndo ) +{ + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + + SwTableNode* pTableNd = pDoc->IsIdxInTable( rSttBox ); + + // Find the Box, to which should be copied: + SwTableBox* pMyBox = GetTableBox( + rSttBox.GetNode().FindTableBoxStartNode()->GetIndex() ); + + OSL_ENSURE( pMyBox, "Index is not in a Box in this Table" ); + + // First delete the Table's Frames + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.DelFrames( pTableNd->GetTable() ); + + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + bool bDelContent = true; + const SwTableBox* pTmp; + + for( auto pLine : rCpyTable.GetTabLines() ) + { + // Get the first from the CopyLine + const SwTableBox* pCpyBox = pLine->GetTabBoxes().front(); + while( !pCpyBox->GetTabLines().empty() ) + pCpyBox = pCpyBox->GetTabLines().front()->GetTabBoxes().front(); + + do { + // First copy the new content and then delete the old one. + // Do not create empty Sections, otherwise they will be deleted! + lcl_CpyBox( rCpyTable, pCpyBox, *this, pMyBox, bDelContent, pUndo ); + + if( nullptr == (pTmp = pCpyBox->FindNextBox( rCpyTable, pCpyBox, false ))) + break; // no more Boxes + pCpyBox = pTmp; + + if( nullptr == ( pTmp = pMyBox->FindNextBox( *this, pMyBox, false ))) + bDelContent = false; // No space left? + else + pMyBox = const_cast<SwTableBox*>(pTmp); + + } while( true ); + + // Find the topmost Line + SwTableLine* pNxtLine = pMyBox->GetUpper(); + while( pNxtLine->GetUpper() ) + pNxtLine = pNxtLine->GetUpper()->GetUpper(); + const SwTableLines::size_type nPos = GetTabLines().GetPos( pNxtLine ) + 1; + // Is there a next? + if( nPos >= GetTabLines().size() ) + bDelContent = false; // there is none, all goes into the last Box + else + { + // Find the next Box with content + pNxtLine = GetTabLines()[ nPos ]; + pMyBox = pNxtLine->GetTabBoxes().front(); + while( !pMyBox->GetTabLines().empty() ) + pMyBox = pMyBox->GetTabLines().front()->GetTabBoxes().front(); + bDelContent = true; + } + } + + aFndBox.MakeFrames( pTableNd->GetTable() ); // Create the Frames anew + return true; +} + +bool SwTable::InsTable( const SwTable& rCpyTable, const SwSelBoxes& rSelBoxes, + SwUndoTableCpyTable* pUndo ) +{ + OSL_ENSURE( !rSelBoxes.empty(), "Missing selection" ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + if( IsNewModel() || rCpyTable.IsNewModel() ) + return InsNewTable( rCpyTable, rSelBoxes, pUndo ); + + OSL_ENSURE( !rCpyTable.IsTableComplex(), "Table too complex" ); + + SwDoc* pDoc = GetFrameFormat()->GetDoc(); + SwDoc* pCpyDoc = rCpyTable.GetFrameFormat()->GetDoc(); + + SwTableNumFormatMerge aTNFM( *pCpyDoc, *pDoc ); + + FndLine_ *pFLine; + FndBox_ aFndBox( nullptr, nullptr ); + // Find all Boxes/Lines + { + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + + // Special case: If a Box is located in a Table, copy it to all selected + // Boxes! + if( 1 != rCpyTable.GetTabSortBoxes().size() ) + { + FndBox_* pFndBox; + + const FndLines_t::size_type nFndCnt = aFndBox.GetLines().size(); + if( !nFndCnt ) + return false; + + // Check if we have enough space for all Lines and Boxes + SwTableLines::size_type nTstLns = 0; + pFLine = aFndBox.GetLines().front().get(); + sal_uInt16 nSttLine = GetTabLines().GetPos( pFLine->GetLine() ); + // Do we have as many rows, actually? + if( 1 == nFndCnt ) + { + // Is there still enough space in the Table? + if( (GetTabLines().size() - nSttLine ) < + rCpyTable.GetTabLines().size() ) + { + // If we don't have enough Lines, then see if we can insert + // new ones to reach our goal. But only if the SSelection + // contains a Box! + if( 1 < rSelBoxes.size() ) + return false; + + const sal_uInt16 nNewLns = rCpyTable.GetTabLines().size() - + (GetTabLines().size() - nSttLine ); + + // See if the Box count is high enough for the Lines + SwTableLine* pLastLn = GetTabLines().back(); + + SwTableBox* pSttBox = pFLine->GetBoxes()[0]->GetBox(); + const SwTableBoxes::size_type nSttBox = pFLine->GetLine()->GetBoxPos( pSttBox ); + for( SwTableLines::size_type n = rCpyTable.GetTabLines().size() - nNewLns; + n < rCpyTable.GetTabLines().size(); ++n ) + { + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ n ]; + + if( pLastLn->GetTabBoxes().size() < nSttBox || + ( pLastLn->GetTabBoxes().size() - nSttBox ) < + pCpyLn->GetTabBoxes().size() ) + return false; + + // Test for nesting + for( SwTableBoxes::size_type nBx = 0; nBx < pCpyLn->GetTabBoxes().size(); ++nBx ) + if( !pLastLn->GetTabBoxes()[ nSttBox + nBx ]->GetSttNd() ) + return false; + } + // We have enough space for the to-be-copied, so insert new + // rows accordingly. + SwTableBox* pInsBox = pLastLn->GetTabBoxes()[ nSttBox ]; + OSL_ENSURE( pInsBox && pInsBox->GetSttNd(), + "no ContentBox or it's not in this Table" ); + SwSelBoxes aBoxes; + + if( pUndo + ? !pUndo->InsertRow( *this, SelLineFromBox( pInsBox, + aBoxes ), nNewLns ) + : !InsertRow( pDoc, SelLineFromBox( pInsBox, + aBoxes ), nNewLns, /*bBehind*/true ) ) + return false; + } + + nTstLns = rCpyTable.GetTabLines().size(); // copy this many + } + else if( 0 == (nFndCnt % rCpyTable.GetTabLines().size()) ) + nTstLns = nFndCnt; + else + return false; // not enough space for the rows + + for( SwTableLines::size_type nLn = 0; nLn < nTstLns; ++nLn ) + { + // We have enough rows, so check the Boxes per row + pFLine = aFndBox.GetLines()[ nLn % nFndCnt ].get(); + SwTableLine* pLine = pFLine->GetLine(); + SwTableBox* pSttBox = pFLine->GetBoxes()[0]->GetBox(); + const SwTableBoxes::size_type nSttBox = pLine->GetBoxPos( pSttBox ); + std::unique_ptr<FndLine_> pInsFLine; + if( nLn >= nFndCnt ) + { + // We have more rows in the ClipBoard than we have selected + pInsFLine.reset(new FndLine_( GetTabLines()[ nSttLine + nLn ], + &aFndBox )); + pLine = pInsFLine->GetLine(); + } + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ nLn % + rCpyTable.GetTabLines().size() ]; + + // Selected too few rows? + if( pInsFLine ) + { + // We insert a new row into the FndBox + if( pLine->GetTabBoxes().size() < nSttBox || + pLine->GetTabBoxes().size() - nSttBox < pFLine->GetBoxes().size() ) + { + return false; + } + + // Test for nesting + for (FndBoxes_t::size_type nBx = 0; nBx < pFLine->GetBoxes().size(); ++nBx) + { + SwTableBox *pTmpBox = pLine->GetTabBoxes()[ nSttBox + nBx ]; + if( !pTmpBox->GetSttNd() ) + { + return false; + } + // if Ok, insert the Box into the FndLine + pFndBox = new FndBox_( pTmpBox, pInsFLine.get() ); + pInsFLine->GetBoxes().insert( pInsFLine->GetBoxes().begin() + nBx, + std::unique_ptr<FndBox_>(pFndBox)); + } + aFndBox.GetLines().insert( aFndBox.GetLines().begin() + nLn, std::move(pInsFLine)); + } + else if( pFLine->GetBoxes().size() == 1 ) + { + if( pLine->GetTabBoxes().size() < nSttBox || + ( pLine->GetTabBoxes().size() - nSttBox ) < + pCpyLn->GetTabBoxes().size() ) + return false; + + // Test for nesting + for( SwTableBoxes::size_type nBx = 0; nBx < pCpyLn->GetTabBoxes().size(); ++nBx ) + { + SwTableBox *pTmpBox = pLine->GetTabBoxes()[ nSttBox + nBx ]; + if( !pTmpBox->GetSttNd() ) + return false; + // if Ok, insert the Box into the FndLine + if( nBx == pFLine->GetBoxes().size() ) + { + pFndBox = new FndBox_( pTmpBox, pFLine ); + pFLine->GetBoxes().insert(pFLine->GetBoxes().begin() + nBx, + std::unique_ptr<FndBox_>(pFndBox)); + } + } + } + else + { + // Match the selected Boxes with the ones in the Clipboard + // (n times) + if( 0 != ( pFLine->GetBoxes().size() % + pCpyLn->GetTabBoxes().size() )) + return false; + + // Test for nesting + for (auto &rpBox : pFLine->GetBoxes()) + { + if (!rpBox->GetBox()->GetSttNd()) + return false; + } + } + } + + if( aFndBox.GetLines().empty() ) + return false; + } + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( &rCpyTable ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pCpyDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + // Delete the Frames + aFndBox.SetTableLines( *this ); + //Not dispose accessible table + aFndBox.DelFrames( *this ); + + if( 1 == rCpyTable.GetTabSortBoxes().size() ) + { + SwTableBox *pTmpBx = rCpyTable.GetTabSortBoxes()[0]; + for (size_t n = 0; n < rSelBoxes.size(); ++n) + { + lcl_CpyBox( rCpyTable, pTmpBx, *this, + rSelBoxes[n], true, pUndo ); + } + } + else + for (FndLines_t::size_type nLn = 0; nLn < aFndBox.GetLines().size(); ++nLn) + { + pFLine = aFndBox.GetLines()[ nLn ].get(); + SwTableLine* pCpyLn = rCpyTable.GetTabLines()[ + nLn % rCpyTable.GetTabLines().size() ]; + for (FndBoxes_t::size_type nBx = 0; nBx < pFLine->GetBoxes().size(); ++nBx) + { + // Copy the pCpyBox into pMyBox + lcl_CpyBox( rCpyTable, pCpyLn->GetTabBoxes()[ + nBx % pCpyLn->GetTabBoxes().size() ], + *this, pFLine->GetBoxes()[nBx]->GetBox(), true, pUndo ); + } + } + + aFndBox.MakeFrames( *this ); + return true; +} + +static void FndContentLine( const SwTableLine* pLine, SwSelBoxes* pPara ); + +static void FndContentBox( const SwTableBox* pBox, SwSelBoxes* pPara ) +{ + if( !pBox->GetTabLines().empty() ) + { + for( const SwTableLine* pLine : pBox->GetTabLines() ) + FndContentLine( pLine, pPara ); + } + else + pPara->insert( const_cast<SwTableBox*>(pBox) ); +} + +static void FndContentLine( const SwTableLine* pLine, SwSelBoxes* pPara ) +{ + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + FndContentBox(pBox, pPara ); +} + +// Find all Boxes with content in this Box +SwSelBoxes& SwTable::SelLineFromBox( const SwTableBox* pBox, + SwSelBoxes& rBoxes, bool bToTop ) +{ + SwTableLine* pLine = const_cast<SwTableLine*>(pBox->GetUpper()); + if( bToTop ) + while( pLine->GetUpper() ) + pLine = pLine->GetUpper()->GetUpper(); + + // Delete all old ones + rBoxes.clear(); + for( const auto& rpBox : pLine->GetTabBoxes() ) + FndContentBox(rpBox, &rBoxes ); + return rBoxes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/tblrwcl.cxx b/sw/source/core/doc/tblrwcl.cxx new file mode 100644 index 000000000..5eb2ff999 --- /dev/null +++ b/sw/source/core/doc/tblrwcl.cxx @@ -0,0 +1,3403 @@ +/* -*- 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 <memory> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <hintids.hxx> + +#include <editeng/lrspitem.hxx> +#include <editeng/boxitem.hxx> +#include <tools/fract.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docsh.hxx> +#include <fesh.hxx> +#include <tabfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <tblsel.hxx> +#include <fldbas.hxx> +#include <rowfrm.hxx> +#include <ddefld.hxx> +#include <hints.hxx> +#include <UndoTable.hxx> +#include <cellatr.hxx> +#include <mvsave.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <poolfmt.hxx> +#include <tblrwcl.hxx> +#include <unochart.hxx> +#include <o3tl/numeric.hxx> +#include <calbck.hxx> +#include <docary.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +#define COLFUZZY 20 +#define ROWFUZZY 10 + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +namespace { + +// In order to set the Frame Formats for the Boxes, it's enough to look +// up the current one in the array. If it's already there return the new one. +struct CpyTabFrame +{ + SwFrameFormat* pFrameFormat; + SwTableBoxFormat *pNewFrameFormat; + + explicit CpyTabFrame(SwFrameFormat* pCurrentFrameFormat) : pNewFrameFormat( nullptr ) + { pFrameFormat = pCurrentFrameFormat; } + + bool operator==( const CpyTabFrame& rCpyTabFrame ) const + { return pFrameFormat == rCpyTabFrame.pFrameFormat; } + bool operator<( const CpyTabFrame& rCpyTabFrame ) const + { return pFrameFormat < rCpyTabFrame.pFrameFormat; } +}; + +struct CR_SetBoxWidth +{ + SwShareBoxFormats aShareFormats; + SwTableNode* pTableNd; + SwTwips nDiff, nSide, nMaxSize, nLowerDiff; + TableChgMode nMode; + bool bBigger, bLeft; + + CR_SetBoxWidth( TableChgWidthHeightType eType, SwTwips nDif, SwTwips nSid, + SwTwips nMax, SwTableNode* pTNd ) + : pTableNd( pTNd ), + nDiff( nDif ), nSide( nSid ), nMaxSize( nMax ), nLowerDiff( 0 ) + { + bLeft = TableChgWidthHeightType::ColLeft == extractPosition( eType ) || + TableChgWidthHeightType::CellLeft == extractPosition( eType ); + bBigger = bool(eType & TableChgWidthHeightType::BiggerMode ); + nMode = pTableNd->GetTable().GetTableChgMode(); + } + CR_SetBoxWidth( const CR_SetBoxWidth& rCpy ) + : pTableNd( rCpy.pTableNd ), + nDiff( rCpy.nDiff ), nSide( rCpy.nSide ), + nMaxSize( rCpy.nMaxSize ), nLowerDiff( 0 ), + nMode( rCpy.nMode ), + bBigger( rCpy.bBigger ), bLeft( rCpy.bLeft ) + { + } + + void LoopClear() + { + nLowerDiff = 0; + } +}; + +} + +static bool lcl_SetSelBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ); +static bool lcl_SetOtherBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ); + +typedef bool (*FN_lcl_SetBoxWidth)(SwTableLine*, CR_SetBoxWidth&, SwTwips, bool ); + +#ifdef DBG_UTIL + +#define CHECKBOXWIDTH \ + { \ + SwTwips nSize = GetFrameFormat()->GetFrameSize().GetWidth(); \ + for (size_t nTmp = 0; nTmp < m_aLines.size(); ++nTmp) \ + ::CheckBoxWidth( *m_aLines[ nTmp ], nSize ); \ + } + +#define CHECKTABLELAYOUT \ + { \ + for ( size_t i = 0; i < GetTabLines().size(); ++i ) \ + { \ + SwFrameFormat* pFormat = GetTabLines()[i]->GetFrameFormat(); \ + SwIterator<SwRowFrame,SwFormat> aIter( *pFormat ); \ + for (SwRowFrame* pFrame=aIter.First(); pFrame; pFrame=aIter.Next())\ + { \ + if ( pFrame->GetTabLine() == GetTabLines()[i] ) \ + { \ + OSL_ENSURE( pFrame->GetUpper()->IsTabFrame(), \ + "Table layout does not match table structure" ); \ + } \ + } \ + } \ + } + +#else + +#define CHECKBOXWIDTH +#define CHECKTABLELAYOUT + +#endif // DBG_UTIL + +namespace { + +struct CR_SetLineHeight +{ + SwTableNode* pTableNd; + SwTwips nMaxSpace, nMaxHeight; + TableChgMode nMode; + bool bBigger; + + CR_SetLineHeight( TableChgWidthHeightType eType, SwTableNode* pTNd ) + : pTableNd( pTNd ), + nMaxSpace( 0 ), nMaxHeight( 0 ) + { + bBigger = bool(eType & TableChgWidthHeightType::BiggerMode ); + nMode = pTableNd->GetTable().GetTableChgMode(); + } + CR_SetLineHeight( const CR_SetLineHeight& rCpy ) + : pTableNd( rCpy.pTableNd ), + nMaxSpace( rCpy.nMaxSpace ), nMaxHeight( rCpy.nMaxHeight ), + nMode( rCpy.nMode ), + bBigger( rCpy.bBigger ) + {} +}; + +} + +static bool lcl_SetSelLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ); +static bool lcl_SetOtherLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ); + +typedef bool (*FN_lcl_SetLineHeight)(SwTableLine*, CR_SetLineHeight&, SwTwips, bool ); + +typedef o3tl::sorted_vector<CpyTabFrame> CpyTabFrames; + +namespace { + +struct CpyPara +{ + std::shared_ptr< std::vector< std::vector< sal_uLong > > > pWidths; + SwDoc* pDoc; + SwTableNode* pTableNd; + CpyTabFrames& rTabFrameArr; + SwTableLine* pInsLine; + SwTableBox* pInsBox; + sal_uLong nOldSize, nNewSize; // in order to correct the size attributes + sal_uLong nMinLeft, nMaxRight; + sal_uInt16 nCpyCnt, nInsPos; + sal_uInt16 nLnIdx, nBoxIdx; + sal_uInt8 nDelBorderFlag; + bool bCpyContent; + + CpyPara( SwTableNode* pNd, sal_uInt16 nCopies, CpyTabFrames& rFrameArr ) + : pDoc( pNd->GetDoc() ), pTableNd( pNd ), rTabFrameArr(rFrameArr), + pInsLine(nullptr), pInsBox(nullptr), nOldSize(0), nNewSize(0), + nMinLeft(ULONG_MAX), nMaxRight(0), + nCpyCnt(nCopies), nInsPos(0), + nLnIdx(0), nBoxIdx(0), + nDelBorderFlag(0), bCpyContent( true ) + {} + CpyPara( const CpyPara& rPara, SwTableLine* pLine ) + : pWidths( rPara.pWidths ), pDoc(rPara.pDoc), pTableNd(rPara.pTableNd), + rTabFrameArr(rPara.rTabFrameArr), pInsLine(pLine), pInsBox(rPara.pInsBox), + nOldSize(0), nNewSize(rPara.nNewSize), nMinLeft( rPara.nMinLeft ), + nMaxRight( rPara.nMaxRight ), nCpyCnt(rPara.nCpyCnt), nInsPos(0), + nLnIdx( rPara.nLnIdx), nBoxIdx( rPara.nBoxIdx ), + nDelBorderFlag( rPara.nDelBorderFlag ), bCpyContent( rPara.bCpyContent ) + {} + CpyPara( const CpyPara& rPara, SwTableBox* pBox ) + : pWidths( rPara.pWidths ), pDoc(rPara.pDoc), pTableNd(rPara.pTableNd), + rTabFrameArr(rPara.rTabFrameArr), pInsLine(rPara.pInsLine), pInsBox(pBox), + nOldSize(rPara.nOldSize), nNewSize(rPara.nNewSize), + nMinLeft( rPara.nMinLeft ), nMaxRight( rPara.nMaxRight ), + nCpyCnt(rPara.nCpyCnt), nInsPos(0), nLnIdx(rPara.nLnIdx), nBoxIdx(rPara.nBoxIdx), + nDelBorderFlag( rPara.nDelBorderFlag ), bCpyContent( rPara.bCpyContent ) + {} +}; + +} + +static void lcl_CopyRow(FndLine_ & rFndLine, CpyPara *const pCpyPara); + +static void lcl_CopyCol( FndBox_ & rFndBox, CpyPara *const pCpyPara) +{ + // Look up the Frame Format in the Frame Format Array + SwTableBox* pBox = rFndBox.GetBox(); + CpyTabFrame aFindFrame(pBox->GetFrameFormat()); + + sal_uInt16 nFndPos; + if( pCpyPara->nCpyCnt ) + { + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.lower_bound( aFindFrame ); + nFndPos = itFind - pCpyPara->rTabFrameArr.begin(); + if( itFind == pCpyPara->rTabFrameArr.end() || !(*itFind == aFindFrame) ) + { + // For nested copying, also save the new Format as an old one. + SwTableBoxFormat* pNewFormat = static_cast<SwTableBoxFormat*>(pBox->ClaimFrameFormat()); + + // Find the selected Boxes in the Line: + FndLine_ const* pCmpLine = nullptr; + SwFormatFrameSize aFrameSz( pNewFormat->GetFrameSize() ); + + bool bDiffCount = false; + if( !pBox->GetTabLines().empty() ) + { + pCmpLine = rFndBox.GetLines().front().get(); + if ( pCmpLine->GetBoxes().size() != pCmpLine->GetLine()->GetTabBoxes().size() ) + bDiffCount = true; + } + + if( bDiffCount ) + { + // The first Line should be enough + FndBoxes_t const& rFndBoxes = pCmpLine->GetBoxes(); + long nSz = 0; + for( auto n = rFndBoxes.size(); n; ) + { + nSz += rFndBoxes[--n]->GetBox()-> + GetFrameFormat()->GetFrameSize().GetWidth(); + } + aFrameSz.SetWidth( aFrameSz.GetWidth() - + nSz / ( pCpyPara->nCpyCnt + 1 ) ); + pNewFormat->SetFormatAttr( aFrameSz ); + aFrameSz.SetWidth( nSz / ( pCpyPara->nCpyCnt + 1 ) ); + + // Create a new Format for the new Box, specifying its size. + aFindFrame.pNewFrameFormat = reinterpret_cast<SwTableBoxFormat*>(pNewFormat->GetDoc()-> + MakeTableLineFormat()); + *aFindFrame.pNewFrameFormat = *pNewFormat; + aFindFrame.pNewFrameFormat->SetFormatAttr( aFrameSz ); + } + else + { + aFrameSz.SetWidth( aFrameSz.GetWidth() / ( pCpyPara->nCpyCnt + 1 ) ); + pNewFormat->SetFormatAttr( aFrameSz ); + + aFindFrame.pNewFrameFormat = pNewFormat; + pCpyPara->rTabFrameArr.insert( aFindFrame ); + aFindFrame.pFrameFormat = pNewFormat; + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + } + else + { + aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]; + pBox->ChgFrameFormat( aFindFrame.pNewFrameFormat ); + } + } + else + { + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.find( aFindFrame ); + if( pCpyPara->nDelBorderFlag && + itFind != pCpyPara->rTabFrameArr.end() ) + aFindFrame = *itFind; + else + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + } + + if (!rFndBox.GetLines().empty()) + { + pBox = new SwTableBox( aFindFrame.pNewFrameFormat, + rFndBox.GetLines().size(), pCpyPara->pInsLine ); + pCpyPara->pInsLine->GetTabBoxes().insert( pCpyPara->pInsLine->GetTabBoxes().begin() + pCpyPara->nInsPos++, pBox ); + CpyPara aPara( *pCpyPara, pBox ); + aPara.nDelBorderFlag &= 7; + + for (auto const& pFndLine : rFndBox.GetLines()) + { + lcl_CopyRow(*pFndLine, &aPara); + } + } + else + { + ::InsTableBox( pCpyPara->pDoc, pCpyPara->pTableNd, pCpyPara->pInsLine, + aFindFrame.pNewFrameFormat, pBox, pCpyPara->nInsPos++ ); + + const FndBoxes_t& rFndBxs = rFndBox.GetUpper()->GetBoxes(); + if( 8 > pCpyPara->nDelBorderFlag + ? pCpyPara->nDelBorderFlag != 0 + : &rFndBox == rFndBxs[rFndBxs.size() - 1].get()) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + if( 8 > pCpyPara->nDelBorderFlag + ? rBoxItem.GetTop() + : rBoxItem.GetRight() ) + { + aFindFrame.pFrameFormat = pBox->GetFrameFormat(); + + SvxBoxItem aNew( rBoxItem ); + if( 8 > pCpyPara->nDelBorderFlag ) + aNew.SetLine( nullptr, SvxBoxItemLine::TOP ); + else + aNew.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + + if( 1 == pCpyPara->nDelBorderFlag || + 8 == pCpyPara->nDelBorderFlag ) + { + // For all Boxes that delete TopBorderLine, we copy after that + pBox = pCpyPara->pInsLine->GetTabBoxes()[ + pCpyPara->nInsPos - 1 ]; + } + + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pBox->GetFrameFormat()); + + // Else we copy before that and the first Line keeps the TopLine + // and we remove it at the original + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + + if( !pCpyPara->nCpyCnt ) + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + } + } +} + +static void lcl_CopyRow(FndLine_& rFndLine, CpyPara *const pCpyPara) +{ + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(rFndLine.GetLine()->GetFrameFormat()), + rFndLine.GetBoxes().size(), pCpyPara->pInsBox ); + if( pCpyPara->pInsBox ) + { + SwTableLines& rLines = pCpyPara->pInsBox->GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + else + { + SwTableLines& rLines = pCpyPara->pTableNd->GetTable().GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + + CpyPara aPara( *pCpyPara, pNewLine ); + for (auto const& it : rFndLine.GetBoxes()) + { + lcl_CopyCol(*it, &aPara); + } + + pCpyPara->nDelBorderFlag &= 0xf8; +} + +static void lcl_InsCol( FndLine_* pFndLn, CpyPara& rCpyPara, sal_uInt16 nCpyCnt, + bool bBehind ) +{ + // Bug 29124: Not only copy in the BaseLines. If possible, we go down as far as possible + FndBox_* pFBox; + if( 1 == pFndLn->GetBoxes().size() && + !( pFBox = pFndLn->GetBoxes()[0].get() )->GetBox()->GetSttNd() ) + { + // A Box with multiple Lines, so insert into these Lines + for (auto &rpLine : pFBox->GetLines()) + { + lcl_InsCol( rpLine.get(), rCpyPara, nCpyCnt, bBehind ); + } + } + else + { + rCpyPara.pInsLine = pFndLn->GetLine(); + SwTableBox* pBox = pFndLn->GetBoxes()[ bBehind ? + pFndLn->GetBoxes().size()-1 : 0 ]->GetBox(); + rCpyPara.nInsPos = pFndLn->GetLine()->GetBoxPos( pBox ); + if( bBehind ) + ++rCpyPara.nInsPos; + + for( sal_uInt16 n = 0; n < nCpyCnt; ++n ) + { + if( n + 1 == nCpyCnt && bBehind ) + rCpyPara.nDelBorderFlag = 9; + else + rCpyPara.nDelBorderFlag = 8; + for (auto const& it : pFndLn->GetBoxes()) + { + lcl_CopyCol(*it, &rCpyPara); + } + } + } +} + +static SwRowFrame* GetRowFrame( SwTableLine& rLine ) +{ + SwIterator<SwRowFrame,SwFormat> aIter( *rLine.GetFrameFormat() ); + for( SwRowFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + if( pFrame->GetTabLine() == &rLine ) + return pFrame; + return nullptr; +} + +bool SwTable::InsertCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( !rBoxes.empty() && nCnt, "No valid Box List" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + bool bRes = true; + if( IsNewModel() ) + bRes = NewInsertCol( pDoc, rBoxes, nCnt, bBehind ); + else + { + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // Find Lines for the layout update + aFndBox.SetTableLines( *this ); + aFndBox.DelFrames( *this ); + + // TL_CHART2: nothing to be done since chart2 currently does not want to + // get notified about new rows/cols. + + CpyTabFrames aTabFrameArr; + CpyPara aCpyPara( pTableNd, nCnt, aTabFrameArr ); + + for (auto & rpLine : aFndBox.GetLines()) + { + lcl_InsCol( rpLine.get(), aCpyPara, nCnt, bBehind ); + } + + // clean up this Line's structure once again, generally all of them + GCLines(); + + // Update Layout + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH; + CHECKTABLELAYOUT; + bRes = true; + } + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD && nCnt) + pPCD->AddRowCols( *this, rBoxes, nCnt, bBehind ); + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + pDoc->GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + return bRes; +} + +bool SwTable::InsertRow_( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "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( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + FndBox_* pFndBox = &aFndBox; + { + FndLine_* pFndLine; + while( 1 == pFndBox->GetLines().size() ) + { + pFndLine = pFndBox->GetLines()[0].get(); + if( 1 != pFndLine->GetBoxes().size() ) + break; + // Don't go down too far! One Line with Box needs to remain! + FndBox_ *const pTmpBox = pFndLine->GetBoxes().front().get(); + if( !pTmpBox->GetLines().empty() ) + pFndBox = pTmpBox; + else + break; + } + } + + // Find Lines for the layout update + const bool bLayout = !IsNewModel() && + nullptr != SwIterator<SwTabFrame,SwFormat>( *GetFrameFormat() ).First(); + + if ( bLayout ) + { + aFndBox.SetTableLines( *this ); + if( pFndBox != &aFndBox ) + aFndBox.DelFrames( *this ); + // TL_CHART2: nothing to be done since chart2 currently does not want to + // get notified about new rows/cols. + } + + CpyTabFrames aTabFrameArr; + CpyPara aCpyPara( pTableNd, 0, aTabFrameArr ); + + SwTableLine* pLine = pFndBox->GetLines()[ bBehind ? + pFndBox->GetLines().size()-1 : 0 ]->GetLine(); + if( &aFndBox == pFndBox ) + aCpyPara.nInsPos = GetTabLines().GetPos( pLine ); + else + { + aCpyPara.pInsBox = pFndBox->GetBox(); + aCpyPara.nInsPos = pFndBox->GetBox()->GetTabLines().GetPos( pLine ); + } + + if( bBehind ) + { + ++aCpyPara.nInsPos; + aCpyPara.nDelBorderFlag = 1; + } + else + aCpyPara.nDelBorderFlag = 2; + + for( sal_uInt16 nCpyCnt = 0; nCpyCnt < nCnt; ++nCpyCnt ) + { + if( bBehind ) + aCpyPara.nDelBorderFlag = 1; + for (auto & rpFndLine : pFndBox->GetLines()) + lcl_CopyRow( *rpFndLine, &aCpyPara ); + } + + // clean up this Line's structure once again, generally all of them + if( !pDoc->IsInReading() ) + GCLines(); + + // Update Layout + if ( bLayout ) + { + if( pFndBox != &aFndBox ) + aFndBox.MakeFrames( *this ); + else + aFndBox.MakeNewFrames( *this, nCnt, bBehind ); + } + + CHECKBOXWIDTH; + CHECKTABLELAYOUT; + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD && nCnt) + pPCD->AddRowCols( *this, rBoxes, nCnt, bBehind ); + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + pDoc->GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + return true; +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ); + +static void lcl_LastBoxSetWidthLine( SwTableLines &rLines, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ) +{ + for ( auto pLine : rLines ) + ::lcl_LastBoxSetWidth( pLine->GetTabBoxes(), nOffset, bFirst, rShareFormats ); +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ) +{ + SwTableBox& rBox = *(bFirst ? rBoxes.front() : rBoxes.back()); + if( !rBox.GetSttNd() ) + ::lcl_LastBoxSetWidthLine( rBox.GetTabLines(), nOffset, + bFirst, rShareFormats ); + + // Adapt the Box + const SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(); + SwFormatFrameSize aNew( pBoxFormat->GetFrameSize() ); + aNew.SetWidth( aNew.GetWidth() + nOffset ); + SwFrameFormat *pFormat = rShareFormats.GetFormat( *pBoxFormat, aNew ); + if( pFormat ) + rBox.ChgFrameFormat( static_cast<SwTableBoxFormat*>(pFormat) ); + else + { + pFormat = rBox.ClaimFrameFormat(); + + pFormat->LockModify(); + pFormat->SetFormatAttr( aNew ); + pFormat->UnlockModify(); + + rShareFormats.AddFormat( *pBoxFormat, *pFormat ); + } +} + +void DeleteBox_( SwTable& rTable, SwTableBox* pBox, SwUndo* pUndo, + bool bCalcNewSize, const bool bCorrBorder, + SwShareBoxFormats* pShareFormats ) +{ + do { + SwTwips nBoxSz = bCalcNewSize ? + pBox->GetFrameFormat()->GetFrameSize().GetWidth() : 0; + SwTableLine* pLine = pBox->GetUpper(); + SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + sal_uInt16 nDelPos = pLine->GetBoxPos( pBox ); + SwTableBox* pUpperBox = pBox->GetUpper()->GetUpper(); + + // Special treatment for the border: + if( bCorrBorder && 1 < rTableBoxes.size() ) + { + const SvxBoxItem& rBoxItem = pBox->GetFrameFormat()->GetBox(); + + if( rBoxItem.GetLeft() || rBoxItem.GetRight() ) + { + bool bChgd = false; + + // JP 02.04.97: 1st part for Bug 36271 + // First the left/right edges + if( nDelPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) ) + { + SwTableBox* pNxtBox = rTableBoxes[ nDelPos + 1 ]; + const SvxBoxItem& rNxtBoxItem = pNxtBox->GetFrameFormat()->GetBox(); + + SwTableBox* pPrvBox = nDelPos ? rTableBoxes[ nDelPos - 1 ] : nullptr; + + if( pNxtBox->GetSttNd() && !rNxtBoxItem.GetLeft() && + ( !pPrvBox || !pPrvBox->GetFrameFormat()->GetBox().GetRight()) ) + { + SvxBoxItem aTmp( rNxtBoxItem ); + aTmp.SetLine( rBoxItem.GetLeft() ? rBoxItem.GetLeft() + : rBoxItem.GetRight(), + SvxBoxItemLine::LEFT ); + if( pShareFormats ) + pShareFormats->SetAttr( *pNxtBox, aTmp ); + else + pNxtBox->ClaimFrameFormat()->SetFormatAttr( aTmp ); + bChgd = true; + } + } + if( !bChgd && nDelPos ) + { + SwTableBox* pPrvBox = rTableBoxes[ nDelPos - 1 ]; + const SvxBoxItem& rPrvBoxItem = pPrvBox->GetFrameFormat()->GetBox(); + + SwTableBox* pNxtBox = nDelPos + 1 < static_cast<sal_uInt16>(rTableBoxes.size()) + ? rTableBoxes[ nDelPos + 1 ] : nullptr; + + if( pPrvBox->GetSttNd() && !rPrvBoxItem.GetRight() && + ( !pNxtBox || !pNxtBox->GetFrameFormat()->GetBox().GetLeft()) ) + { + SvxBoxItem aTmp( rPrvBoxItem ); + aTmp.SetLine( rBoxItem.GetLeft() ? rBoxItem.GetLeft() + : rBoxItem.GetRight(), + SvxBoxItemLine::RIGHT ); + if( pShareFormats ) + pShareFormats->SetAttr( *pPrvBox, aTmp ); + else + pPrvBox->ClaimFrameFormat()->SetFormatAttr( aTmp ); + } + } + } + } + + // Delete the Box first, then the Nodes! + SwStartNode* pSttNd = const_cast<SwStartNode*>(pBox->GetSttNd()); + if( pShareFormats ) + pShareFormats->RemoveFormat( *rTableBoxes[ nDelPos ]->GetFrameFormat() ); + + // Before deleting the 'Table Box' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableCellRedline( rTable.GetFrameFormat()->GetDoc(), *(rTableBoxes[nDelPos]), true, RedlineType::Any ); + delete rTableBoxes[nDelPos]; + rTableBoxes.erase( rTableBoxes.begin() + nDelPos ); + + if( pSttNd ) + { + // Has the UndoObject been prepared to save the Section? + if( pUndo && pUndo->IsDelBox() ) + static_cast<SwUndoTableNdsChg*>(pUndo)->SaveSection( pSttNd ); + else + pSttNd->GetDoc()->getIDocumentContentOperations().DeleteSection( pSttNd ); + } + + // Also delete the Line? + if( !rTableBoxes.empty() ) + { + // Then adapt the Frame-SSize + bool bLastBox = nDelPos == rTableBoxes.size(); + if( bLastBox ) + --nDelPos; + pBox = rTableBoxes[nDelPos]; + if( bCalcNewSize ) + { + SwFormatFrameSize aNew( pBox->GetFrameFormat()->GetFrameSize() ); + aNew.SetWidth( aNew.GetWidth() + nBoxSz ); + if( pShareFormats ) + pShareFormats->SetSize( *pBox, aNew ); + else + pBox->ClaimFrameFormat()->SetFormatAttr( aNew ); + + if( !pBox->GetSttNd() ) + { + // We need to this recursively in all Lines in all Cells! + SwShareBoxFormats aShareFormats; + ::lcl_LastBoxSetWidthLine( pBox->GetTabLines(), nBoxSz, + !bLastBox, + pShareFormats ? *pShareFormats + : aShareFormats ); + } + } + break; // Stop deleting + } + // Delete the Line from the Table/Box + if( !pUpperBox ) + { + // Also delete the Line from the Table + nDelPos = rTable.GetTabLines().GetPos( pLine ); + if( pShareFormats ) + pShareFormats->RemoveFormat( *rTable.GetTabLines()[ nDelPos ]->GetFrameFormat() ); + + SwTableLine* pTabLineToDelete = rTable.GetTabLines()[ nDelPos ]; + // Before deleting the 'Table Line' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableRowRedline( rTable.GetFrameFormat()->GetDoc(), *pTabLineToDelete, true, RedlineType::Any ); + delete pTabLineToDelete; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + nDelPos ); + break; // we cannot delete more + } + + // finally also delete the Line + pBox = pUpperBox; + nDelPos = pBox->GetTabLines().GetPos( pLine ); + if( pShareFormats ) + pShareFormats->RemoveFormat( *pBox->GetTabLines()[ nDelPos ]->GetFrameFormat() ); + + SwTableLine* pTabLineToDelete = pBox->GetTabLines()[ nDelPos ]; + // Before deleting the 'Table Line' from memory - delete any redlines attached to it + if ( rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().HasExtraRedlineTable() ) + rTable.GetFrameFormat()->GetDoc()->getIDocumentRedlineAccess().GetExtraRedlineTable().DeleteTableRowRedline( rTable.GetFrameFormat()->GetDoc(), *pTabLineToDelete, true, RedlineType::Any ); + delete pTabLineToDelete; + pBox->GetTabLines().erase( pBox->GetTabLines().begin() + nDelPos ); + } while( pBox->GetTabLines().empty() ); +} + +static SwTableBox* +lcl_FndNxtPrvDelBox( const SwTableLines& rTableLns, + SwTwips nBoxStt, SwTwips nBoxWidth, + sal_uInt16 nLinePos, bool bNxt, + SwSelBoxes* pAllDelBoxes, size_t *const pCurPos) +{ + SwTableBox* pFndBox = nullptr; + do { + if( bNxt ) + ++nLinePos; + else + --nLinePos; + SwTableLine* pLine = rTableLns[ nLinePos ]; + SwTwips nFndBoxWidth = 0; + SwTwips nFndWidth = nBoxStt + nBoxWidth; + + pFndBox = pLine->GetTabBoxes()[ 0 ]; + for( auto pBox : pLine->GetTabBoxes() ) + { + if ( nFndWidth <= 0 ) + { + break; + } + pFndBox = pBox; + nFndBoxWidth = pFndBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nFndWidth -= nFndBoxWidth; + } + + // Find the first ContentBox + while( !pFndBox->GetSttNd() ) + { + const SwTableLines& rLowLns = pFndBox->GetTabLines(); + if( bNxt ) + pFndBox = rLowLns.front()->GetTabBoxes().front(); + else + pFndBox = rLowLns.back()->GetTabBoxes().front(); + } + + if( std::abs( nFndWidth ) > COLFUZZY || + std::abs( nBoxWidth - nFndBoxWidth ) > COLFUZZY ) + pFndBox = nullptr; + else if( pAllDelBoxes ) + { + // If the predecessor will also be deleted, there's nothing to do + SwSelBoxes::const_iterator aFndIt = pAllDelBoxes->find( pFndBox); + if( aFndIt == pAllDelBoxes->end() ) + break; + size_t const nFndPos = aFndIt - pAllDelBoxes->begin() ; + + // else, we keep on searching. + // We do not need to recheck the Box, however + pFndBox = nullptr; + if( nFndPos <= *pCurPos ) + --*pCurPos; + pAllDelBoxes->erase( pAllDelBoxes->begin() + nFndPos ); + } + } while( bNxt ? ( nLinePos + 1 < static_cast<sal_uInt16>(rTableLns.size()) ) : nLinePos != 0 ); + return pFndBox; +} + +static void +lcl_SaveUpperLowerBorder( SwTable& rTable, const SwTableBox& rBox, + SwShareBoxFormats& rShareFormats, + SwSelBoxes* pAllDelBoxes = nullptr, + size_t *const pCurPos = nullptr ) +{ +//JP 16.04.97: 2. part for Bug 36271 + const SwTableLine* pLine = rBox.GetUpper(); + const SwTableBoxes& rTableBoxes = pLine->GetTabBoxes(); + const SwTableBox* pUpperBox = &rBox; + sal_uInt16 nDelPos = pLine->GetBoxPos( pUpperBox ); + pUpperBox = rBox.GetUpper()->GetUpper(); + const SvxBoxItem& rBoxItem = rBox.GetFrameFormat()->GetBox(); + + // then the top/bottom edges + if( !rBoxItem.GetTop() && !rBoxItem.GetBottom() ) + return; + + bool bChgd = false; + const SwTableLines* pTableLns; + if( pUpperBox ) + pTableLns = &pUpperBox->GetTabLines(); + else + pTableLns = &rTable.GetTabLines(); + + sal_uInt16 nLnPos = pTableLns->GetPos( pLine ); + + // Calculate the attribute position of the top-be-deleted Box and then + // search in the top/bottom Line of the respective counterparts. + SwTwips nBoxStt = 0; + for( sal_uInt16 n = 0; n < nDelPos; ++n ) + nBoxStt += rTableBoxes[ n ]->GetFrameFormat()->GetFrameSize().GetWidth(); + SwTwips nBoxWidth = rBox.GetFrameFormat()->GetFrameSize().GetWidth(); + + SwTableBox *pPrvBox = nullptr, *pNxtBox = nullptr; + if( nLnPos ) // Predecessor? + pPrvBox = ::lcl_FndNxtPrvDelBox( *pTableLns, nBoxStt, nBoxWidth, + nLnPos, false, pAllDelBoxes, pCurPos ); + + if( nLnPos + 1 < static_cast<sal_uInt16>(pTableLns->size()) ) // Successor? + pNxtBox = ::lcl_FndNxtPrvDelBox( *pTableLns, nBoxStt, nBoxWidth, + nLnPos, true, pAllDelBoxes, pCurPos ); + + if( pNxtBox && pNxtBox->GetSttNd() ) + { + const SvxBoxItem& rNxtBoxItem = pNxtBox->GetFrameFormat()->GetBox(); + if( !rNxtBoxItem.GetTop() && ( !pPrvBox || + !pPrvBox->GetFrameFormat()->GetBox().GetBottom()) ) + { + SvxBoxItem aTmp( rNxtBoxItem ); + aTmp.SetLine( rBoxItem.GetTop() ? rBoxItem.GetTop() + : rBoxItem.GetBottom(), + SvxBoxItemLine::TOP ); + rShareFormats.SetAttr( *pNxtBox, aTmp ); + bChgd = true; + } + } + if( !bChgd && pPrvBox && pPrvBox->GetSttNd() ) + { + const SvxBoxItem& rPrvBoxItem = pPrvBox->GetFrameFormat()->GetBox(); + if( !rPrvBoxItem.GetTop() && ( !pNxtBox || + !pNxtBox->GetFrameFormat()->GetBox().GetTop()) ) + { + SvxBoxItem aTmp( rPrvBoxItem ); + aTmp.SetLine( rBoxItem.GetTop() ? rBoxItem.GetTop() + : rBoxItem.GetBottom(), + SvxBoxItemLine::BOTTOM ); + rShareFormats.SetAttr( *pPrvBox, aTmp ); + } + } + +} + +bool SwTable::DeleteSel( + SwDoc* pDoc + , + const SwSelBoxes& rBoxes, + const SwSelBoxes* pMerged, SwUndo* pUndo, + const bool bDelMakeFrames, const bool bCorrBorder ) +{ + OSL_ENSURE( pDoc, "No doc?" ); + SwTableNode* pTableNd = nullptr; + if( !rBoxes.empty() ) + { + pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + } + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + if ( bDelMakeFrames ) + { + if( pMerged && !pMerged->empty() ) + aFndBox.SetTableLines( *pMerged, *this ); + else if( !rBoxes.empty() ) + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + } + + SwShareBoxFormats aShareFormats; + + // First switch the Border, then delete + if( bCorrBorder ) + { + SwSelBoxes aBoxes( rBoxes ); + for (size_t n = 0; n < aBoxes.size(); ++n) + { + ::lcl_SaveUpperLowerBorder( *this, *rBoxes[ n ], aShareFormats, + &aBoxes, &n ); + } + } + + PrepareDelBoxes( rBoxes ); + + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + // Delete boxes from last to first + for (size_t n = 0; n < rBoxes.size(); ++n) + { + size_t const nIdx = rBoxes.size() - 1 - n; + + // First adapt the data-sequence for chart if necessary + // (needed to move the implementation cursor properly to its new + // position which can't be done properly if the cell is already gone) + if (pPCD && pTableNd) + pPCD->DeleteBox( &pTableNd->GetTable(), *rBoxes[nIdx] ); + + // ... then delete the boxes + DeleteBox_( *this, rBoxes[nIdx], pUndo, true, bCorrBorder, &aShareFormats ); + } + + // then clean up the structure of all Lines + GCLines(); + + if( bDelMakeFrames && aFndBox.AreLinesToRestore( *this ) ) + aFndBox.MakeFrames( *this ); + + // TL_CHART2: now inform chart that sth has changed + pDoc->UpdateCharts( GetFrameFormat()->GetName() ); + + CHECKTABLELAYOUT; + CHECK_TABLE( *this ); + + return true; +} + +bool SwTable::OldSplitRow( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "No valid values" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + // If the rows should get the same (min) height, we first have + // to store the old row heights before deleting the frames + std::unique_ptr<long[]> pRowHeights; + if ( bSameHeight ) + { + pRowHeights.reset(new long[ rBoxes.size() ]); + for (size_t n = 0; n < rBoxes.size(); ++n) + { + SwTableBox* pSelBox = rBoxes[n]; + const SwRowFrame* pRow = GetRowFrame( *pSelBox->GetUpper() ); + OSL_ENSURE( pRow, "Where is the SwTableLine's Frame?" ); + SwRectFnSet aRectFnSet(pRow); + pRowHeights[ n ] = aRectFnSet.GetHeight(pRow->getFrameArea()); + } + } + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( rBoxes, *this ); + aFndBox.DelFrames( *this ); + + for (size_t n = 0; n < rBoxes.size(); ++n) + { + SwTableBox* pSelBox = rBoxes[n]; + OSL_ENSURE( pSelBox, "Box is not within the Table" ); + + // Insert nCnt new Lines into the Box + SwTableLine* pInsLine = pSelBox->GetUpper(); + SwTableBoxFormat* pFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + + // Respect the Line's height, reset if needed + SwFormatFrameSize aFSz( pInsLine->GetFrameFormat()->GetFrameSize() ); + if ( bSameHeight && SwFrameSize::Variable == aFSz.GetHeightSizeType() ) + aFSz.SetHeightSizeType( SwFrameSize::Minimum ); + + bool bChgLineSz = 0 != aFSz.GetHeight() || bSameHeight; + if ( bChgLineSz ) + aFSz.SetHeight( ( bSameHeight ? pRowHeights[ n ] : aFSz.GetHeight() ) / + (nCnt + 1) ); + + SwTableBox* pNewBox = new SwTableBox( pFrameFormat, nCnt, pInsLine ); + sal_uInt16 nBoxPos = pInsLine->GetBoxPos( pSelBox ); + pInsLine->GetTabBoxes()[nBoxPos] = pNewBox; // overwrite old one + + // Delete background/border attribute + SwTableBox* pLastBox = pSelBox; // To distribute the TextNodes! + // If Areas are contained in the Box, it stays as is + // !! If this is changed we need to adapt the Undo, too !!! + bool bMoveNodes = true; + { + sal_uLong nSttNd = pLastBox->GetSttIdx() + 1, + nEndNd = pLastBox->GetSttNd()->EndOfSectionIndex(); + while( nSttNd < nEndNd ) + if( !pDoc->GetNodes()[ nSttNd++ ]->IsTextNode() ) + { + bMoveNodes = false; + break; + } + } + + SwTableBoxFormat* pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + bool bChkBorder = nullptr != pCpyBoxFrameFormat->GetBox().GetTop(); + if( bChkBorder ) + pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->ClaimFrameFormat()); + + for( sal_uInt16 i = 0; i <= nCnt; ++i ) + { + // Create a new Line in the new Box + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(pInsLine->GetFrameFormat()), 1, pNewBox ); + if( bChgLineSz ) + { + pNewLine->ClaimFrameFormat()->SetFormatAttr( aFSz ); + } + + pNewBox->GetTabLines().insert( pNewBox->GetTabLines().begin() + i, pNewLine ); + // then a new Box in the Line + if( !i ) // hang up the original Box + { + pSelBox->SetUpper( pNewLine ); + pNewLine->GetTabBoxes().insert( pNewLine->GetTabBoxes().begin(), pSelBox ); + } + else + { + ::InsTableBox( pDoc, pTableNd, pNewLine, pCpyBoxFrameFormat, + pLastBox, 0 ); + + if( bChkBorder ) + { + pCpyBoxFrameFormat = static_cast<SwTableBoxFormat*>(pNewLine->GetTabBoxes()[ 0 ]->ClaimFrameFormat()); + SvxBoxItem aTmp( pCpyBoxFrameFormat->GetBox() ); + aTmp.SetLine( nullptr, SvxBoxItemLine::TOP ); + pCpyBoxFrameFormat->SetFormatAttr( aTmp ); + bChkBorder = false; + } + + if( bMoveNodes ) + { + const SwNode* pEndNd = pLastBox->GetSttNd()->EndOfSectionNode(); + if( pLastBox->GetSttIdx()+2 != pEndNd->GetIndex() ) + { + // Move TextNodes + SwNodeRange aRg( *pLastBox->GetSttNd(), +2, *pEndNd ); + pLastBox = pNewLine->GetTabBoxes()[0]; // reset + SwNodeIndex aInsPos( *pLastBox->GetSttNd(), 1 ); + pDoc->GetNodes().MoveNodes(aRg, pDoc->GetNodes(), aInsPos, false); + pDoc->GetNodes().Delete( aInsPos ); // delete the empty one + } + } + } + } + // In Boxes with Lines, we can only have Size/Fillorder + pFrameFormat = static_cast<SwTableBoxFormat*>(pNewBox->ClaimFrameFormat()); + pFrameFormat->ResetFormatAttr( RES_LR_SPACE, RES_FRMATR_END - 1 ); + pFrameFormat->ResetFormatAttr( RES_BOXATR_BEGIN, RES_BOXATR_END - 1 ); + } + + pRowHeights.reset(); + + GCLines(); + + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + return true; +} + +bool SwTable::SplitCol( SwDoc* pDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pDoc && !rBoxes.empty() && nCnt, "No valid values" ); + SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode()); + if( !pTableNd ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + SwSelBoxes aSelBoxes(rBoxes); + ExpandSelection( aSelBoxes ); + + // Find Lines for the Layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( aSelBoxes, *this ); + aFndBox.DelFrames( *this ); + + CpyTabFrames aFrameArr; + std::vector<SwTableBoxFormat*> aLastBoxArr; + for (size_t n = 0; n < aSelBoxes.size(); ++n) + { + SwTableBox* pSelBox = aSelBoxes[n]; + OSL_ENSURE( pSelBox, "Box is not in the table" ); + + // We don't want to split small table cells into very very small cells + if( pSelBox->GetFrameFormat()->GetFrameSize().GetWidth()/( nCnt + 1 ) < 10 ) + continue; + + // Then split the nCnt Box up into nCnt Boxes + SwTableLine* pInsLine = pSelBox->GetUpper(); + sal_uInt16 nBoxPos = pInsLine->GetBoxPos( pSelBox ); + + // Find the Frame Format in the Frame Format Array + SwTableBoxFormat* pLastBoxFormat; + CpyTabFrame aFindFrame( static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()) ); + CpyTabFrames::const_iterator itFind = aFrameArr.lower_bound( aFindFrame ); + const size_t nFndPos = itFind - aFrameArr.begin(); + if( itFind == aFrameArr.end() || !(*itFind == aFindFrame) ) + { + // Change the FrameFormat + aFindFrame.pNewFrameFormat = static_cast<SwTableBoxFormat*>(pSelBox->ClaimFrameFormat()); + SwTwips nBoxSz = aFindFrame.pNewFrameFormat->GetFrameSize().GetWidth(); + SwTwips nNewBoxSz = nBoxSz / ( nCnt + 1 ); + aFindFrame.pNewFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + nNewBoxSz, 0 ) ); + aFrameArr.insert( aFindFrame ); + + pLastBoxFormat = aFindFrame.pNewFrameFormat; + if( nBoxSz != ( nNewBoxSz * (nCnt + 1))) + { + // We have a remainder, so we need to define an own Format + // for the last Box. + pLastBoxFormat = new SwTableBoxFormat( *aFindFrame.pNewFrameFormat ); + pLastBoxFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, + nBoxSz - ( nNewBoxSz * nCnt ), 0 ) ); + } + aLastBoxArr.insert( aLastBoxArr.begin() + nFndPos, pLastBoxFormat ); + } + else + { + aFindFrame = aFrameArr[ nFndPos ]; + pSelBox->ChgFrameFormat( aFindFrame.pNewFrameFormat ); + pLastBoxFormat = aLastBoxArr[ nFndPos ]; + } + + // Insert the Boxes at the Position + for( sal_uInt16 i = 1; i < nCnt; ++i ) + ::InsTableBox( pDoc, pTableNd, pInsLine, aFindFrame.pNewFrameFormat, + pSelBox, nBoxPos + i ); // insert after + + ::InsTableBox( pDoc, pTableNd, pInsLine, pLastBoxFormat, + pSelBox, nBoxPos + nCnt ); // insert after + + // Special treatment for the Border: + const SvxBoxItem& aSelBoxItem = aFindFrame.pNewFrameFormat->GetBox(); + if( aSelBoxItem.GetRight() ) + { + pInsLine->GetTabBoxes()[ nBoxPos + nCnt ]->ClaimFrameFormat(); + + SvxBoxItem aTmp( aSelBoxItem ); + aTmp.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + aFindFrame.pNewFrameFormat->SetFormatAttr( aTmp ); + + // Remove the Format from the "cache" + for( auto i = aFrameArr.size(); i; ) + { + const CpyTabFrame& rCTF = aFrameArr[ --i ]; + if( rCTF.pNewFrameFormat == aFindFrame.pNewFrameFormat || + rCTF.pFrameFormat == aFindFrame.pNewFrameFormat ) + { + aFrameArr.erase( aFrameArr.begin() + i ); + aLastBoxArr.erase( aLastBoxArr.begin() + i ); + } + } + } + } + + // Update Layout + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + return true; +} + +/* + * >> MERGE << + * Algorithm: + * If we only have one Line in the FndBox_, take this Line and test + * the Box count: + * If we have more than one Box, we merge on Box level, meaning + * the new Box will be as wide as the old ones. + * All Lines that are above/under the Area, are inserted into + * the Box as Line + Box. + * All Lines that come before/after the Area, are inserted into + * the Boxes Left/Right. + * + * >> MERGE << + */ +static void lcl_CpyLines( sal_uInt16 nStt, sal_uInt16 nEnd, + SwTableLines& rLines, + SwTableBox* pInsBox, + sal_uInt16 nPos = USHRT_MAX ) +{ + for( sal_uInt16 n = nStt; n < nEnd; ++n ) + rLines[n]->SetUpper( pInsBox ); + if( USHRT_MAX == nPos ) + nPos = pInsBox->GetTabLines().size(); + pInsBox->GetTabLines().insert( pInsBox->GetTabLines().begin() + nPos, + rLines.begin() + nStt, rLines.begin() + nEnd ); + rLines.erase( rLines.begin() + nStt, rLines.begin() + nEnd ); +} + +static void lcl_CpyBoxes( sal_uInt16 nStt, sal_uInt16 nEnd, + SwTableBoxes& rBoxes, + SwTableLine* pInsLine ) +{ + for( sal_uInt16 n = nStt; n < nEnd; ++n ) + rBoxes[n]->SetUpper( pInsLine ); + sal_uInt16 nPos = pInsLine->GetTabBoxes().size(); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + nPos, + rBoxes.begin() + nStt, rBoxes.begin() + nEnd ); + rBoxes.erase( rBoxes.begin() + nStt, rBoxes.begin() + nEnd ); +} + +static void lcl_CalcWidth( SwTableBox* pBox ) +{ + // Assertion: Every Line in the Box is as large + SwFrameFormat* pFormat = pBox->ClaimFrameFormat(); + OSL_ENSURE( pBox->GetTabLines().size(), "Box does not have any Lines" ); + + SwTableLine* pLine = pBox->GetTabLines()[0]; + OSL_ENSURE( pLine, "Box is not within a Line" ); + + long nWidth = 0; + for( auto pTabBox : pLine->GetTabBoxes() ) + nWidth += pTabBox->GetFrameFormat()->GetFrameSize().GetWidth(); + + pFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Variable, nWidth, 0 )); + + // Boxes with Lines can only have Size/Fillorder + pFormat->ResetFormatAttr( RES_LR_SPACE, RES_FRMATR_END - 1 ); + pFormat->ResetFormatAttr( RES_BOXATR_BEGIN, RES_BOXATR_END - 1 ); +} + +namespace { + +struct InsULPara +{ + SwTableNode* pTableNd; + SwTableLine* pInsLine; + SwTableBox* pInsBox; + bool bUL_LR : 1; // Upper-Lower(true) or Left-Right(false) ? + bool bUL : 1; // Upper-Left(true) or Lower-Right(false) ? + + SwTableBox* pLeftBox; + + InsULPara( SwTableNode* pTNd, + SwTableBox* pLeft, + SwTableLine* pLine ) + : pTableNd( pTNd ), pInsLine( pLine ), pInsBox( nullptr ), + pLeftBox( pLeft ) + { bUL_LR = true; bUL = true; } + + void SetLeft( SwTableBox* pBox ) + { bUL_LR = false; bUL = true; if( pBox ) pInsBox = pBox; } + void SetRight( SwTableBox* pBox ) + { bUL_LR = false; bUL = false; if( pBox ) pInsBox = pBox; } + void SetLower( SwTableLine* pLine ) + { bUL_LR = true; bUL = false; if( pLine ) pInsLine = pLine; } +}; + +} + +static void lcl_Merge_MoveLine(FndLine_ & rFndLine, InsULPara *const pULPara); + +static void lcl_Merge_MoveBox(FndBox_ & rFndBox, InsULPara *const pULPara) +{ + SwTableBoxes* pBoxes; + + sal_uInt16 nStt = 0, nEnd = rFndBox.GetLines().size(); + sal_uInt16 nInsPos = USHRT_MAX; + if( !pULPara->bUL_LR ) // Left/Right + { + sal_uInt16 nPos; + SwTableBox* pFndTableBox = rFndBox.GetBox(); + pBoxes = &pFndTableBox->GetUpper()->GetTabBoxes(); + if( pULPara->bUL ) // Left ? + { + // if there are Boxes before it, move them + if( 0 != ( nPos = pFndTableBox->GetUpper()->GetBoxPos( pFndTableBox ) ) ) + lcl_CpyBoxes( 0, nPos, *pBoxes, pULPara->pInsLine ); + } + else // Right + { + // if there are Boxes behind it, move them + nPos = pFndTableBox->GetUpper()->GetBoxPos( pFndTableBox ); + if( nPos +1 < static_cast<sal_uInt16>(pBoxes->size()) ) + { + nInsPos = pULPara->pInsLine->GetTabBoxes().size(); + lcl_CpyBoxes( nPos+1, pBoxes->size(), + *pBoxes, pULPara->pInsLine ); + } + } + } + // Upper/Lower and still deeper? + else if (!rFndBox.GetLines().empty()) + { + // Only search the Line from which we need to move + nStt = pULPara->bUL ? 0 : rFndBox.GetLines().size()-1; + nEnd = nStt+1; + } + + pBoxes = &pULPara->pInsLine->GetTabBoxes(); + + // Is there still a level to step down to? + if (!rFndBox.GetBox()->GetTabLines().empty()) + { + SwTableBox* pBox = new SwTableBox( + static_cast<SwTableBoxFormat*>(rFndBox.GetBox()->GetFrameFormat()), + 0, pULPara->pInsLine ); + InsULPara aPara( *pULPara ); + aPara.pInsBox = pBox; + for (FndLines_t::iterator it = rFndBox.GetLines().begin() + nStt; + it != rFndBox.GetLines().begin() + nEnd; ++it ) + { + lcl_Merge_MoveLine(**it, &aPara); + } + if( !pBox->GetTabLines().empty() ) + { + if( USHRT_MAX == nInsPos ) + nInsPos = pBoxes->size(); + pBoxes->insert( pBoxes->begin() + nInsPos, pBox ); + lcl_CalcWidth( pBox ); // calculate the Box's width + } + else + delete pBox; + } +} + +static void lcl_Merge_MoveLine(FndLine_& rFndLine, InsULPara *const pULPara) +{ + SwTableLines* pLines; + + sal_uInt16 nStt = 0, nEnd = rFndLine.GetBoxes().size(); + sal_uInt16 nInsPos = USHRT_MAX; + if( pULPara->bUL_LR ) // UpperLower ? + { + sal_uInt16 nPos; + SwTableLine* pFndLn = rFndLine.GetLine(); + pLines = pFndLn->GetUpper() ? + &pFndLn->GetUpper()->GetTabLines() : + &pULPara->pTableNd->GetTable().GetTabLines(); + + SwTableBox* pLBx = rFndLine.GetBoxes().front()->GetBox(); + SwTableBox* pRBx = rFndLine.GetBoxes().back()->GetBox(); + sal_uInt16 nLeft = pFndLn->GetBoxPos( pLBx ); + sal_uInt16 nRight = pFndLn->GetBoxPos( pRBx ); + + if( !nLeft || nRight == pFndLn->GetTabBoxes().size() ) + { + if( pULPara->bUL ) // Upper ? + { + // If there are Lines before it, move them + nPos = pLines->GetPos( pFndLn ); + if( 0 != nPos ) + lcl_CpyLines( 0, nPos, *pLines, pULPara->pInsBox ); + } + else + // If there are Lines after it, move them + if( (nPos = pLines->GetPos( pFndLn )) + 1 < static_cast<sal_uInt16>(pLines->size()) ) + { + nInsPos = pULPara->pInsBox->GetTabLines().size(); + lcl_CpyLines( nPos+1, pLines->size(), *pLines, + pULPara->pInsBox ); + } + } + else + { + // There are still Boxes on the left side, so put the Left- + // and Merge-Box into one Box and Line, insert before/after + // a Line with a Box, into which the upper/lower Lines are + // inserted + SwTableLine* pInsLine = pULPara->pLeftBox->GetUpper(); + SwTableBox* pLMBox = new SwTableBox( + static_cast<SwTableBoxFormat*>(pULPara->pLeftBox->GetFrameFormat()), 0, pInsLine ); + SwTableLine* pLMLn = new SwTableLine( + static_cast<SwTableLineFormat*>(pInsLine->GetFrameFormat()), 2, pLMBox ); + pLMLn->ClaimFrameFormat()->ResetFormatAttr( RES_FRM_SIZE ); + + pLMBox->GetTabLines().insert( pLMBox->GetTabLines().begin(), pLMLn ); + + lcl_CpyBoxes( 0, 2, pInsLine->GetTabBoxes(), pLMLn ); + + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin(), pLMBox ); + + if( pULPara->bUL ) // Upper ? + { + // If there are Lines before it, move them + nPos = pLines->GetPos( pFndLn ); + if( 0 != nPos ) + lcl_CpyLines( 0, nPos, *pLines, pLMBox, 0 ); + } + else + // If there are Lines after it, move them + if( (nPos = pLines->GetPos( pFndLn )) + 1 < static_cast<sal_uInt16>(pLines->size()) ) + lcl_CpyLines( nPos+1, pLines->size(), *pLines, + pLMBox ); + lcl_CalcWidth( pLMBox ); // calculate the Box's width + } + } + // Left/Right + else + { + // Find only the Line from which we need to move + nStt = pULPara->bUL ? 0 : rFndLine.GetBoxes().size()-1; + nEnd = nStt+1; + } + pLines = &pULPara->pInsBox->GetTabLines(); + + SwTableLine* pNewLine = new SwTableLine( + static_cast<SwTableLineFormat*>(rFndLine.GetLine()->GetFrameFormat()), 0, pULPara->pInsBox ); + InsULPara aPara( *pULPara ); // copying + aPara.pInsLine = pNewLine; + FndBoxes_t & rLineBoxes = rFndLine.GetBoxes(); + for (FndBoxes_t::iterator it = rLineBoxes.begin() + nStt; + it != rLineBoxes.begin() + nEnd; ++it) + { + lcl_Merge_MoveBox(**it, &aPara); + } + + if( !pNewLine->GetTabBoxes().empty() ) + { + if( USHRT_MAX == nInsPos ) + nInsPos = pLines->size(); + pLines->insert( pLines->begin() + nInsPos, pNewLine ); + } + else + delete pNewLine; +} + +static void lcl_BoxSetHeadCondColl( const SwTableBox* pBox ); + +bool SwTable::OldMerge( SwDoc* pDoc, const SwSelBoxes& rBoxes, + SwTableBox* pMergeBox, SwUndoTableMerge* pUndo ) +{ + OSL_ENSURE( !rBoxes.empty() && pMergeBox, "no valid values" ); + 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( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // TL_CHART2: splitting/merging of a number of cells or rows will usually make + // the table too complex to be handled with chart. + // Thus we tell the charts to use their own data provider and forget about this table + pDoc->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( this ); + + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + if( pUndo ) + pUndo->SetSelBoxes( rBoxes ); + + // Find Lines for the Layout update + aFndBox.SetTableLines( *this ); + aFndBox.DelFrames( *this ); + + FndBox_* pFndBox = &aFndBox; + while( 1 == pFndBox->GetLines().size() && + 1 == pFndBox->GetLines().front()->GetBoxes().size() ) + { + pFndBox = pFndBox->GetLines().front()->GetBoxes().front().get(); + } + + SwTableLine* pInsLine = new SwTableLine( + static_cast<SwTableLineFormat*>(pFndBox->GetLines().front()->GetLine()->GetFrameFormat()), 0, + !pFndBox->GetUpper() ? nullptr : pFndBox->GetBox() ); + pInsLine->ClaimFrameFormat()->ResetFormatAttr( RES_FRM_SIZE ); + + // Add the new Line + SwTableLines* pLines = pFndBox->GetUpper() ? + &pFndBox->GetBox()->GetTabLines() : &GetTabLines(); + + SwTableLine* pNewLine = pFndBox->GetLines().front()->GetLine(); + sal_uInt16 nInsPos = pLines->GetPos( pNewLine ); + pLines->insert( pLines->begin() + nInsPos, pInsLine ); + + SwTableBox* pLeftBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pMergeBox->GetFrameFormat()), 0, pInsLine ); + SwTableBox* pRightBox = new SwTableBox( static_cast<SwTableBoxFormat*>(pMergeBox->GetFrameFormat()), 0, pInsLine ); + pMergeBox->SetUpper( pInsLine ); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin(), pLeftBox ); + pLeftBox->ClaimFrameFormat(); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + 1, pMergeBox); + pInsLine->GetTabBoxes().insert( pInsLine->GetTabBoxes().begin() + 2, pRightBox ); + pRightBox->ClaimFrameFormat(); + + // This contains all Lines that are above the selected Area, + // thus they form a Upper/Lower Line + InsULPara aPara( pTableNd, pLeftBox, pInsLine ); + + // Move the overlapping upper/lower Lines of the selected Area + for (auto & it : pFndBox->GetLines().front()->GetBoxes()) + { + lcl_Merge_MoveBox(*it, &aPara); + } + aPara.SetLower( pInsLine ); + const auto nEnd = pFndBox->GetLines().size()-1; + for (auto & it : pFndBox->GetLines()[nEnd]->GetBoxes()) + { + lcl_Merge_MoveBox(*it, &aPara); + } + + // Move the Boxes extending into the selected Area from left/right + aPara.SetLeft( pLeftBox ); + for (auto & rpFndLine : pFndBox->GetLines()) + { + lcl_Merge_MoveLine( *rpFndLine, &aPara ); + } + + aPara.SetRight( pRightBox ); + for (auto & rpFndLine : pFndBox->GetLines()) + { + lcl_Merge_MoveLine( *rpFndLine, &aPara ); + } + + if( pLeftBox->GetTabLines().empty() ) + DeleteBox_( *this, pLeftBox, nullptr, false, false ); + else + { + lcl_CalcWidth( pLeftBox ); // calculate the Box's width + if( pUndo && pLeftBox->GetSttNd() ) + pUndo->AddNewBox( pLeftBox->GetSttIdx() ); + } + if( pRightBox->GetTabLines().empty() ) + DeleteBox_( *this, pRightBox, nullptr, false, false ); + else + { + lcl_CalcWidth( pRightBox ); // calculate the Box's width + if( pUndo && pRightBox->GetSttNd() ) + pUndo->AddNewBox( pRightBox->GetSttIdx() ); + } + + DeleteSel( pDoc, rBoxes, nullptr, nullptr, false, false ); + + // Clean up this Line's structure once again, generally all of them + GCLines(); + + for( const auto& rpBox : GetTabLines()[0]->GetTabBoxes() ) + lcl_BoxSetHeadCondColl(rpBox); + + aFndBox.MakeFrames( *this ); + + CHECKBOXWIDTH + CHECKTABLELAYOUT + + return true; +} + +static void lcl_CheckRowSpan( SwTable &rTable ) +{ + const long nLineCount = static_cast<long>(rTable.GetTabLines().size()); + long nMaxSpan = nLineCount; + long nMinSpan = 1; + while( nMaxSpan ) + { + SwTableLine* pLine = rTable.GetTabLines()[ nLineCount - nMaxSpan ]; + for( auto pBox : pLine->GetTabBoxes() ) + { + long nRowSpan = pBox->getRowSpan(); + if( nRowSpan > nMaxSpan ) + pBox->setRowSpan( nMaxSpan ); + else if( nRowSpan < nMinSpan ) + pBox->setRowSpan( nMinSpan > 0 ? nMaxSpan : nMinSpan ); + } + --nMaxSpan; + nMinSpan = -nMaxSpan; + } +} + +static sal_uInt16 lcl_GetBoxOffset( const FndBox_& rBox ) +{ + // Find the first Box + const FndBox_* pFirstBox = &rBox; + while (!pFirstBox->GetLines().empty()) + { + pFirstBox = pFirstBox->GetLines().front()->GetBoxes().front().get(); + } + + sal_uInt16 nRet = 0; + // Calculate the position relative to above via the Lines + const SwTableBox* pBox = pFirstBox->GetBox(); + do { + const SwTableBoxes& rBoxes = pBox->GetUpper()->GetTabBoxes(); + for( auto pCmp : rBoxes ) + { + if (pBox==pCmp) + break; + nRet = nRet + static_cast<sal_uInt16>(pCmp->GetFrameFormat()->GetFrameSize().GetWidth()); + } + pBox = pBox->GetUpper()->GetUpper(); + } while( pBox ); + return nRet; +} + +static sal_uInt16 lcl_GetLineWidth( const FndLine_& rLine ) +{ + sal_uInt16 nRet = 0; + for( auto n = rLine.GetBoxes().size(); n; ) + { + nRet = nRet + static_cast<sal_uInt16>(rLine.GetBoxes()[--n]->GetBox() + ->GetFrameFormat()->GetFrameSize().GetWidth()); + } + return nRet; +} + +static void lcl_CalcNewWidths(const FndLines_t& rFndLines, CpyPara& rPara) +{ + rPara.pWidths.reset(); + const size_t nLineCount = rFndLines.size(); + if( nLineCount ) + { + rPara.pWidths = std::make_shared< std::vector< std::vector< sal_uLong > > > + ( nLineCount ); + // First we collect information about the left/right borders of all + // selected cells + for( size_t nLine = 0; nLine < nLineCount; ++nLine ) + { + std::vector< sal_uLong > &rWidth = (*rPara.pWidths)[ nLine ]; + const FndLine_ *pFndLine = rFndLines[ nLine ].get(); + if( pFndLine && !pFndLine->GetBoxes().empty() ) + { + const SwTableLine *pLine = pFndLine->GetLine(); + if( pLine && !pLine->GetTabBoxes().empty() ) + { + size_t nBoxCount = pLine->GetTabBoxes().size(); + sal_uLong nPos = 0; + // The first selected box... + const SwTableBox *const pSel = + pFndLine->GetBoxes().front()->GetBox(); + size_t nBox = 0; + // Sum up the width of all boxes before the first selected box + while( nBox < nBoxCount ) + { + SwTableBox* pBox = pLine->GetTabBoxes()[nBox++]; + if( pBox != pSel ) + nPos += pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + else + break; + } + // nPos is now the left border of the first selected box + if( rPara.nMinLeft > nPos ) + rPara.nMinLeft = nPos; + nBoxCount = pFndLine->GetBoxes().size(); + rWidth = std::vector< sal_uLong >( nBoxCount+2 ); + rWidth[ 0 ] = nPos; + // Add now the widths of all selected boxes and store + // the positions in the vector + for( nBox = 0; nBox < nBoxCount; ) + { + nPos += pFndLine->GetBoxes()[nBox] + ->GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + rWidth[ ++nBox ] = nPos; + } + // nPos: The right border of the last selected box + if( rPara.nMaxRight < nPos ) + rPara.nMaxRight = nPos; + if( nPos <= rWidth[ 0 ] ) + rWidth.clear(); + } + } + } + } + // Second step: calculate the new widths for the copied cells + sal_uLong nSelSize = rPara.nMaxRight - rPara.nMinLeft; + if( nSelSize ) + { + for( size_t nLine = 0; nLine < nLineCount; ++nLine ) + { + std::vector< sal_uLong > &rWidth = (*rPara.pWidths)[ nLine ]; + const size_t nCount = rWidth.size(); + if( nCount > 2 ) + { + rWidth[ nCount - 1 ] = rPara.nMaxRight; + sal_uLong nLastPos = 0; + for( size_t nBox = 0; nBox < nCount; ++nBox ) + { + sal_uInt64 nNextPos = rWidth[ nBox ]; + nNextPos -= rPara.nMinLeft; + nNextPos *= rPara.nNewSize; + nNextPos /= nSelSize; + rWidth[ nBox ] = static_cast<sal_uLong>(nNextPos - nLastPos); + nLastPos = static_cast<sal_uLong>(nNextPos); + } + } + } + } +} + +static void +lcl_CopyLineToDoc(FndLine_ const& rpFndLn, CpyPara *const pCpyPara); + +static void lcl_CopyBoxToDoc(FndBox_ const& rFndBox, CpyPara *const pCpyPara) +{ + // Calculation of new size + sal_uLong nRealSize; + sal_uLong nDummy1 = 0; + sal_uLong nDummy2 = 0; + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + { + if( pCpyPara->nBoxIdx == 1 ) + nDummy1 = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][0]; + nRealSize = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][pCpyPara->nBoxIdx++]; + if( pCpyPara->nBoxIdx == (*pCpyPara->pWidths)[pCpyPara->nLnIdx].size()-1 ) + nDummy2 = (*pCpyPara->pWidths)[pCpyPara->nLnIdx][pCpyPara->nBoxIdx]; + } + else + { + nRealSize = pCpyPara->nNewSize; + nRealSize *= rFndBox.GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + if (pCpyPara->nOldSize == 0) + throw o3tl::divide_by_zero(); + nRealSize /= pCpyPara->nOldSize; + } + + sal_uLong nSize; + bool bDummy = nDummy1 > 0; + if( bDummy ) + nSize = nDummy1; + else + { + nSize = nRealSize; + nRealSize = 0; + } + do + { + // Find the Frame Format in the list of all Frame Formats + CpyTabFrame aFindFrame(static_cast<SwTableBoxFormat*>(rFndBox.GetBox()->GetFrameFormat())); + + std::shared_ptr<SwFormatFrameSize> aFrameSz(std::make_shared<SwFormatFrameSize>()); + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.lower_bound( aFindFrame ); + const CpyTabFrames::size_type nFndPos = itFind - pCpyPara->rTabFrameArr.begin(); + + // It *is* sometimes cool to have multiple tests/if's and assignments + // in a single statement, and it is technically possible. But it is definitely + // not simply readable - where from my POV reading code is done 1000 times + // more often than writing it. Thus I dismantled the expression in smaller + // chunks to keep it handy/understandable/changeable (hopefully without error) + // The original for reference: + // if( itFind == pCpyPara->rTabFrameArr.end() || !(*itFind == aFindFrame) || + // ( aFrameSz = ( aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]).pNewFrameFormat-> + // GetFrameSize()).GetWidth() != static_cast<SwTwips>(nSize) ) + + bool DoCopyIt(itFind == pCpyPara->rTabFrameArr.end()); + + if(!DoCopyIt) + { + DoCopyIt = !(*itFind == aFindFrame); + } + + if(!DoCopyIt) + { + aFindFrame = pCpyPara->rTabFrameArr[ nFndPos ]; + aFrameSz.reset(aFindFrame.pNewFrameFormat->GetFrameSize().Clone()); + DoCopyIt = aFrameSz->GetWidth() != static_cast<SwTwips>(nSize); + } + + if(DoCopyIt) + { + // It doesn't exist yet, so copy it + aFindFrame.pNewFrameFormat = pCpyPara->pDoc->MakeTableBoxFormat(); + aFindFrame.pNewFrameFormat->CopyAttrs( *rFndBox.GetBox()->GetFrameFormat() ); + if( !pCpyPara->bCpyContent ) + aFindFrame.pNewFrameFormat->ResetFormatAttr( RES_BOXATR_FORMULA, RES_BOXATR_VALUE ); + aFrameSz->SetWidth( nSize ); + aFindFrame.pNewFrameFormat->SetFormatAttr( *aFrameSz ); + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + + SwTableBox* pBox; + if (!rFndBox.GetLines().empty()) + { + pBox = new SwTableBox( aFindFrame.pNewFrameFormat, + rFndBox.GetLines().size(), pCpyPara->pInsLine ); + pCpyPara->pInsLine->GetTabBoxes().insert( pCpyPara->pInsLine->GetTabBoxes().begin() + pCpyPara->nInsPos++, pBox ); + CpyPara aPara( *pCpyPara, pBox ); + aPara.nNewSize = nSize; // get the size + for (auto const& rpFndLine : rFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + } + else + { + // Create an empty Box + pCpyPara->pDoc->GetNodes().InsBoxen( pCpyPara->pTableNd, pCpyPara->pInsLine, + aFindFrame.pNewFrameFormat, + pCpyPara->pDoc->GetDfltTextFormatColl(), + nullptr, pCpyPara->nInsPos ); + pBox = pCpyPara->pInsLine->GetTabBoxes()[ pCpyPara->nInsPos ]; + if( bDummy ) + pBox->setDummyFlag( true ); + else if( pCpyPara->bCpyContent ) + { + // Copy the content into this empty Box + pBox->setRowSpan(rFndBox.GetBox()->getRowSpan()); + + // We can also copy formulas and values, if we copy the content + { + SfxItemSet aBoxAttrSet( pCpyPara->pDoc->GetAttrPool(), + svl::Items<RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{} ); + aBoxAttrSet.Put(rFndBox.GetBox()->GetFrameFormat()->GetAttrSet()); + if( aBoxAttrSet.Count() ) + { + const SfxPoolItem* pItem; + SvNumberFormatter* pN = pCpyPara->pDoc->GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && SfxItemState::SET == aBoxAttrSet. + GetItemState( RES_BOXATR_FORMAT, false, &pItem ) ) + { + sal_uLong nOldIdx = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue(); + sal_uLong nNewIdx = pN->GetMergeFormatIndex( nOldIdx ); + if( nNewIdx != nOldIdx ) + aBoxAttrSet.Put( SwTableBoxNumFormat( nNewIdx )); + } + pBox->ClaimFrameFormat()->SetFormatAttr( aBoxAttrSet ); + } + } + SwDoc* pFromDoc = rFndBox.GetBox()->GetFrameFormat()->GetDoc(); + SwNodeRange aCpyRg( *rFndBox.GetBox()->GetSttNd(), 1, + *rFndBox.GetBox()->GetSttNd()->EndOfSectionNode() ); + SwNodeIndex aInsIdx( *pBox->GetSttNd(), 1 ); + + pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aCpyRg, aInsIdx, nullptr, false); + // Delete the initial TextNode + pCpyPara->pDoc->GetNodes().Delete( aInsIdx ); + } + ++pCpyPara->nInsPos; + } + if( nRealSize ) + { + bDummy = false; + nSize = nRealSize; + nRealSize = 0; + } + else + { + bDummy = true; + nSize = nDummy2; + nDummy2 = 0; + } + } + while( nSize ); +} + +static void +lcl_CopyLineToDoc(const FndLine_& rFndLine, CpyPara *const pCpyPara) +{ + // Find the Frame Format in the list of all Frame Formats + CpyTabFrame aFindFrame( rFndLine.GetLine()->GetFrameFormat() ); + CpyTabFrames::const_iterator itFind = pCpyPara->rTabFrameArr.find( aFindFrame ); + if( itFind == pCpyPara->rTabFrameArr.end() ) + { + // It doesn't exist yet, so copy it + aFindFrame.pNewFrameFormat = reinterpret_cast<SwTableBoxFormat*>(pCpyPara->pDoc->MakeTableLineFormat()); + aFindFrame.pNewFrameFormat->CopyAttrs( *rFndLine.GetLine()->GetFrameFormat() ); + pCpyPara->rTabFrameArr.insert( aFindFrame ); + } + else + aFindFrame = *itFind; + + SwTableLine* pNewLine = new SwTableLine( reinterpret_cast<SwTableLineFormat*>(aFindFrame.pNewFrameFormat), + rFndLine.GetBoxes().size(), pCpyPara->pInsBox ); + if( pCpyPara->pInsBox ) + { + SwTableLines& rLines = pCpyPara->pInsBox->GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine ); + } + else + { + SwTableLines& rLines = pCpyPara->pTableNd->GetTable().GetTabLines(); + rLines.insert( rLines.begin() + pCpyPara->nInsPos++, pNewLine); + } + + CpyPara aPara( *pCpyPara, pNewLine ); + + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + { + aPara.nOldSize = 0; // will not be used + aPara.nBoxIdx = 1; + } + else if( rFndLine.GetBoxes().size() == + rFndLine.GetLine()->GetTabBoxes().size() ) + { + // Get the Parent's size + const SwFrameFormat* pFormat; + + if( rFndLine.GetLine()->GetUpper() ) + pFormat = rFndLine.GetLine()->GetUpper()->GetFrameFormat(); + else + pFormat = pCpyPara->pTableNd->GetTable().GetFrameFormat(); + aPara.nOldSize = pFormat->GetFrameSize().GetWidth(); + } + else + // Calculate it + for (auto &rpBox : rFndLine.GetBoxes()) + { + aPara.nOldSize += rpBox->GetBox()->GetFrameFormat()->GetFrameSize().GetWidth(); + } + + const FndBoxes_t& rBoxes = rFndLine.GetBoxes(); + for (auto const& it : rBoxes) + { + lcl_CopyBoxToDoc(*it, &aPara); + } + if( pCpyPara->pTableNd->GetTable().IsNewModel() ) + ++pCpyPara->nLnIdx; +} + +void SwTable::CopyHeadlineIntoTable( SwTableNode& rTableNd ) +{ + // Find all Boxes/Lines + SwSelBoxes aSelBoxes; + SwTableBox* pBox = GetTabSortBoxes()[ 0 ]; + pBox = GetTableBox( pBox->GetSttNd()->StartOfSectionNode()->GetIndex() + 1 ); + SelLineFromBox( pBox, aSelBoxes ); + + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( aSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( GetTabLines(), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return; + + { + // Convert Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( this ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + GetFrameFormat()->GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + CpyTabFrames aCpyFormat; + CpyPara aPara( &rTableNd, 1, aCpyFormat ); + aPara.nNewSize = aPara.nOldSize = rTableNd.GetTable().GetFrameFormat()->GetFrameSize().GetWidth(); + // Copy + if( IsNewModel() ) + lcl_CalcNewWidths( aFndBox.GetLines(), aPara ); + for (const auto & rpFndLine : aFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + if( rTableNd.GetTable().IsNewModel() ) + { // The copied line must not contain any row span attributes > 1 + SwTableLine* pLine = rTableNd.GetTable().GetTabLines()[0]; + OSL_ENSURE( !pLine->GetTabBoxes().empty(), "Empty Table Line" ); + for( auto pTableBox : pLine->GetTabBoxes() ) + { + OSL_ENSURE( pTableBox, "Missing Table Box" ); + pTableBox->setRowSpan( 1 ); + } + } +} + +bool SwTable::MakeCopy( SwDoc* pInsDoc, const SwPosition& rPos, + const SwSelBoxes& rSelBoxes, + bool bCpyName ) const +{ + // Find all Boxes/Lines + FndBox_ aFndBox( nullptr, nullptr ); + { + FndPara aPara( rSelBoxes, &aFndBox ); + ForEach_FndLineCopyCol( const_cast<SwTableLines&>(GetTabLines()), &aPara ); + } + if( aFndBox.GetLines().empty() ) + return false; + + // First copy the PoolTemplates for the Table, so that the Tables are + // actually copied and have valid values. + SwDoc* pSrcDoc = GetFrameFormat()->GetDoc(); + if( pSrcDoc != pInsDoc ) + { + pInsDoc->CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ) ); + pInsDoc->CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ) ); + } + + SwTable* pNewTable = const_cast<SwTable*>(pInsDoc->InsertTable( + SwInsertTableOptions( SwInsertTableFlags::HeadlineNoBorder, 1 ), + rPos, 1, 1, GetFrameFormat()->GetHoriOrient().GetHoriOrient(), + nullptr, nullptr, false, IsNewModel() )); + if( !pNewTable ) + return false; + + SwNodeIndex aIdx( rPos.nNode, -1 ); + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + ++aIdx; + OSL_ENSURE( pTableNd, "Where is the TableNode now?" ); + + pTableNd->GetTable().SetRowsToRepeat( GetRowsToRepeat() ); + + pNewTable->SetTableStyleName(pTableNd->GetTable().GetTableStyleName()); + + if( auto pSwDDETable = dynamic_cast<const SwDDETable*>(this) ) + { + // A DDE-Table is being copied + // Does the new Document actually have it's FieldType? + SwFieldType* pFieldType = pInsDoc->getIDocumentFieldsAccess().InsertFieldType( + *pSwDDETable->GetDDEFieldType() ); + OSL_ENSURE( pFieldType, "unknown FieldType" ); + + // Change the Table Pointer at the Node + pNewTable = new SwDDETable( *pNewTable, + static_cast<SwDDEFieldType*>(pFieldType) ); + pTableNd->SetNewTable( std::unique_ptr<SwTable>(pNewTable), false ); + } + + pNewTable->GetFrameFormat()->CopyAttrs( *GetFrameFormat() ); + pNewTable->SetTableChgMode( GetTableChgMode() ); + + // Destroy the already created Frames + pTableNd->DelFrames(); + + { + // Convert the Table formulas to their relative representation + SwTableFormulaUpdate aMsgHint( this ); + aMsgHint.m_eFlags = TBL_RELBOXNAME; + pSrcDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + } + + SwTableNumFormatMerge aTNFM( *pSrcDoc, *pInsDoc ); + + // Also copy Names or enforce a new unique one + if( bCpyName ) + pNewTable->GetFrameFormat()->SetName( GetFrameFormat()->GetName() ); + + CpyTabFrames aCpyFormat; + CpyPara aPara( pTableNd, 1, aCpyFormat ); + aPara.nNewSize = aPara.nOldSize = GetFrameFormat()->GetFrameSize().GetWidth(); + + if( IsNewModel() ) + lcl_CalcNewWidths( aFndBox.GetLines(), aPara ); + // Copy + for (const auto & rpFndLine : aFndBox.GetLines()) + { + lcl_CopyLineToDoc( *rpFndLine, &aPara ); + } + + // Set the "right" margin above/below + { + FndLine_* pFndLn = aFndBox.GetLines().front().get(); + SwTableLine* pLn = pFndLn->GetLine(); + const SwTableLine* pTmp = pLn; + sal_uInt16 nLnPos = GetTabLines().GetPos( pTmp ); + if( USHRT_MAX != nLnPos && nLnPos ) + { + // There is a Line before it + SwCollectTableLineBoxes aLnPara( false, SplitTable_HeadlineOption::BorderCopy ); + + pLn = GetTabLines()[ nLnPos - 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox( rpBox, &aLnPara ); + + if( aLnPara.Resize( lcl_GetBoxOffset( aFndBox ), + lcl_GetLineWidth( *pFndLn )) ) + { + aLnPara.SetValues( true ); + pLn = pNewTable->GetTabLines()[ 0 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aLnPara ); + } + } + + pFndLn = aFndBox.GetLines().back().get(); + pLn = pFndLn->GetLine(); + pTmp = pLn; + nLnPos = GetTabLines().GetPos( pTmp ); + if( nLnPos < GetTabLines().size() - 1 ) + { + // There is a Line following it + SwCollectTableLineBoxes aLnPara( true, SplitTable_HeadlineOption::BorderCopy ); + + pLn = GetTabLines()[ nLnPos + 1 ]; + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_Box_CollectBox( rpBox, &aLnPara ); + + if( aLnPara.Resize( lcl_GetBoxOffset( aFndBox ), + lcl_GetLineWidth( *pFndLn )) ) + { + aLnPara.SetValues( false ); + pLn = pNewTable->GetTabLines().back(); + for( const auto& rpBox : pLn->GetTabBoxes() ) + sw_BoxSetSplitBoxFormats(rpBox, &aLnPara ); + } + } + } + + // We need to delete the initial Box + DeleteBox_( *pNewTable, pNewTable->GetTabLines().back()->GetTabBoxes()[0], + nullptr, false, false ); + + if( pNewTable->IsNewModel() ) + lcl_CheckRowSpan( *pNewTable ); + // Clean up + pNewTable->GCLines(); + + pTableNd->MakeOwnFrames( &aIdx ); // re-generate the Frames + + CHECKTABLELAYOUT + + return true; +} + +// Find the next Box with content from this Line +SwTableBox* SwTableLine::FindNextBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + const SwTableLine* pLine = this; // for M800 + SwTableBox* pBox; + sal_uInt16 nFndPos; + if( !GetTabBoxes().empty() && pSrchBox ) + { + nFndPos = GetBoxPos( pSrchBox ); + if( USHRT_MAX != nFndPos && + nFndPos + 1 != static_cast<sal_uInt16>(GetTabBoxes().size()) ) + { + pBox = GetTabBoxes()[ nFndPos + 1 ]; + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes()[0]; + return pBox; + } + } + + if( GetUpper() ) + { + nFndPos = GetUpper()->GetTabLines().GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nFndPos, "Line is not in the Table" ); + // Is there another Line? + if( nFndPos+1 >= static_cast<sal_uInt16>(GetUpper()->GetTabLines().size()) ) + return GetUpper()->GetUpper()->FindNextBox( rTable, GetUpper(), bOvrTableLns ); + pLine = GetUpper()->GetTabLines()[nFndPos+1]; + } + else if( bOvrTableLns ) // Over a Table's the "BaseLines"?? + { + // Search for the next Line in the Table + nFndPos = rTable.GetTabLines().GetPos( pLine ); + if( nFndPos + 1 >= static_cast<sal_uInt16>(rTable.GetTabLines().size()) ) + return nullptr; // there are no more Boxes + + pLine = rTable.GetTabLines()[ nFndPos+1 ]; + } + else + return nullptr; + + if( !pLine->GetTabBoxes().empty() ) + { + pBox = pLine->GetTabBoxes().front(); + while( !pBox->GetTabLines().empty() ) + pBox = pBox->GetTabLines().front()->GetTabBoxes().front(); + return pBox; + } + return pLine->FindNextBox( rTable, nullptr, bOvrTableLns ); +} + +// Find the previous Box from this Line +SwTableBox* SwTableLine::FindPreviousBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + const SwTableLine* pLine = this; // for M800 + SwTableBox* pBox; + sal_uInt16 nFndPos; + if( !GetTabBoxes().empty() && pSrchBox ) + { + nFndPos = GetBoxPos( pSrchBox ); + if( USHRT_MAX != nFndPos && nFndPos ) + { + pBox = GetTabBoxes()[ nFndPos - 1 ]; + while( !pBox->GetTabLines().empty() ) + { + pLine = pBox->GetTabLines().back(); + pBox = pLine->GetTabBoxes().back(); + } + return pBox; + } + } + + if( GetUpper() ) + { + nFndPos = GetUpper()->GetTabLines().GetPos( pLine ); + OSL_ENSURE( USHRT_MAX != nFndPos, "Line is not in the Table" ); + // Is there another Line? + if( !nFndPos ) + return GetUpper()->GetUpper()->FindPreviousBox( rTable, GetUpper(), bOvrTableLns ); + pLine = GetUpper()->GetTabLines()[nFndPos-1]; + } + else if( bOvrTableLns ) // Over a Table's the "BaseLines"?? + { + // Search for the next Line in the Table + nFndPos = rTable.GetTabLines().GetPos( pLine ); + if( !nFndPos ) + return nullptr; // there are no more Boxes + + pLine = rTable.GetTabLines()[ nFndPos-1 ]; + } + else + return nullptr; + + if( !pLine->GetTabBoxes().empty() ) + { + pBox = pLine->GetTabBoxes().back(); + while( !pBox->GetTabLines().empty() ) + { + pLine = pBox->GetTabLines().back(); + pBox = pLine->GetTabBoxes().back(); + } + return pBox; + } + return pLine->FindPreviousBox( rTable, nullptr, bOvrTableLns ); +} + +// Find the next Box with content from this Line +SwTableBox* SwTableBox::FindNextBox( const SwTable& rTable, + const SwTableBox* pSrchBox, bool bOvrTableLns ) const +{ + if( !pSrchBox && GetTabLines().empty() ) + return const_cast<SwTableBox*>(this); + return GetUpper()->FindNextBox( rTable, pSrchBox ? pSrchBox : this, + bOvrTableLns ); + +} + +// Find the next Box with content from this Line +SwTableBox* SwTableBox::FindPreviousBox( const SwTable& rTable, + const SwTableBox* pSrchBox ) const +{ + if( !pSrchBox && GetTabLines().empty() ) + return const_cast<SwTableBox*>(this); + return GetUpper()->FindPreviousBox( rTable, pSrchBox ? pSrchBox : this ); +} + +static void lcl_BoxSetHeadCondColl( const SwTableBox* pBox ) +{ + // We need to adapt the paragraphs with conditional templates in the HeadLine + const SwStartNode* pSttNd = pBox->GetSttNd(); + if( pSttNd ) + pSttNd->CheckSectionCondColl(); + else + for( const SwTableLine* pLine : pBox->GetTabLines() ) + sw_LineSetHeadCondColl( pLine ); +} + +void sw_LineSetHeadCondColl( const SwTableLine* pLine ) +{ + for( const SwTableBox* pBox : pLine->GetTabBoxes() ) + lcl_BoxSetHeadCondColl(pBox); +} + +static SwTwips lcl_GetDistance( SwTableBox* pBox, bool bLeft ) +{ + bool bFirst = true; + SwTwips nRet = 0; + SwTableLine* pLine; + while( pBox ) + { + pLine = pBox->GetUpper(); + if( !pLine ) + break; + sal_uInt16 nStt = 0, nPos = pLine->GetBoxPos( pBox ); + + if( bFirst && !bLeft ) + ++nPos; + bFirst = false; + + while( nStt < nPos ) + nRet += pLine->GetTabBoxes()[ nStt++ ]->GetFrameFormat() + ->GetFrameSize().GetWidth(); + pBox = pLine->GetUpper(); + } + return nRet; +} + +static bool lcl_SetSelBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFrameFormat* pFormat = pBox->GetFrameFormat(); + const SwFormatFrameSize& rSz = pFormat->GetFrameSize(); + SwTwips nWidth = rSz.GetWidth(); + bool bGreaterBox = false; + + if( bCheck ) + { + for( auto pLn : pBox->GetTabLines() ) + if( !::lcl_SetSelBoxWidth( pLn, rParam, nDist, true )) + return false; + + // Collect all "ContentBoxes" + bGreaterBox = (TableChgMode::FixedWidthChangeAbs != rParam.nMode) + && ((nDist + (rParam.bLeft ? 0 : nWidth)) >= rParam.nSide); + if (bGreaterBox + || (!rParam.bBigger + && (std::abs(nDist + ((rParam.nMode != TableChgMode::FixedWidthChangeAbs && rParam.bLeft) ? 0 : nWidth) - rParam.nSide) < COLFUZZY))) + { + SwTwips nLowerDiff; + if( bGreaterBox && TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + // The "other Boxes" have been adapted, so change by this value + nLowerDiff = (nDist + ( rParam.bLeft ? 0 : nWidth ) ) - rParam.nSide; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + nLowerDiff = rParam.nDiff - nLowerDiff; + } + else + nLowerDiff = rParam.nDiff; + + if( nWidth < nLowerDiff || nWidth - nLowerDiff < MINLAY ) + return false; + } + } + else + { + SwTwips nLowerDiff = 0, nOldLower = rParam.nLowerDiff; + for( auto pLn : pBox->GetTabLines() ) + { + rParam.nLowerDiff = 0; + lcl_SetSelBoxWidth( pLn, rParam, nDist, false ); + + if( nLowerDiff < rParam.nLowerDiff ) + nLowerDiff = rParam.nLowerDiff; + } + rParam.nLowerDiff = nOldLower; + + if( nLowerDiff || + (bGreaterBox = !nOldLower && TableChgMode::FixedWidthChangeAbs != rParam.nMode && + ( nDist + ( rParam.bLeft ? 0 : nWidth ) ) >= rParam.nSide) || + ( std::abs( nDist + ( (rParam.nMode != TableChgMode::FixedWidthChangeAbs && rParam.bLeft) ? 0 : nWidth ) + - rParam.nSide ) < COLFUZZY )) + { + // This column contains the Cursor - so decrease/increase + SwFormatFrameSize aNew( rSz ); + + if( !nLowerDiff ) + { + if( bGreaterBox && TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + // The "other Boxes" have been adapted, so change by this value + nLowerDiff = (nDist + ( rParam.bLeft ? 0 : nWidth ) ) - rParam.nSide; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + nLowerDiff = rParam.nDiff - nLowerDiff; + } + else + nLowerDiff = rParam.nDiff; + } + + rParam.nLowerDiff += nLowerDiff; + + if( rParam.bBigger ) + aNew.SetWidth( nWidth + nLowerDiff ); + else + aNew.SetWidth( nWidth - nLowerDiff ); + rParam.aShareFormats.SetSize( *pBox, aNew ); + break; + } + } + + if( rParam.bLeft && rParam.nMode != TableChgMode::FixedWidthChangeAbs && nDist >= rParam.nSide ) + break; + + nDist += nWidth; + + // If it gets bigger, then that's it + if( ( TableChgMode::FixedWidthChangeAbs == rParam.nMode || !rParam.bLeft ) && + nDist >= rParam.nSide ) + break; + } + return true; +} + +static bool lcl_SetOtherBoxWidth( SwTableLine* pLine, CR_SetBoxWidth& rParam, + SwTwips nDist, bool bCheck ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFrameFormat* pFormat = pBox->GetFrameFormat(); + const SwFormatFrameSize& rSz = pFormat->GetFrameSize(); + SwTwips nWidth = rSz.GetWidth(); + + if( bCheck ) + { + for( auto pLn : pBox->GetTabLines() ) + if( !::lcl_SetOtherBoxWidth( pLn, rParam, nDist, true )) + return false; + + if( rParam.bBigger && ( TableChgMode::FixedWidthChangeAbs == rParam.nMode + ? std::abs( nDist - rParam.nSide ) < COLFUZZY + : ( rParam.bLeft ? nDist < rParam.nSide - COLFUZZY + : nDist >= rParam.nSide - COLFUZZY )) ) + { + SwTwips nDiff; + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) // Table fixed, proportional + { + // calculate relative + nDiff = nWidth; + nDiff *= rParam.nDiff; + nDiff /= rParam.nMaxSize; + } + else + nDiff = rParam.nDiff; + + if( nWidth < nDiff || nWidth - nDiff < MINLAY ) + return false; + } + } + else + { + SwTwips nLowerDiff = 0, nOldLower = rParam.nLowerDiff; + for( auto pLn : pBox->GetTabLines() ) + { + rParam.nLowerDiff = 0; + lcl_SetOtherBoxWidth( pLn, rParam, nDist, false ); + + if( nLowerDiff < rParam.nLowerDiff ) + nLowerDiff = rParam.nLowerDiff; + } + rParam.nLowerDiff = nOldLower; + + if( nLowerDiff || + ( TableChgMode::FixedWidthChangeAbs == rParam.nMode + ? std::abs( nDist - rParam.nSide ) < COLFUZZY + : ( rParam.bLeft ? nDist < rParam.nSide - COLFUZZY + : nDist >= rParam.nSide - COLFUZZY) + ) ) + { + SwFormatFrameSize aNew( rSz ); + + if( !nLowerDiff ) + { + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) // Table fixed, proportional + { + // calculate relative + nLowerDiff = nWidth; + nLowerDiff *= rParam.nDiff; + nLowerDiff /= rParam.nMaxSize; + } + else + nLowerDiff = rParam.nDiff; + } + + rParam.nLowerDiff += nLowerDiff; + + if( rParam.bBigger ) + aNew.SetWidth( nWidth - nLowerDiff ); + else + aNew.SetWidth( nWidth + nLowerDiff ); + + rParam.aShareFormats.SetSize( *pBox, aNew ); + } + } + + nDist += nWidth; + if( ( TableChgMode::FixedWidthChangeAbs == rParam.nMode || rParam.bLeft ) && + nDist > rParam.nSide ) + break; + } + return true; +} + +static void lcl_AjustLines( SwTableLine* pLine, CR_SetBoxWidth& rParam ) +{ + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + for( auto pBox : rBoxes ) + { + SwFormatFrameSize aSz( pBox->GetFrameFormat()->GetFrameSize() ); + SwTwips nWidth = aSz.GetWidth(); + nWidth *= rParam.nDiff; + nWidth /= rParam.nMaxSize; + aSz.SetWidth( nWidth ); + rParam.aShareFormats.SetSize( *pBox, aSz ); + + for( auto pLn : pBox->GetTabLines() ) + ::lcl_AjustLines( pLn, rParam ); + } +} + +#ifdef DBG_UTIL +void CheckBoxWidth( const SwTableLine& rLine, SwTwips nSize ) +{ + const SwTableBoxes& rBoxes = rLine.GetTabBoxes(); + + SwTwips nCurrentSize = 0; + // See if the tables have a correct width + for (const SwTableBox* pBox : rBoxes) + { + const SwTwips nBoxW = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + nCurrentSize += nBoxW; + + for( auto pLn : pBox->GetTabLines() ) + CheckBoxWidth( *pLn, nBoxW ); + } + + if (sal::static_int_cast< unsigned long >(std::abs(nCurrentSize - nSize)) > + (COLFUZZY * rBoxes.size())) + { + OSL_FAIL( "Line's Boxes are too small or too large" ); + } +} +#endif + +bool SwTable::SetColWidth( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff, std::unique_ptr<SwUndo>* ppUndo ) +{ + SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>()); // Delete HTML Layout + + const SwFormatFrameSize& rSz = GetFrameFormat()->GetFrameSize(); + const SvxLRSpaceItem& rLR = GetFrameFormat()->GetLRSpace(); + + std::unique_ptr<FndBox_> xFndBox; // for insertion/deletion + bool bBigger, + bRet = false, + bLeft = TableChgWidthHeightType::ColLeft == extractPosition( eType ) || + TableChgWidthHeightType::CellLeft == extractPosition( eType ); + + // Get the current Box's edge + // Only needed for manipulating the width + const SwTwips nDist = ::lcl_GetDistance( &rCurrentBox, bLeft ); + SwTwips nDistStt = 0; + CR_SetBoxWidth aParam( eType, nRelDiff, nDist, + bLeft ? nDist : rSz.GetWidth() - nDist, + const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()) ); + bBigger = aParam.bBigger; + + FN_lcl_SetBoxWidth fnSelBox, fnOtherBox; + fnSelBox = lcl_SetSelBoxWidth; + fnOtherBox = lcl_SetOtherBoxWidth; + + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::ColRight: + case TableChgWidthHeightType::ColLeft: + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // First test if we have room at all + bool bChgLRSpace = true; + if( bBigger ) + { + if( GetFrameFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) && + !rSz.GetWidthPercent() ) + { + // silence -Wsign-compare on Android with the static cast + bRet = rSz.GetWidth() < static_cast<unsigned short>(USHRT_MAX) - nRelDiff; + bChgLRSpace = bLeft ? rLR.GetLeft() >= nAbsDiff + : rLR.GetRight() >= nAbsDiff; + } + else + bRet = bLeft ? rLR.GetLeft() >= nAbsDiff + : rLR.GetRight() >= nAbsDiff; + + if( !bRet ) + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::FixedWidthChangeProp; + + bRet = SetColWidth( rCurrentBox, eType, nAbsDiff, nRelDiff, + ppUndo ); + m_eTableChgMode = eOld; + return bRet; + } + } + else + { + bRet = true; + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnSelBox)( n, aParam, nDistStt, true )) + { + bRet = false; + break; + } + } + } + + if( bRet ) + { + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + long nFrameWidth = LONG_MAX; + LockModify(); + SwFormatFrameSize aSz( rSz ); + SvxLRSpaceItem aLR( rLR ); + if( bBigger ) + { + // If the Table does not have any room to grow, we need to create some! + // silence -Wsign-compare on Android with the static cast + if( aSz.GetWidth() + nRelDiff > static_cast<unsigned short>(USHRT_MAX) ) + { + // Break down to USHRT_MAX / 2 + CR_SetBoxWidth aTmpPara( TableChgWidthHeightType::ColLeft, aSz.GetWidth() / 2, + 0, aSz.GetWidth(), aParam.pTableNd ); + for( size_t nLn = 0; nLn < m_aLines.size(); ++nLn ) + ::lcl_AjustLines( m_aLines[ nLn ], aTmpPara ); + aSz.SetWidth( aSz.GetWidth() / 2 ); + aParam.nDiff = nRelDiff /= 2; + aParam.nSide /= 2; + aParam.nMaxSize /= 2; + } + + if( bLeft ) + aLR.SetLeft( sal_uInt16( aLR.GetLeft() - nAbsDiff ) ); + else + aLR.SetRight( sal_uInt16( aLR.GetRight() - nAbsDiff ) ); + } + else if( bLeft ) + aLR.SetLeft( sal_uInt16( aLR.GetLeft() + nAbsDiff ) ); + else + aLR.SetRight( sal_uInt16( aLR.GetRight() + nAbsDiff ) ); + + if( bChgLRSpace ) + GetFrameFormat()->SetFormatAttr( aLR ); + const SwFormatHoriOrient& rHOri = GetFrameFormat()->GetHoriOrient(); + if( text::HoriOrientation::FULL == rHOri.GetHoriOrient() || + (text::HoriOrientation::LEFT == rHOri.GetHoriOrient() && aLR.GetLeft()) || + (text::HoriOrientation::RIGHT == rHOri.GetHoriOrient() && aLR.GetRight())) + { + SwFormatHoriOrient aHOri( rHOri ); + aHOri.SetHoriOrient( text::HoriOrientation::NONE ); + GetFrameFormat()->SetFormatAttr( aHOri ); + + // If the Table happens to contain relative values (USHORT_MAX), + // we need to convert them to absolute ones now. + // Bug 61494 + if( GetFrameFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) && + !rSz.GetWidthPercent() ) + { + SwTabFrame* pTabFrame = SwIterator<SwTabFrame,SwFormat>( *GetFrameFormat() ).First(); + if( pTabFrame && + pTabFrame->getFramePrintArea().Width() != rSz.GetWidth() ) + { + nFrameWidth = pTabFrame->getFramePrintArea().Width(); + if( bBigger ) + nFrameWidth += nAbsDiff; + else + nFrameWidth -= nAbsDiff; + } + } + } + + if( bBigger ) + aSz.SetWidth( aSz.GetWidth() + nRelDiff ); + else + aSz.SetWidth( aSz.GetWidth() - nRelDiff ); + + if( rSz.GetWidthPercent() ) + aSz.SetWidthPercent( static_cast<sal_uInt8>(( aSz.GetWidth() * 100 ) / + ( aSz.GetWidth() + aLR.GetRight() + aLR.GetLeft()))); + + GetFrameFormat()->SetFormatAttr( aSz ); + + UnlockModify(); + + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + } + + // If the Table happens to contain relative values (USHORT_MAX), + // we need to convert them to absolute ones now. + // Bug 61494 + if( LONG_MAX != nFrameWidth ) + { + SwFormatFrameSize aAbsSz( aSz ); + aAbsSz.SetWidth( nFrameWidth ); + GetFrameFormat()->SetFormatAttr( aAbsSz ); + } + } + } + else if( bLeft ? nDist != 0 : std::abs( rSz.GetWidth() - nDist ) > COLFUZZY ) + { + bRet = true; + if( bLeft && TableChgMode::FixedWidthChangeAbs == m_eTableChgMode ) + aParam.bBigger = !bBigger; + + // First test if we have room at all + if( aParam.bBigger ) + { + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnOtherBox)( n, aParam, 0, true )) + { + bRet = false; + break; + } + } + } + else + { + for( auto const & n: m_aLines ) + { + aParam.LoopClear(); + if( !(*fnSelBox)( n, aParam, nDistStt, true )) + { + bRet = false; + break; + } + } + } + + // If true, set it + if( bRet ) + { + CR_SetBoxWidth aParam1( aParam ); + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + if( TableChgMode::FixedWidthChangeAbs != m_eTableChgMode && bLeft ) + { + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + aParam1.LoopClear(); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + (*fnOtherBox)( m_aLines[ n ], aParam1, nDistStt, false ); + } + } + else + { + for( sal_uInt16 n = m_aLines.size(); n; ) + { + --n; + aParam.LoopClear(); + aParam1.LoopClear(); + (*fnOtherBox)( m_aLines[ n ], aParam1, nDistStt, false ); + (*fnSelBox)( m_aLines[ n ], aParam, nDistStt, false ); + } + } + } + } + break; + + case TableChgWidthHeightType::CellRight: + case TableChgWidthHeightType::CellLeft: + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::FixedWidthChangeAbs; + + bRet = SetColWidth( rCurrentBox, eType, nAbsDiff, nRelDiff, + ppUndo ); + m_eTableChgMode = eOld; + return bRet; + } + else if( bLeft ? nDist != 0 : (rSz.GetWidth() - nDist) > COLFUZZY ) + { + if( bLeft && TableChgMode::FixedWidthChangeAbs == m_eTableChgMode ) + aParam.bBigger = !bBigger; + + // First, see if there is enough room at all + SwTableBox* pBox = &rCurrentBox; + SwTableLine* pLine = rCurrentBox.GetUpper(); + while( pLine->GetUpper() ) + { + const SwTableBoxes::size_type nPos = pLine->GetBoxPos( pBox ); + if( bLeft ? nPos != 0 : nPos + 1 != pLine->GetTabBoxes().size() ) + break; + + pBox = pLine->GetUpper(); + pLine = pBox->GetUpper(); + } + + if( pLine->GetUpper() ) + { + // We need to correct the distance once again! + aParam.nSide -= ::lcl_GetDistance( pLine->GetUpper(), true ); + + if( bLeft ) + aParam.nMaxSize = aParam.nSide; + else + aParam.nMaxSize = pLine->GetUpper()->GetFrameFormat()-> + GetFrameSize().GetWidth() - aParam.nSide; + } + + // First, see if there is enough room at all + FN_lcl_SetBoxWidth fnTmp = aParam.bBigger ? fnOtherBox : fnSelBox; + bRet = (*fnTmp)( pLine, aParam, nDistStt, true ); + + // If true, set it + if( bRet ) + { + CR_SetBoxWidth aParam1( aParam ); + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + if( TableChgMode::FixedWidthChangeAbs != m_eTableChgMode && bLeft ) + { + (*fnSelBox)( pLine, aParam, nDistStt, false ); + (*fnOtherBox)( pLine, aParam1, nDistStt, false ); + } + else + { + (*fnOtherBox)( pLine, aParam1, nDistStt, false ); + (*fnSelBox)( pLine, aParam, nDistStt, false ); + } + } + } + break; + default: break; + } + + if( xFndBox ) + { + // Clean up the structure of all Lines + GCLines(); + + // Update Layout + if( !bBigger || xFndBox->AreLinesToRestore( *this ) ) + xFndBox->MakeFrames( *this ); + + // TL_CHART2: it is currently unclear if sth has to be done here. + // The function name hints that nothing needs to be done, on the other + // hand there is a case where sth gets deleted. :-( + + xFndBox.reset(); + } + +#if defined DBG_UTIL + if( bRet ) + { + CHECKBOXWIDTH + CHECKTABLELAYOUT + } +#endif + + return bRet; +} + +static void SetLineHeight( SwTableLine& rLine, SwTwips nOldHeight, SwTwips nNewHeight, + bool bMinSize ) +{ + SwLayoutFrame* pLineFrame = GetRowFrame( rLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + + SwFrameFormat* pFormat = rLine.ClaimFrameFormat(); + + SwTwips nMyNewH, nMyOldH = pLineFrame->getFrameArea().Height(); + if( !nOldHeight ) // the BaseLine and absolute + nMyNewH = nMyOldH + nNewHeight; + else + { + // Calculate as exactly as possible + Fraction aTmp( nMyOldH ); + aTmp *= Fraction( nNewHeight, nOldHeight ); + aTmp += Fraction( 1, 2 ); // round up if needed + nMyNewH = long(aTmp); + } + + SwFrameSize eSize = SwFrameSize::Minimum; + if( !bMinSize && + ( nMyOldH - nMyNewH ) > ( CalcRowRstHeight( pLineFrame ) + ROWFUZZY )) + eSize = SwFrameSize::Fixed; + + pFormat->SetFormatAttr( SwFormatFrameSize( eSize, 0, nMyNewH ) ); + + // First adapt all internal ones + for( auto pBox : rLine.GetTabBoxes() ) + { + for( auto pLine : pBox->GetTabLines() ) + SetLineHeight( *pLine, nMyOldH, nMyNewH, bMinSize ); + } +} + +static bool lcl_SetSelLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ) +{ + bool bRet = true; + if( !bCheck ) + { + // Set line height + SetLineHeight( *pLine, 0, rParam.bBigger ? nDist : -nDist, + rParam.bBigger ); + } + else if( !rParam.bBigger ) + { + // Calculate the new relative size by means of the old one + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + SwTwips nRstHeight = CalcRowRstHeight( pLineFrame ); + if( (nRstHeight + ROWFUZZY) < nDist ) + bRet = false; + } + return bRet; +} + +static bool lcl_SetOtherLineHeight( SwTableLine* pLine, const CR_SetLineHeight& rParam, + SwTwips nDist, bool bCheck ) +{ + bool bRet = true; + if( bCheck ) + { + if( rParam.bBigger ) + { + // Calculate the new relative size by means of the old one + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine?" ); + + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + nDist *= pLineFrame->getFrameArea().Height(); + nDist /= rParam.nMaxHeight; + } + bRet = nDist <= CalcRowRstHeight( pLineFrame ); + } + } + else + { + // Set line height + // pLine is the following/preceding, thus adjust it + if( TableChgMode::FixedWidthChangeProp == rParam.nMode ) + { + SwLayoutFrame* pLineFrame = GetRowFrame( *pLine ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine??" ); + + // Calculate the new relative size by means of the old one + // If the selected Box get bigger, adjust via the max space else + // via the max height. + if( (true) /*!rParam.bBigger*/ ) + { + nDist *= pLineFrame->getFrameArea().Height(); + nDist /= rParam.nMaxHeight; + } + else + { + // Calculate the new relative size by means of the old one + nDist *= CalcRowRstHeight( pLineFrame ); + nDist /= rParam.nMaxSpace; + } + } + SetLineHeight( *pLine, 0, rParam.bBigger ? -nDist : nDist, + !rParam.bBigger ); + } + return bRet; +} + +bool SwTable::SetRowHeight( SwTableBox& rCurrentBox, TableChgWidthHeightType eType, + SwTwips nAbsDiff, SwTwips nRelDiff, std::unique_ptr<SwUndo>* ppUndo ) +{ + SwTableLine* pLine = rCurrentBox.GetUpper(); + + SwTableLine* pBaseLine = pLine; + while( pBaseLine->GetUpper() ) + pBaseLine = pBaseLine->GetUpper()->GetUpper(); + + std::unique_ptr<FndBox_> xFndBox; // for insertion/deletion + bool bBigger, + bRet = false, + bTop = TableChgWidthHeightType::CellTop == extractPosition( eType ); + sal_uInt16 nBaseLinePos = GetTabLines().GetPos( pBaseLine ); + + CR_SetLineHeight aParam( eType, + const_cast<SwTableNode*>(rCurrentBox.GetSttNd()->FindTableNode()) ); + bBigger = aParam.bBigger; + + SwTableLines* pLines = &m_aLines; + + // How do we get to the height? + switch( extractPosition(eType) ) + { + case TableChgWidthHeightType::CellTop: + case TableChgWidthHeightType::CellBottom: + if( pLine == pBaseLine ) + break; // it doesn't work then! + + // Is a nested Line (Box!) + pLines = &pLine->GetUpper()->GetTabLines(); + nBaseLinePos = pLines->GetPos( pLine ); + [[fallthrough]]; + + case TableChgWidthHeightType::RowBottom: + { + if( TableChgMode::VarWidthChangeAbs == m_eTableChgMode ) + { + // First test if we have room at all + if( bBigger ) + bRet = true; + else + bRet = lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, true ); + + if( bRet ) + { + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + } + } + else + { + bRet = true; + SwTableLines::size_type nStt; + SwTableLines::size_type nEnd; + if( bTop ) + { + nStt = 0; + nEnd = nBaseLinePos; + } + else + { + nStt = nBaseLinePos + 1; + nEnd = pLines->size(); + } + + // Get the current Lines' height + if( TableChgMode::FixedWidthChangeProp == m_eTableChgMode ) + { + for( auto n = nStt; n < nEnd; ++n ) + { + SwLayoutFrame* pLineFrame = GetRowFrame( *(*pLines)[ n ] ); + OSL_ENSURE( pLineFrame, "Where is the Frame from the SwTableLine??" ); + aParam.nMaxSpace += CalcRowRstHeight( pLineFrame ); + aParam.nMaxHeight += pLineFrame->getFrameArea().Height(); + } + if( bBigger && aParam.nMaxSpace < nAbsDiff ) + bRet = false; + } + else + { + if( bTop ? nEnd != 0 : nStt < nEnd ) + { + if( bTop ) + nStt = nEnd - 1; + else + nEnd = nStt + 1; + } + else + bRet = false; + } + + if( bRet ) + { + if( bBigger ) + { + for( auto n = nStt; n < nEnd; ++n ) + { + if( !lcl_SetOtherLineHeight( (*pLines)[ n ], aParam, + nAbsDiff, true )) + { + bRet = false; + break; + } + } + } + else + bRet = lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, true ); + } + + if( bRet ) + { + // Adjust + if( ppUndo ) + ppUndo->reset(new SwUndoAttrTable( *aParam.pTableNd, true )); + + CR_SetLineHeight aParam1( aParam ); + + if( bTop ) + { + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + for( auto n = nStt; n < nEnd; ++n ) + lcl_SetOtherLineHeight( (*pLines)[ n ], aParam1, + nAbsDiff, false ); + } + else + { + for( auto n = nStt; n < nEnd; ++n ) + lcl_SetOtherLineHeight( (*pLines)[ n ], aParam1, + nAbsDiff, false ); + lcl_SetSelLineHeight( (*pLines)[ nBaseLinePos ], aParam, + nAbsDiff, false ); + } + } + else + { + // Then call itself recursively; only with another mode (proportional) + TableChgMode eOld = m_eTableChgMode; + m_eTableChgMode = TableChgMode::VarWidthChangeAbs; + + bRet = SetRowHeight( rCurrentBox, eType, nAbsDiff, + nRelDiff, ppUndo ); + + m_eTableChgMode = eOld; + xFndBox.reset(); + } + } + } + break; + default: break; + } + + if( xFndBox ) + { + // then clean up the structure of all Lines + GCLines(); + + // Update Layout + if( bBigger || xFndBox->AreLinesToRestore( *this ) ) + xFndBox->MakeFrames( *this ); + + // TL_CHART2: it is currently unclear if sth has to be done here. + + xFndBox.reset(); + } + + CHECKTABLELAYOUT + + return bRet; +} + +SwFrameFormat* SwShareBoxFormat::GetFormat( long nWidth ) const +{ + SwFrameFormat *pRet = nullptr, *pTmp; + for( auto n = aNewFormats.size(); n; ) + if( ( pTmp = aNewFormats[ --n ])->GetFrameSize().GetWidth() + == nWidth ) + { + pRet = pTmp; + break; + } + return pRet; +} + +SwFrameFormat* SwShareBoxFormat::GetFormat( const SfxPoolItem& rItem ) const +{ + const SfxPoolItem* pItem; + sal_uInt16 nWhich = rItem.Which(); + SwFrameFormat *pRet = nullptr, *pTmp; + const SfxPoolItem& rFrameSz = pOldFormat->GetFormatAttr( RES_FRM_SIZE, false ); + for( auto n = aNewFormats.size(); n; ) + if( SfxItemState::SET == ( pTmp = aNewFormats[ --n ])-> + GetItemState( nWhich, false, &pItem ) && *pItem == rItem && + pTmp->GetFormatAttr( RES_FRM_SIZE, false ) == rFrameSz ) + { + pRet = pTmp; + break; + } + return pRet; +} + +void SwShareBoxFormat::AddFormat( SwFrameFormat& rNew ) +{ + aNewFormats.push_back( &rNew ); +} + +bool SwShareBoxFormat::RemoveFormat( const SwFrameFormat& rFormat ) +{ + // returns true, if we can delete + if( pOldFormat == &rFormat ) + return true; + + std::vector<SwFrameFormat*>::iterator it = std::find( aNewFormats.begin(), aNewFormats.end(), &rFormat ); + if( aNewFormats.end() != it ) + aNewFormats.erase( it ); + return aNewFormats.empty(); +} + +SwShareBoxFormats::~SwShareBoxFormats() +{ +} + +SwFrameFormat* SwShareBoxFormats::GetFormat( const SwFrameFormat& rFormat, long nWidth ) const +{ + sal_uInt16 nPos; + return Seek_Entry( rFormat, &nPos ) + ? m_ShareArr[ nPos ]->GetFormat(nWidth) + : nullptr; +} +SwFrameFormat* SwShareBoxFormats::GetFormat( const SwFrameFormat& rFormat, + const SfxPoolItem& rItem ) const +{ + sal_uInt16 nPos; + return Seek_Entry( rFormat, &nPos ) + ? m_ShareArr[ nPos ]->GetFormat(rItem) + : nullptr; +} + +void SwShareBoxFormats::AddFormat( const SwFrameFormat& rOld, SwFrameFormat& rNew ) +{ + sal_uInt16 nPos; + SwShareBoxFormat* pEntry; + if( !Seek_Entry( rOld, &nPos )) + { + pEntry = new SwShareBoxFormat( rOld ); + m_ShareArr.insert(m_ShareArr.begin() + nPos, std::unique_ptr<SwShareBoxFormat>(pEntry)); + } + else + pEntry = m_ShareArr[ nPos ].get(); + + pEntry->AddFormat( rNew ); +} + +void SwShareBoxFormats::ChangeFrameFormat( SwTableBox* pBox, SwTableLine* pLn, + SwFrameFormat& rFormat ) +{ + SwClient aCl; + SwFrameFormat* pOld = nullptr; + if( pBox ) + { + pOld = pBox->GetFrameFormat(); + pOld->Add( &aCl ); + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(&rFormat) ); + } + else if( pLn ) + { + pOld = pLn->GetFrameFormat(); + pOld->Add( &aCl ); + pLn->ChgFrameFormat( static_cast<SwTableLineFormat*>(&rFormat) ); + } + if( pOld && pOld->HasOnlyOneListener() ) + { + RemoveFormat( *pOld ); + delete pOld; + } +} + +void SwShareBoxFormats::SetSize( SwTableBox& rBox, const SwFormatFrameSize& rSz ) +{ + SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(), + *pRet = GetFormat( *pBoxFormat, rSz.GetWidth() ); + if( pRet ) + ChangeFrameFormat( &rBox, nullptr, *pRet ); + else + { + pRet = rBox.ClaimFrameFormat(); + pRet->SetFormatAttr( rSz ); + AddFormat( *pBoxFormat, *pRet ); + } +} + +void SwShareBoxFormats::SetAttr( SwTableBox& rBox, const SfxPoolItem& rItem ) +{ + SwFrameFormat *pBoxFormat = rBox.GetFrameFormat(), + *pRet = GetFormat( *pBoxFormat, rItem ); + if( pRet ) + ChangeFrameFormat( &rBox, nullptr, *pRet ); + else + { + pRet = rBox.ClaimFrameFormat(); + pRet->SetFormatAttr( rItem ); + AddFormat( *pBoxFormat, *pRet ); + } +} + +void SwShareBoxFormats::SetAttr( SwTableLine& rLine, const SfxPoolItem& rItem ) +{ + SwFrameFormat *pLineFormat = rLine.GetFrameFormat(), + *pRet = GetFormat( *pLineFormat, rItem ); + if( pRet ) + ChangeFrameFormat( nullptr, &rLine, *pRet ); + else + { + pRet = rLine.ClaimFrameFormat(); + pRet->SetFormatAttr( rItem ); + AddFormat( *pLineFormat, *pRet ); + } +} + +void SwShareBoxFormats::RemoveFormat( const SwFrameFormat& rFormat ) +{ + for (auto i = m_ShareArr.size(); i; ) + { + if (m_ShareArr[ --i ]->RemoveFormat(rFormat)) + { + m_ShareArr.erase( m_ShareArr.begin() + i ); + } + } +} + +bool SwShareBoxFormats::Seek_Entry( const SwFrameFormat& rFormat, sal_uInt16* pPos ) const +{ + sal_uIntPtr nIdx = reinterpret_cast<sal_uIntPtr>(&rFormat); + auto nO = m_ShareArr.size(); + decltype(nO) nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const auto nM = nU + ( nO - nU ) / 2; + sal_uIntPtr nFormat = reinterpret_cast<sal_uIntPtr>(&m_ShareArr[ nM ]->GetOldFormat()); + if( nFormat == nIdx ) + { + if( pPos ) + *pPos = nM; + return true; + } + else if( nFormat < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + { + if( pPos ) + *pPos = nU; + return false; + } + else + nO = nM - 1; + } + } + if( pPos ) + *pPos = nU; + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx new file mode 100644 index 000000000..9a2cc95dc --- /dev/null +++ b/sw/source/core/doc/textboxhelper.cxx @@ -0,0 +1,774 @@ +/* -*- 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/. + */ + +#include <textboxhelper.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtcnct.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docsh.hxx> +#include <unocoll.hxx> +#include <unoframe.hxx> +#include <unodraw.hxx> +#include <unotextrange.hxx> +#include <cmdid.h> +#include <unomid.h> +#include <unoprnms.hxx> +#include <mvsave.hxx> +#include <fmtsrnd.hxx> +#include <frmfmt.hxx> +#include <frameformats.hxx> + +#include <editeng/unoprnms.hxx> +#include <editeng/memberids.h> +#include <svx/svdoashp.hxx> +#include <svx/svdpage.hxx> +#include <svl/itemiter.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <sal/log.hxx> +#include <svx/anchorid.hxx> + +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> + +using namespace com::sun::star; + +void SwTextBoxHelper::create(SwFrameFormat* pShape) +{ + // If TextBox wasn't enabled previously + if (pShape->GetAttrSet().HasItem(RES_CNTNT)) + return; + + // Create the associated TextFrame and insert it into the document. + uno::Reference<text::XTextContent> xTextFrame( + SwXServiceProvider::MakeInstance(SwServiceType::TypeTextFrame, *pShape->GetDoc()), + uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument( + pShape->GetDoc()->GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + uno::Reference<text::XTextContentAppend> xTextContentAppend(xTextDocument->getText(), + uno::UNO_QUERY); + try + { + SdrObject* pSourceSDRShape = pShape->FindRealSdrObject(); + uno::Reference<text::XTextContent> XSourceShape(pSourceSDRShape->getUnoShape(), + uno::UNO_QUERY_THROW); + xTextContentAppend->insertTextContentWithProperties( + xTextFrame, uno::Sequence<beans::PropertyValue>(), XSourceShape->getAnchor()); + } + catch (uno::Exception&) + { + xTextContentAppend->appendTextContent(xTextFrame, uno::Sequence<beans::PropertyValue>()); + } + // Link FLY and DRAW formats, so it becomes a text box (needed for syncProperty calls). + uno::Reference<text::XTextFrame> xRealTextFrame(xTextFrame, uno::UNO_QUERY); + auto pTextFrame = dynamic_cast<SwXTextFrame*>(xRealTextFrame.get()); + assert(nullptr != pTextFrame); + SwFrameFormat* pFormat = pTextFrame->GetFrameFormat(); + + assert(nullptr != dynamic_cast<SwDrawFrameFormat*>(pShape)); + assert(nullptr != dynamic_cast<SwFlyFrameFormat*>(pFormat)); + + pShape->SetOtherTextBoxFormat(pFormat); + pFormat->SetOtherTextBoxFormat(pShape); + + // Initialize properties. + uno::Reference<beans::XPropertySet> xPropertySet(xTextFrame, uno::UNO_QUERY); + uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2()); + xPropertySet->setPropertyValue(UNO_NAME_TOP_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_BOTTOM_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_LEFT_BORDER, aEmptyBorder); + xPropertySet->setPropertyValue(UNO_NAME_RIGHT_BORDER, aEmptyBorder); + + xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(sal_Int32(100))); + + xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::makeAny(text::SizeType::FIX)); + + xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::makeAny(text::WrapTextMode_THROUGH)); + + uno::Reference<container::XNamed> xNamed(xTextFrame, uno::UNO_QUERY); + xNamed->setName(pShape->GetDoc()->GetUniqueFrameName()); + + // Link its text range to the original shape. + uno::Reference<text::XTextRange> xTextBox(xTextFrame, uno::UNO_QUERY_THROW); + SwUnoInternalPaM aInternalPaM(*pShape->GetDoc()); + if (sw::XTextRangeToSwPaM(aInternalPaM, xTextBox)) + { + SwAttrSet aSet(pShape->GetAttrSet()); + SwFormatContent aContent(aInternalPaM.GetNode().StartOfSectionNode()); + aSet.Put(aContent); + pShape->SetFormatAttr(aSet); + } + + // Also initialize the properties, which are not constant, but inherited from the shape's ones. + uno::Reference<drawing::XShape> xShape(pShape->FindRealSdrObject()->getUnoShape(), + uno::UNO_QUERY); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::makeAny(xShape->getSize())); + + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + syncProperty(pShape, RES_FOLLOW_TEXT_FLOW, MID_FOLLOW_TEXT_FLOW, + xShapePropertySet->getPropertyValue(UNO_NAME_IS_FOLLOWING_TEXT_FLOW)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION)); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION)); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION)); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT)); + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST)); + text::WritingMode eMode; + if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(sal_Int16(eMode))); +} + +void SwTextBoxHelper::destroy(SwFrameFormat* pShape) +{ + // If a TextBox was enabled previously + if (pShape->GetAttrSet().HasItem(RES_CNTNT)) + { + SwFrameFormat* pFormat = pShape->GetOtherTextBoxFormat(); + + // Unlink the TextBox's text range from the original shape. + pShape->ResetFormatAttr(RES_CNTNT); + + // Delete the associated TextFrame. + if (pFormat) + pShape->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat(pFormat); + } +} + +bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType) +{ + assert(nType == RES_FLYFRMFMT || nType == RES_DRAWFRMFMT); + if (!pFormat || pFormat->Which() != nType || !pFormat->GetAttrSet().HasItem(RES_CNTNT)) + return false; + + sal_uInt16 nOtherType = (pFormat->Which() == RES_FLYFRMFMT) ? sal_uInt16(RES_DRAWFRMFMT) + : sal_uInt16(RES_FLYFRMFMT); + SwFrameFormat* pOtherFormat = pFormat->GetOtherTextBoxFormat(); + if (!pOtherFormat) + return false; + + assert(pOtherFormat->Which() == nOtherType); + if (pOtherFormat->Which() != nOtherType) + return false; + + const SwFormatContent& rContent = pFormat->GetContent(); + return pOtherFormat->GetAttrSet().HasItem(RES_CNTNT) && pOtherFormat->GetContent() == rContent; +} + +sal_Int32 SwTextBoxHelper::getCount(SdrPage const* pPage) +{ + sal_Int32 nRet = 0; + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + ++nRet; + } + return nRet; +} + +sal_Int32 SwTextBoxHelper::getCount(const SwDoc* pDoc) +{ + sal_Int32 nRet = 0; + const SwFrameFormats& rSpzFrameFormats = *pDoc->GetSpzFrameFormats(); + for (const auto pFormat : rSpzFrameFormats) + { + if (isTextBox(pFormat, RES_FLYFRMFMT)) + ++nRet; + } + return nRet; +} + +uno::Any SwTextBoxHelper::getByIndex(SdrPage const* pPage, sal_Int32 nIndex) +{ + if (nIndex < 0) + throw lang::IndexOutOfBoundsException(); + + SdrObject* pRet = nullptr; + sal_Int32 nCount = 0; // Current logical index. + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + if (nCount == nIndex) + { + pRet = p; + break; + } + ++nCount; + } + + if (!pRet) + throw lang::IndexOutOfBoundsException(); + + return uno::makeAny(uno::Reference<drawing::XShape>(pRet->getUnoShape(), uno::UNO_QUERY)); +} + +sal_Int32 SwTextBoxHelper::getOrdNum(const SdrObject* pObject) +{ + if (const SdrPage* pPage = pObject->getSdrPageFromSdrObject()) + { + sal_Int32 nOrder = 0; // Current logical order. + for (std::size_t i = 0; i < pPage->GetObjCount(); ++i) + { + SdrObject* p = pPage->GetObj(i); + if (p && p->IsTextBox()) + continue; + if (p == pObject) + return nOrder; + ++nOrder; + } + } + + SAL_WARN("sw.core", "SwTextBoxHelper::getOrdNum: no page or page doesn't contain the object"); + return pObject->GetOrdNum(); +} + +void SwTextBoxHelper::getShapeWrapThrough(const SwFrameFormat* pTextBox, bool& rWrapThrough) +{ + SwFrameFormat* pShape = SwTextBoxHelper::getOtherTextBoxFormat(pTextBox, RES_FLYFRMFMT); + if (pShape) + rWrapThrough = pShape->GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH; +} + +SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(const SwFrameFormat* pFormat, + sal_uInt16 nType) +{ + if (!isTextBox(pFormat, nType)) + return nullptr; + return pFormat->GetOtherTextBoxFormat(); +} + +SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(uno::Reference<drawing::XShape> const& xShape) +{ + auto pShape = dynamic_cast<SwXShape*>(xShape.get()); + if (!pShape) + return nullptr; + + SwFrameFormat* pFormat = pShape->GetFrameFormat(); + return getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT); +} + +template <typename T> static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny) +{ + if (SwFrameFormat* pFormat = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + uno::Reference<T> const xInterface( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); + rAny <<= xInterface; + } +} + +uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType) +{ + uno::Any aRet; + + if (rType == cppu::UnoType<css::text::XTextAppend>::get()) + { + lcl_queryInterface<text::XTextAppend>(pShape, aRet); + } + else if (rType == cppu::UnoType<css::text::XText>::get()) + { + lcl_queryInterface<text::XText>(pShape, aRet); + } + else if (rType == cppu::UnoType<css::text::XTextRange>::get()) + { + lcl_queryInterface<text::XTextRange>(pShape, aRet); + } + + return aRet; +} + +tools::Rectangle SwTextBoxHelper::getTextRectangle(SwFrameFormat* pShape, bool bAbsolute) +{ + tools::Rectangle aRet; + aRet.SetEmpty(); + auto pSdrShape = pShape->FindRealSdrObject(); + auto pCustomShape = dynamic_cast<SdrObjCustomShape*>(pSdrShape); + if (pCustomShape) + { + // Need to temporarily release the lock acquired in + // SdXMLShapeContext::AddShape(), otherwise we get an empty rectangle, + // see EnhancedCustomShapeEngine::getTextBounds(). + uno::Reference<document::XActionLockable> xLockable(pCustomShape->getUnoShape(), + uno::UNO_QUERY); + sal_Int16 nLocks = 0; + if (xLockable.is()) + nLocks = xLockable->resetActionLocks(); + pCustomShape->GetTextBounds(aRet); + if (nLocks) + xLockable->setActionLocks(nLocks); + } + else if (pSdrShape) + { + // fallback - get *any* bound rect we can possibly get hold of + aRet = pSdrShape->GetCurrentBoundRect(); + } + + if (!bAbsolute && pSdrShape) + { + // Relative, so count the logic (reference) rectangle, see the EnhancedCustomShape2d ctor. + Point aPoint(pSdrShape->GetSnapRect().Center()); + Size aSize(pSdrShape->GetLogicRect().GetSize()); + aPoint.AdjustX(-(aSize.Width() / 2)); + aPoint.AdjustY(-(aSize.Height() / 2)); + tools::Rectangle aLogicRect(aPoint, aSize); + aRet.Move(-1 * aLogicRect.Left(), -1 * aLogicRect.Top()); + } + + return aRet; +} + +void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, const OUString& rPropertyName, + const css::uno::Any& rValue) +{ + if (rPropertyName == "CustomShapeGeometry") + { + // CustomShapeGeometry changes the textbox position offset and size, so adjust both. + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any()); + + SdrObject* pObject = pShape->FindRealSdrObject(); + if (pObject) + { + tools::Rectangle aRectangle(pObject->GetSnapRect()); + syncProperty( + pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + uno::makeAny(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Left())))); + syncProperty( + pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + uno::makeAny(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Top())))); + } + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); + if (!pFormat) + return; + + comphelper::SequenceAsHashMap aCustomShapeGeometry(rValue); + auto it = aCustomShapeGeometry.find("TextPreRotateAngle"); + if (it == aCustomShapeGeometry.end()) + { + it = aCustomShapeGeometry.find("TextRotateAngle"); + } + + if (it != aCustomShapeGeometry.end()) + { + auto nAngle = it->second.has<sal_Int32>() ? it->second.get<sal_Int32>() : 0; + if (nAngle == 0) + { + nAngle = it->second.has<double>() ? it->second.get<double>() : 0; + } + + sal_Int16 nDirection = 0; + switch (nAngle) + { + case -90: + nDirection = text::WritingMode2::TB_RL; + break; + case -270: + nDirection = text::WritingMode2::BT_LR; + break; + } + + if (nDirection) + { + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(nDirection)); + } + } + } + else if (rPropertyName == UNO_NAME_TEXT_VERT_ADJUST) + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, rValue); + else if (rPropertyName == UNO_NAME_TEXT_AUTOGROWHEIGHT) + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, rValue); + else if (rPropertyName == UNO_NAME_TEXT_LEFTDIST) + syncProperty(pShape, RES_BOX, LEFT_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_RIGHTDIST) + syncProperty(pShape, RES_BOX, RIGHT_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_UPPERDIST) + syncProperty(pShape, RES_BOX, TOP_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_LOWERDIST) + syncProperty(pShape, RES_BOX, BOTTOM_BORDER_DISTANCE, rValue); + else if (rPropertyName == UNO_NAME_TEXT_WRITINGMODE) + { + text::WritingMode eMode; + if (rValue >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::makeAny(sal_Int16(eMode))); + } +} + +void SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + css::uno::Any& rValue) +{ + if (!pShape) + return; + + nMemberID &= ~CONVERT_TWIPS; + + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + if (nWID == RES_CHAIN) + { + switch (nMemberID) + { + case MID_CHAIN_PREVNAME: + case MID_CHAIN_NEXTNAME: + { + const SwFormatChain& rChain = pFormat->GetChain(); + rChain.QueryValue(rValue, nMemberID); + } + break; + case MID_CHAIN_NAME: + rValue <<= pFormat->GetName(); + break; + } + } + } +} + +void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + const css::uno::Any& rValue) +{ + // No shape yet? Then nothing to do, initial properties are set by create(). + if (!pShape) + return; + + uno::Any aValue(rValue); + nMemberID &= ~CONVERT_TWIPS; + + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) + { + OUString aPropertyName; + bool bAdjustX = false; + bool bAdjustY = false; + bool bAdjustSize = false; + switch (nWID) + { + case RES_HORI_ORIENT: + switch (nMemberID) + { + case MID_HORIORIENT_ORIENT: + aPropertyName = UNO_NAME_HORI_ORIENT; + break; + case MID_HORIORIENT_RELATION: + aPropertyName = UNO_NAME_HORI_ORIENT_RELATION; + break; + case MID_HORIORIENT_POSITION: + aPropertyName = UNO_NAME_HORI_ORIENT_POSITION; + bAdjustX = true; + break; + } + break; + case RES_LR_SPACE: + { + switch (nMemberID) + { + case MID_L_MARGIN: + aPropertyName = UNO_NAME_LEFT_MARGIN; + break; + case MID_R_MARGIN: + aPropertyName = UNO_NAME_RIGHT_MARGIN; + break; + } + break; + } + case RES_VERT_ORIENT: + switch (nMemberID) + { + case MID_VERTORIENT_ORIENT: + aPropertyName = UNO_NAME_VERT_ORIENT; + break; + case MID_VERTORIENT_RELATION: + aPropertyName = UNO_NAME_VERT_ORIENT_RELATION; + break; + case MID_VERTORIENT_POSITION: + aPropertyName = UNO_NAME_VERT_ORIENT_POSITION; + bAdjustY = true; + break; + } + break; + case RES_FRM_SIZE: + switch (nMemberID) + { + case MID_FRMSIZE_IS_AUTO_HEIGHT: + aPropertyName = UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT; + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + aPropertyName = UNO_NAME_RELATIVE_HEIGHT_RELATION; + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + aPropertyName = UNO_NAME_RELATIVE_WIDTH_RELATION; + break; + default: + aPropertyName = UNO_NAME_SIZE; + bAdjustSize = true; + break; + } + break; + case RES_ANCHOR: + switch (nMemberID) + { + case MID_ANCHOR_ANCHORTYPE: + if (aValue.get<text::TextContentAnchorType>() + == text::TextContentAnchorType_AS_CHARACTER) + { + uno::Reference<beans::XPropertySet> const xPropertySet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), + uno::UNO_QUERY); + xPropertySet->setPropertyValue( + UNO_NAME_SURROUND, uno::makeAny(text::WrapTextMode_THROUGH)); + return; + } + break; + } + break; + case FN_TEXT_RANGE: + { + uno::Reference<text::XTextRange> xRange; + rValue >>= xRange; + SwUnoInternalPaM aInternalPaM(*pFormat->GetDoc()); + if (sw::XTextRangeToSwPaM(aInternalPaM, xRange)) + { + SwFormatAnchor aAnchor(pFormat->GetAnchor()); + aAnchor.SetAnchor(aInternalPaM.Start()); + pFormat->SetFormatAttr(aAnchor); + } + } + break; + case RES_CHAIN: + switch (nMemberID) + { + case MID_CHAIN_PREVNAME: + aPropertyName = UNO_NAME_CHAIN_PREV_NAME; + break; + case MID_CHAIN_NEXTNAME: + aPropertyName = UNO_NAME_CHAIN_NEXT_NAME; + break; + } + break; + case RES_TEXT_VERT_ADJUST: + aPropertyName = UNO_NAME_TEXT_VERT_ADJUST; + break; + case RES_BOX: + switch (nMemberID) + { + case LEFT_BORDER_DISTANCE: + aPropertyName = UNO_NAME_LEFT_BORDER_DISTANCE; + break; + case RIGHT_BORDER_DISTANCE: + aPropertyName = UNO_NAME_RIGHT_BORDER_DISTANCE; + break; + case TOP_BORDER_DISTANCE: + aPropertyName = UNO_NAME_TOP_BORDER_DISTANCE; + break; + case BOTTOM_BORDER_DISTANCE: + aPropertyName = UNO_NAME_BOTTOM_BORDER_DISTANCE; + break; + } + break; + case RES_OPAQUE: + aPropertyName = UNO_NAME_OPAQUE; + break; + case RES_FRAMEDIR: + aPropertyName = UNO_NAME_WRITING_MODE; + break; + case RES_WRAP_INFLUENCE_ON_OBJPOS: + switch (nMemberID) + { + case MID_ALLOW_OVERLAP: + aPropertyName = UNO_NAME_ALLOW_OVERLAP; + break; + } + break; + } + + if (!aPropertyName.isEmpty()) + { + // Position/size should be the text position/size, not the shape one as-is. + if (bAdjustX || bAdjustY || bAdjustSize) + { + tools::Rectangle aRect = getTextRectangle(pShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + { + if (bAdjustX || bAdjustY) + { + sal_Int32 nValue; + if (aValue >>= nValue) + { + if (bAdjustX) + nValue += TWIPS_TO_MM(aRect.getX()); + else if (bAdjustY) + nValue += TWIPS_TO_MM(aRect.getY()); + aValue <<= nValue; + } + } + else if (bAdjustSize) + { + awt::Size aSize(TWIPS_TO_MM(aRect.getWidth()), + TWIPS_TO_MM(aRect.getHeight())); + aValue <<= aSize; + } + } + } + + uno::Reference<beans::XPropertySet> const xPropertySet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); + xPropertySet->setPropertyValue(aPropertyName, aValue); + } + } +} + +void SwTextBoxHelper::saveLinks(const SwFrameFormats& rFormats, + std::map<const SwFrameFormat*, const SwFrameFormat*>& rLinks) +{ + for (const auto pFormat : rFormats) + { + if (SwFrameFormat* pTextBox = getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT)) + rLinks[pFormat] = pTextBox; + } +} + +void SwTextBoxHelper::restoreLinks(std::set<ZSortFly>& rOld, std::vector<SwFrameFormat*>& rNew, + SavedLink& rSavedLinks) +{ + std::size_t i = 0; + for (const auto& rIt : rOld) + { + auto aTextBoxIt = rSavedLinks.find(rIt.GetFormat()); + if (aTextBoxIt != rSavedLinks.end()) + { + std::size_t j = 0; + for (const auto& rJt : rOld) + { + if (rJt.GetFormat() == aTextBoxIt->second) + rNew[i]->SetFormatAttr(rNew[j]->GetContent()); + ++j; + } + } + ++i; + } +} + +void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet) +{ + if (SwFrameFormat* pFormat = getOtherTextBoxFormat(&rShape, RES_DRAWFRMFMT)) + { + SfxItemSet aTextBoxSet(pFormat->GetDoc()->GetAttrPool(), aFrameFormatSetRange); + + SfxItemIter aIter(rSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if (rShape.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + SwFormatAnchor pShapeAnch = rShape.GetAnchor(); + aTextBoxSet.Put(pShapeAnch); + } + + switch (pItem->Which()) + { + case RES_VERT_ORIENT: + { + auto& rOrient = static_cast<const SwFormatVertOrient&>(*pItem); + SwFormatVertOrient aOrient(rOrient); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.getY()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + aOrient.SetRelationOrient(rShape.GetVertOrient().GetRelationOrient()); + } + aTextBoxSet.Put(aOrient); + + // restore height (shrunk for extending beyond the page bottom - tdf#91260) + SwFormatFrameSize aSize(pFormat->GetFrameSize()); + if (!aRect.IsEmpty()) + { + aSize.SetHeight(aRect.getHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + case RES_HORI_ORIENT: + { + auto& rOrient = static_cast<const SwFormatHoriOrient&>(*pItem); + SwFormatHoriOrient aOrient(rOrient); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.getX()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + aOrient.SetRelationOrient(rShape.GetHoriOrient().GetRelationOrient()); + } + aTextBoxSet.Put(aOrient); + } + break; + case RES_FRM_SIZE: + { + // In case the shape got resized, then we need to adjust both + // the position and the size of the textbox (e.g. larger + // rounded edges of a rectangle -> need to push right/down the + // textbox). + SwFormatVertOrient aVertOrient(rShape.GetVertOrient()); + SwFormatHoriOrient aHoriOrient(rShape.GetHoriOrient()); + SwFormatFrameSize aSize(pFormat->GetFrameSize()); + + tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false); + if (!aRect.IsEmpty()) + { + aVertOrient.SetPos(aVertOrient.GetPos() + aRect.getY()); + aTextBoxSet.Put(aVertOrient); + + aHoriOrient.SetPos(aHoriOrient.GetPos() + aRect.getX()); + aTextBoxSet.Put(aHoriOrient); + + aSize.SetWidth(aRect.getWidth()); + aSize.SetHeight(aRect.getHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncFlyFrameAttr: unhandled which-id: " + << pItem->Which()); + break; + } + + pItem = aIter.NextItem(); + } while (pItem && (0 != pItem->Which())); + + if (aTextBoxSet.Count()) + pFormat->GetDoc()->SetFlyFrameAttr(*pFormat, aTextBoxSet); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/doc/visiturl.cxx b/sw/source/core/doc/visiturl.cxx new file mode 100644 index 000000000..1e284f1d8 --- /dev/null +++ b/sw/source/core/doc/visiturl.cxx @@ -0,0 +1,125 @@ +/* -*- 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 <sfx2/docfile.hxx> +#include <svl/inethist.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <visiturl.hxx> +#include <hints.hxx> +#include <ndtxt.hxx> +#include <editsh.hxx> +#include <docsh.hxx> + +SwURLStateChanged::SwURLStateChanged( SwDoc* pD ) + : pDoc( pD ) +{ + StartListening( *INetURLHistory::GetOrCreate() ); +} + +SwURLStateChanged::~SwURLStateChanged() +{ + EndListening( *INetURLHistory::GetOrCreate() ); +} + +void SwURLStateChanged::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + if( dynamic_cast<const INetURLHistoryHint*>(&rHint) && pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // This URL has been changed: + const INetURLObject* pIURL = static_cast<const INetURLHistoryHint&>(rHint).GetObject(); + OUString sURL( pIURL->GetMainURL( INetURLObject::DecodeMechanism::NONE ) ), sBkmk; + + SwEditShell* pESh = pDoc->GetEditShell(); + + if( pDoc->GetDocShell() && pDoc->GetDocShell()->GetMedium() && + // If this is our Doc, we can also have local jumps! + pDoc->GetDocShell()->GetMedium()->GetName() == sURL ) + sBkmk = "#" + pIURL->GetMark(); + + bool bAction = false, bUnLockView = false; + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + const SwFormatINetFormat* pFormatItem = dynamic_cast<const SwFormatINetFormat*>(pItem); + if( pFormatItem != nullptr && + ( pFormatItem->GetValue() == sURL || ( !sBkmk.isEmpty() && pFormatItem->GetValue() == sBkmk ))) + { + const SwTextINetFormat* pTextAttr = pFormatItem->GetTextINetFormat(); + if (pTextAttr != nullptr) + { + const SwTextNode* pTextNd = pTextAttr->GetpTextNode(); + if (pTextNd != nullptr) + { + if( !bAction && pESh ) + { + pESh->StartAllAction(); + bAction = true; + bUnLockView = !pESh->IsViewLocked(); + pESh->LockView( true ); + } + const_cast<SwTextINetFormat*>(pTextAttr)->SetVisitedValid(false); + const SwTextAttr* pAttr = pTextAttr; + SwUpdateAttr aUpdateAttr( + pAttr->GetStart(), + *pAttr->End(), + RES_FMT_CHG); + + const_cast< SwTextNode* >(pTextNd)->ModifyNotification(&aUpdateAttr, &aUpdateAttr); + } + } + } + } + + if( bAction ) + pESh->EndAllAction(); + if( bUnLockView ) + pESh->LockView( false ); + } +} + +// Check if the URL has been visited before. Via the Doc, if only one Bookmark is set +// We need to put the Doc's name before it! +bool SwDoc::IsVisitedURL( const OUString& rURL ) +{ + bool bRet = false; + if( !rURL.isEmpty() ) + { + INetURLHistory *pHist = INetURLHistory::GetOrCreate(); + if( '#' == rURL[0] && mpDocShell && mpDocShell->GetMedium() ) + { + INetURLObject aIObj( mpDocShell->GetMedium()->GetURLObject() ); + aIObj.SetMark( rURL.copy( 1 ) ); + bRet = pHist->QueryUrl( aIObj ); + } + else + bRet = pHist->QueryUrl( rURL ); + + // We also want to be informed about status updates in the History + if( !mpURLStateChgd ) + { + SwDoc* pD = this; + pD->mpURLStateChgd.reset( new SwURLStateChanged( this ) ); + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |