diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/doc | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/doc')
66 files changed, 64322 insertions, 0 deletions
diff --git a/sw/source/core/doc/CntntIdxStore.cxx b/sw/source/core/doc/CntntIdxStore.cxx new file mode 100644 index 0000000000..6b82ebc20c --- /dev/null +++ b/sw/source/core/doc/CntntIdxStore.cxx @@ -0,0 +1,470 @@ +/* -*- 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 <bookmark.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 + + const int BEFORE_NODE = 0; // Position before the given node index + const int BEFORE_SAME_NODE = 1; // Same node index but content index before given content index + const int SAME_POSITION = 2; // Same node index and samecontent index + const int BEHIND_SAME_NODE = 3; // Same node index but content index behind given content index + const int BEHIND_NODE = 4; // Position behind the given node index + + int lcl_RelativePosition( const SwPosition& rPos, SwNodeOffset nNode, sal_Int32 nContent ) + { + SwNodeOffset nIndex = rPos.GetNodeIndex(); + int nReturn = BEFORE_NODE; + if( nIndex == nNode ) + { + const sal_Int32 nCntIdx = rPos.GetContentIndex(); + 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 + { + tools::Long 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.Assign(*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 + { + if( nContent < m_nCorrLen ) + { + nContent = std::min( nContent, static_cast<sal_Int32>(m_nLen) ); + } + else + { + nContent -= m_nCorrLen; + } + rPos.Assign( *m_pNewContentNode, nContent ); + }; + }; + 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 bool Empty() override + { + return m_aBkmkEntries.empty() && m_aRedlineEntries.empty() && m_aFlyEntries.empty() && m_aUnoCursorEntries.empty() && m_aShellCursorEntries.empty(); + } + virtual void Save(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent, bool bSaveFlySplit=false) override + { + SaveBkmks(rDoc, nNode, nContent); + SaveRedlines(rDoc, nNode, nContent); + SaveFlys(rDoc, nNode, nContent, bSaveFlySplit); + SaveUnoCursors(rDoc, nNode, nContent); + SaveShellCursors(rDoc, nNode, nContent); + } + virtual void Restore(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nOffset=0, bool bAuto = false, bool bAtStart = false, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = rDoc.GetNodes()[ nNode ]->GetContentNode(); + updater_t aUpdater = OffsetUpdater(pCNd, nOffset); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(rDoc, aUpdater); + RestoreRedlines(rDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(rDoc, aUpdater, bAuto, bAtStart); + } + } + virtual void Restore(SwNode& rNd, sal_Int32 nLen, sal_Int32 nCorrLen, RestoreMode eMode = RestoreMode::All) override + { + SwContentNode* pCNd = rNd.GetContentNode(); + SwDoc& rDoc = rNd.GetDoc(); + updater_t aUpdater = LimitUpdater(pCNd, nLen, nCorrLen); + if (eMode & RestoreMode::NonFlys) + { + RestoreBkmks(rDoc, aUpdater); + RestoreRedlines(rDoc, aUpdater); + RestoreUnoCursors(aUpdater); + RestoreShellCursors(aUpdater); + } + if (eMode & RestoreMode::Flys) + { + RestoreFlys(rDoc, aUpdater, false, false); + } + } + + private: + void SaveBkmks(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent); + void RestoreBkmks(SwDoc& rDoc, updater_t const & rUpdater); + void SaveRedlines(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent); + void RestoreRedlines(SwDoc& rDoc, updater_t const & rUpdater); + void SaveFlys(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent, bool bSaveFlySplit); + void RestoreFlys(SwDoc& rDoc, updater_t const & rUpdater, bool bAuto, bool bAtStart); + void SaveUnoCursors(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent); + void RestoreUnoCursors(updater_t const & rUpdater); + void SaveShellCursors(SwDoc& rDoc, SwNodeOffset 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 SwNodeOffset nNode, const sal_Int32 nContent, SwPaM& rPaM, const bool bGetPoint, bool bSetMark) + { + const SwPosition* pPos = &rPaM.GetBound(bGetPoint); + if( pPos->GetNodeIndex() == nNode && pPos->GetContentIndex() < nContent ) + { + const PaMEntry aEntry = { &rPaM, bSetMark, pPos->GetContentIndex() }; + rPaMEntries.push_back(aEntry); + } + } + void lcl_ChkPaMBoth( std::vector<PaMEntry>& rPaMEntries, const SwNodeOffset 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 SwNodeOffset 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& rDoc, SwNodeOffset nNode, sal_Int32 nContent) +{ + IDocumentMarkAccess* const pMarkAccess = rDoc.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().GetNodeIndex() == nNode + && pBkmk->GetMarkPos().GetContentIndex() <= nContent) + { + if(pBkmk->GetMarkPos().GetContentIndex() < nContent) + { + const MarkEntry aEntry = { static_cast<tools::Long>(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().GetContentIndex() }; + 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().GetNodeIndex() == nNode + && pBkmk->GetOtherMarkPos().GetContentIndex() <= nContent) + { + if(bMarkPosEqual) + { // the other position is before, the (main) position is equal + const MarkEntry aEntry = { static_cast<tools::Long>(ppBkmk - pMarkAccess->getAllMarksBegin()), false, pBkmk->GetMarkPos().GetContentIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + const MarkEntry aEntry = { static_cast<tools::Long>(ppBkmk - pMarkAccess->getAllMarksBegin()), true, pBkmk->GetOtherMarkPos().GetContentIndex() }; + m_aBkmkEntries.push_back(aEntry); + } + } +} + +void ContentIdxStoreImpl::RestoreBkmks(SwDoc& rDoc, updater_t const & rUpdater) +{ + IDocumentMarkAccess* const pMarkAccess = rDoc.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& rDoc, SwNodeOffset nNode, sal_Int32 nContent) +{ + SwRedlineTable const & rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + tools::Long 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()->GetContentIndex() }; + 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()->GetContentIndex() }; + m_aRedlineEntries.push_back(aEntry); + } + ++nIdx; + } +} + +void ContentIdxStoreImpl::RestoreRedlines(SwDoc& rDoc, updater_t const & rUpdater) +{ + const SwRedlineTable& rRedlTable = rDoc.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& rDoc, SwNodeOffset nNode, sal_Int32 nContent, bool bSaveFlySplit) +{ + SwContentNode *pNode = rDoc.GetNodes()[nNode]->GetContentNode(); + if( !pNode ) + return; + SwFrame* pFrame = pNode->getLayoutFrame( rDoc.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 : *rDoc.GetSpzFrameFormats()) + { + if ( RES_FLYFRMFMT == pFrameFormat->Which() || RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + SwNode const*const pAnchorNode = rAnchor.GetAnchorNode(); + if ( pAnchorNode && ( nNode == pAnchorNode->GetIndex() ) && + ( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId() || + RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId() ) ) + { + bool bSkip = false; + aSave.m_bOther = false; + aSave.m_nContent = rAnchor.GetAnchorContentOffset(); + 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& rDoc, updater_t const & rUpdater, bool bAuto, bool bAtStart) +{ + sw::SpzFrameFormats* pSpz = rDoc.GetSpzFrameFormats(); + for (const MarkEntry& aEntry : m_aFlyEntries) + { + if(!aEntry.m_bOther) + { + sw::SpzFrameFormat* pFrameFormat = (*pSpz)[ aEntry.m_nIdx ]; + const SwFormatAnchor& rFlyAnchor = pFrameFormat->GetAnchor(); + if( rFlyAnchor.GetContentAnchor() ) + { + if(bAtStart && RndStdIds::FLY_AT_PARA == rFlyAnchor.GetAnchorId()) + continue; + 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 ]; + const SfxPoolItem* pAnchor = &pFrameFormat->GetAnchor(); + pFrameFormat->CallSwClientNotify(sw::LegacyModifyHint(pAnchor, pAnchor)); + } + } +} + +void ContentIdxStoreImpl::SaveUnoCursors(SwDoc& rDoc, SwNodeOffset nNode, sal_Int32 nContent) +{ + rDoc.cleanupUnoCursorTable(); + for (const auto& pWeakUnoCursor : rDoc.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& rDoc, SwNodeOffset nNode, sal_Int32 nContent) +{ + SwCursorShell* pShell = rDoc.GetEditShell(); + if (!pShell) + return; + for(SwViewShell& rCurShell : pShell->GetRingContainer()) + { + if( auto pCursorShell = dynamic_cast<SwCursorShell *>(&rCurShell) ) + { + SwPaM *_pStackCursor = pCursorShell->GetStackCursor(); + if( _pStackCursor ) + for (;;) + { + lcl_ChkPaMBoth( m_aShellCursorEntries, nNode, nContent, *_pStackCursor); + if (!_pStackCursor) + break; + _pStackCursor = _pStackCursor->GetNext(); + if (_pStackCursor == pCursorShell->GetStackCursor()) + break; + } + + for(SwPaM& rPaM : pCursorShell->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 0000000000..e559892abf --- /dev/null +++ b/sw/source/core/doc/DocumentChartDataProviderManager.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 <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 ) +{ + +} + +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) + return; + + 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 0000000000..09e0a1233e --- /dev/null +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -0,0 +1,5520 @@ +/* -*- 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 <DocumentRedlineManager.hxx> +#include <wrtsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentRedlineAccess.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 <bookmark.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <hints.hxx> +#include <fmtflcnt.hxx> +#include <docedt.hxx> +#include <frameformats.hxx> +#include <formatflysplit.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <i18nutil/transliteration.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& rDoc, SwNodeOffset nSttNd, SwNodeOffset nEndNd, + SwNodeOffset nInsNd ) + { + + for(sw::SpzFrameFormat* pFormat: *rDoc.GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((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 <= pAnchorNode->GetIndex() && + pAnchorNode->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( rDoc, pSNd->GetIndex(), + pSNd->EndOfSectionIndex(), nInsNd ) ) + // Do not copy ! + return true; + } + } + + return false; + } + + SwNodeIndex InitDelCount(SwPaM const& rSourcePaM, SwNodeOffset & rDelCount) + { + SwPosition const& rStart(*rSourcePaM.Start()); + // Special handling for SwDoc::AppendDoc + if (rSourcePaM.GetDoc().GetNodes().GetEndOfExtras().GetIndex() + 1 + == rStart.GetNodeIndex()) + { + rDelCount = SwNodeOffset(1); + return SwNodeIndex(rStart.GetNode(), +1); + } + else + { + rDelCount = SwNodeOffset(0); + return SwNodeIndex(rStart.GetNode()); + } + } + + /* + 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 SwNodeOffset nNewIdx, SwNodeOffset& rDelCount ) + { + SwNodeOffset nStart = rPam.Start()->GetNodeIndex(); + SwNodeOffset nEnd = rPam.End()->GetNodeIndex(); + 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, + SwNodeOffset nDelCount ) + { + SwNodeOffset nNdOff = rOrigPos.GetNodeIndex(); + nNdOff -= rOrigStt.GetNodeIndex(); + nNdOff -= nDelCount; + sal_Int32 nContentPos = rOrigPos.GetContentIndex(); + + // Always adjust <nNode> at to be changed <SwPosition> instance <rChgPos> + rChgPos.Assign( nNdOff + rCpyStt.GetNodeIndex() ); + if (!rChgPos.GetNode().GetContentNode()) + return; + if( !nNdOff ) + { + // just adapt the content index + if( nContentPos > rOrigStt.GetContentIndex() ) + nContentPos -= rOrigStt.GetContentIndex(); + else + nContentPos = 0; + nContentPos += rCpyStt.GetContentIndex(); + } + rChgPos.SetContent( nContentPos ); + } + +} + +namespace sw +{ + // TODO: use SaveBookmark (from DelBookmarks) + void CopyBookmarks(const SwPaM& rPam, const SwPosition& rCpyPam, SwCopyFlags flags) + { + const SwDoc& rSrcDoc = rPam.GetDoc(); + SwDoc& rDestDoc = rCpyPam.GetDoc(); + const IDocumentMarkAccess* const pSrcMarkAccess = rSrcDoc.getIDocumentMarkAccess(); + ::sw::UndoGuard const undoGuard(rDestDoc.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. + bool const isIncludeStart( + (rStt.GetContentIndex() == 0 // paragraph start selected? + // also: only if inserting at the start - cross reference + // marks require index to be 0, and there could be one + // on the target node already + && rCpyPam.GetContentIndex() == 0) + || rMarkStart != rStt); + bool const isIncludeEnd( + (rEnd.GetNode().IsTextNode() // paragraph end selected? + && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len()) + || rMarkEnd != rEnd); + const bool bIsNotOnBoundary = + pMark->IsExpanded() + ? (isIncludeStart || isIncludeEnd) // rMarkStart != rMarkEnd + : (isIncludeStart && isIncludeEnd); // 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... + SwNodeOffset nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + for(const sw::mark::IMark* const pMark : vMarksToCopy) + { + SwPaM aTmpPam(*pCpyStt); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetMarkPos().GetNodeIndex(), nDelCount); + lcl_SetCpyPos( pMark->GetMarkPos(), rStt, *pCpyStt, *aTmpPam.GetPoint(), nDelCount); + if(pMark->IsExpanded()) + { + aTmpPam.SetMark(); + lcl_NonCopyCount(rPam, aCorrIdx, pMark->GetOtherMarkPos().GetNodeIndex(), nDelCount); + lcl_SetCpyPos(pMark->GetOtherMarkPos(), rStt, *pCpyStt, *aTmpPam.GetMark(), nDelCount); + } + + OUString sRequestedName = pMark->GetName(); + if (flags & SwCopyFlags::IsMoveToFly) + { + assert(&rSrcDoc == &rDestDoc); + // Ensure the name can be given to NewMark, since this is ultimately a move + auto pSoonToBeDeletedMark = const_cast<sw::mark::IMark*>(pMark); + rDestDoc.getIDocumentMarkAccess()->renameMark(pSoonToBeDeletedMark, + sRequestedName + "COPY_IS_MOVE"); + } + + ::sw::mark::IMark* const pNewMark = rDestDoc.getIDocumentMarkAccess()->makeMark( + aTmpPam, + sRequestedName, + 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 + if (pNewMark == nullptr) + { + assert(IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK + || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK); + continue; // can't insert duplicate cross reference mark + } + rDestDoc.getIDocumentMarkAccess()->renameMark(pNewMark, sRequestedName); + + // 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()); + pNewBookmark->Hide(pOldBookmark->IsHidden()); + pNewBookmark->SetHideCondition(pOldBookmark->GetHideCondition()); + } + ::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& rSrcDoc = rPam.GetDoc(); + const SwRedlineTable& rTable = rSrcDoc.getIDocumentRedlineAccess().GetRedlineTable(); + if( rTable.empty() ) + return; + + SwDoc& rDestDoc = rCpyPam.GetDoc(); + SwPosition* pCpyStt = rCpyPam.Start(), *pCpyEnd = rCpyPam.End(); + std::unique_ptr<SwPaM> pDelPam; + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + // We have to count the "non-copied" nodes + SwNodeOffset nDelCount; + SwNodeIndex aCorrIdx(InitDelCount(rPam, nDelCount)); + + SwRedlineTable::size_type n = 0; + rSrcDoc.getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for( ; n < rTable.size(); ++n ) + { + const SwRangeRedline* pRedl = rTable[ n ]; + if( RedlineType::Delete == pRedl->GetType() && pRedl->IsVisible() ) + { + auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition* + + 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->GetNodeIndex(), nDelCount ); + lcl_SetCpyPos( *pRStt, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + pDelPam->SetMark(); + + if( *pEnd < *pREnd ) + *pDelPam->GetPoint() = *pCpyEnd; + else + { + lcl_NonCopyCount( rPam, aCorrIdx, pREnd->GetNodeIndex(), nDelCount ); + lcl_SetCpyPos( *pREnd, *pStt, *pCpyStt, + *pDelPam->GetPoint(), nDelCount ); + } + + if (pDelPam->GetNext() != pDelPam.get() + && *pDelPam->GetNext()->End() == *pDelPam->Start()) + { + *pDelPam->GetNext()->End() = *pDelPam->End(); + pDelPam.reset(pDelPam->GetNext()); + } + } + } + } + } + + if( !pDelPam ) + return; + + RedlineFlags eOld = rDestDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::Ignore ); + + ::sw::UndoGuard const undoGuard(rDestDoc.GetIDocumentUndoRedo()); + + // At this point, pDelPam points to the last of maybe several disjoint selections, organized + // in reverse order in document (so every GetNext() returns a PaM closer to document start, + // until wrap to pDelPam). Removal of the selections must be from last in document to first, + // to avoid situations when another PaM in chain points into the node that will be destroyed + // (joined to previous) by removal of the currently processed PaM. + do { + rDestDoc.getIDocumentContentOperations().DeleteAndJoin(*pDelPam); + if( !pDelPam->IsMultiSelection() ) + break; + pDelPam.reset(pDelPam->GetNext()); + } while( true ); + + rDestDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_DeleteRedlines( const SwNodeRange& rRg, SwNodeRange const & rCpyRg ) + { + SwDoc& rSrcDoc = rRg.aStart.GetNode().GetDoc(); + if( !rSrcDoc.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()->GetNode().GetTextNode(); + const SwTextNode* pEndTextNd = rPam.End()->GetNode().GetTextNode(); + if ( pTextNd && pTextNd->IsInList() && + pEndTextNd && pEndTextNd->IsInList() ) + { + bRet = true; + SwNodeIndex aIdx(rPam.Start()->GetNode()); + + 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; + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + if (nullptr != pStt && nullptr != pEnd) + { + const SwTextNode* pSttNd = pStt->GetNode().GetTextNode(); + const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode(); + + if (nullptr != pSttNd && nullptr != pEndNd && + pStt->GetContentIndex() == 0 && + pEnd->GetContentIndex() == pEndNd->Len()) + { + bResult = true; + } + } + + return bResult; + } +} + +//local functions originally from sw/source/core/doc/docedt.cxx +namespace sw +{ + void CalcBreaks(std::vector<std::pair<SwNodeOffset, sal_Int32>> & rBreaks, + SwPaM const & rPam, bool const isOnlyFieldmarks) + { + SwNodeOffset const nStartNode(rPam.Start()->GetNodeIndex()); + SwNodeOffset const nEndNode(rPam.End()->GetNodeIndex()); + SwNodes const& rNodes(rPam.GetPoint()->GetNodes()); + IDocumentMarkAccess const& rIDMA(*rPam.GetDoc().getIDocumentMarkAccess()); + + std::stack<std::tuple<sw::mark::IFieldmark const*, bool, SwNodeOffset, sal_Int32>> startedFields; + + for (SwNodeOffset 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()->GetContentIndex() + : 0); + sal_Int32 const nEnd(n == nEndNode + ? rPam.End()->GetContentIndex() + : 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* pAttr(rTextNode.GetTextAttrForCharAt(i)); + if (pAttr && pAttr->End() && (nEnd < *pAttr->End())) + { + assert(pAttr->HasDummyChar()); + rBreaks.emplace_back(n, i); + } + + if (!pAttr) + { + // See if this is an end dummy character for a content control. + pAttr = rTextNode.GetTextAttrForEndCharAt(i, RES_TXTATR_CONTENTCONTROL); + if (pAttr && (nStart > pAttr->GetStart())) + { + 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()) + { + if (const sw::mark::IFieldmark* pMark = std::get<0>(startedFields.top())) + { + SwPosition const& rStart(pMark->GetMarkStart()); + std::pair<SwNodeOffset, sal_Int32> const pos( + rStart.GetNodeIndex(), rStart.GetContentIndex()); + 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<SwNodeOffset, sal_Int32> const posSep( + std::get<2>(startedFields.top()), + std::get<3>(startedFields.top())); + auto 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, SwDeleteFlags const flags, + bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags)) + { + std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks; + + sw::CalcBreaks(Breaks, rPam); + + if (Breaks.empty()) + { + return (rDocumentContentOperations.*pFunc)(rPam, flags); + } + + // 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() ); + SwNodeOffset nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + + while (iter != Breaks.rend()) + { + rStart.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags); + nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes... + } + rEnd.Assign(*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, flags); + } + + 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()->GetNode() != rPam.GetMark()->GetNode() ) + { + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + const SwTextNode* pEndNd = pEnd->GetNode().GetTextNode(); + if( (nullptr != pEndNd) && pStt->GetNode().IsTextNode() ) + { + const sal_uInt64 nSum = pStt->GetContentIndex() + + pEndNd->GetText().getLength() - pEnd->GetContentIndex(); + return nSum > o3tl::make_unsigned(SAL_MAX_INT32); + } + } + return false; + } + + struct SaveRedline + { + SwRangeRedline* pRedl; + SwNodeOffset nStt, nEnd; + sal_Int32 nSttCnt; + sal_Int32 nEndCnt; + + SaveRedline( SwRangeRedline* pR, const SwNodeIndex& rSttIdx ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + auto [pStt, pEnd] = pR->StartEnd(); // SwPosition* + SwNodeOffset nSttIdx = rSttIdx.GetIndex(); + nStt = pStt->GetNodeIndex() - nSttIdx; + nSttCnt = pStt->GetContentIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->GetNodeIndex() - nSttIdx; + nEndCnt = pEnd->GetContentIndex(); + } + + pRedl->GetPoint()->Assign( SwNodeOffset(0) ); + pRedl->GetMark()->Assign( SwNodeOffset(0) ); + } + + SaveRedline( SwRangeRedline* pR, const SwPosition& rPos ) + : pRedl(pR) + , nEnd(0) + , nEndCnt(0) + { + auto [pStt, pEnd] = pR->StartEnd(); // SwPosition* + SwNodeOffset nSttIdx = rPos.GetNodeIndex(); + nStt = pStt->GetNodeIndex() - nSttIdx; + nSttCnt = pStt->GetContentIndex(); + if( nStt == SwNodeOffset(0) ) + nSttCnt = nSttCnt - rPos.GetContentIndex(); + if( pR->HasMark() ) + { + nEnd = pEnd->GetNodeIndex() - nSttIdx; + nEndCnt = pEnd->GetContentIndex(); + if( nEnd == SwNodeOffset(0) ) + nEndCnt = nEndCnt - rPos.GetContentIndex(); + } + + pRedl->GetPoint()->Assign( SwNodeOffset(0) ); + pRedl->GetMark()->Assign( SwNodeOffset(0) ); + } + + void SetPos( SwNodeOffset nInsPos ) + { + pRedl->GetPoint()->Assign( nInsPos + nStt, nSttCnt ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->Assign( nInsPos + nEnd, nEndCnt ); + } + } + + void SetPos( const SwPosition& aPos ) + { + pRedl->GetPoint()->Assign( aPos.GetNodeIndex() + nStt, + nSttCnt + ( nStt == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) ); + if( pRedl->HasMark() ) + { + pRedl->GetMark()->Assign( aPos.GetNodeIndex() + nEnd, + nEndCnt + ( nEnd == SwNodeOffset(0) ? aPos.GetContentIndex() : 0 ) ); + } + } + }; + + typedef std::vector< SaveRedline > SaveRedlines_t; + + void lcl_SaveRedlines(const SwPaM& aPam, SaveRedlines_t& rArr) + { + SwDoc& rDoc = aPam.GetPointNode().GetDoc(); + + auto [pStart, pEnd] = aPam.StartEnd(); // SwPosition* + + // get first relevant redline + SwRedlineTable::size_type nCurrentRedline; + rDoc.getIDocumentRedlineAccess().GetRedline( *pStart, &nCurrentRedline ); + if( nCurrentRedline > 0) + nCurrentRedline--; + + // redline mode RedlineFlags::Ignore|RedlineFlags::On; save old mode + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.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 = rDoc.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; + rDoc.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; + rDoc.getIDocumentRedlineAccess().AppendRedline( pNewRedline, true ); + } + + // save the current redline + rArr.emplace_back( pCurrent, *pStart ); + } + } + + // restore old redline mode + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc& rDoc, const SwPosition& rPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( rPos ); + rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true ); + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_SaveRedlines(const SwNodeRange& rRg, SaveRedlines_t& rArr) + { + SwDoc& rDoc = rRg.aStart.GetNode().GetDoc(); + SwRedlineTable::size_type nRedlPos; + SwPosition aSrchPos( rRg.aStart ); + aSrchPos.Adjust(SwNodeOffset(-1)); + if( rDoc.getIDocumentRedlineAccess().GetRedline( aSrchPos, &nRedlPos ) && nRedlPos ) + --nRedlPos; + else if( nRedlPos >= rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ) + return ; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwRedlineTable& rRedlTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + + do { + SwRangeRedline* pTmp = rRedlTable[ nRedlPos ]; + + auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition* + + if( pRStt->GetNode() < rRg.aStart.GetNode() ) + { + if( pREnd->GetNode() > rRg.aStart.GetNode() && pREnd->GetNode() < rRg.aEnd.GetNode() ) + { + // 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->Assign(rRg.aStart); + + rArr.emplace_back(pNewRedl, rRg.aStart); + + pTmpPos = pTmp->End(); + pTmpPos->Assign(rRg.aEnd); + } + else if( pREnd->GetNode() == rRg.aStart.GetNode() ) + { + SwPosition* pTmpPos = pTmp->End(); + pTmpPos->Assign(rRg.aEnd); + } + } + else if( pRStt->GetNode() < rRg.aEnd.GetNode() ) + { + rRedlTable.Remove( nRedlPos-- ); + if( pREnd->GetNode() < rRg.aEnd.GetNode() || + ( pREnd->GetNode() == rRg.aEnd.GetNode() && !pREnd->GetContentIndex()) ) + { + // move everything + rArr.emplace_back( pTmp, rRg.aStart ); + } + else + { + // split + SwRangeRedline* pNewRedl = new SwRangeRedline( *pTmp ); + SwPosition* pTmpPos = pNewRedl->End(); + pTmpPos->Assign(rRg.aEnd); + + rArr.emplace_back( pNewRedl, rRg.aStart ); + + pTmpPos = pTmp->Start(); + pTmpPos->Assign(rRg.aEnd); + rDoc.getIDocumentRedlineAccess().AppendRedline( pTmp, true ); + } + } + else + break; + + } while( ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + void lcl_RestoreRedlines(SwDoc& rDoc, SwNodeOffset const nInsPos, SaveRedlines_t& rArr) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + for(SaveRedline & rSvRedLine : rArr) + { + rSvRedLine.SetPos( nInsPos ); + IDocumentRedlineAccess::AppendResult const result( + rDoc.getIDocumentRedlineAccess().AppendRedline( rSvRedLine.pRedl, true )); + if ( IDocumentRedlineAccess::AppendResult::APPENDED == result && + rSvRedLine.pRedl->GetType() == RedlineType::Delete ) + { + UpdateFramesForAddDeleteRedline(rDoc, *rSvRedLine.pRedl); + } + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + + bool lcl_SaveFootnote( const SwNode& rSttNd, const SwNode& rEndNd, + const SwNode& rInsPos, + SwFootnoteIdxs& rFootnoteArr, SwFootnoteIdxs& rSaveArr, + std::optional<sal_Int32> oSttCnt = std::nullopt, std::optional<sal_Int32> oEndCnt = std::nullopt ) + { + 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( ( oEndCnt && oSttCnt ) + ? (( &rSttNd == pFootnoteNd && + *oSttCnt > nFootnoteSttIdx) || + ( &rEndNd == pFootnoteNd && + nFootnoteSttIdx >= *oEndCnt )) + : ( &rEndNd == pFootnoteNd )) + { + ++nPos; // continue searching + } + else + { + // delete it + if( bDelFootnote ) + { + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwContentIndex 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( !oEndCnt || !oSttCnt || + ! (( &rSttNd == pFootnoteNd && + *oSttCnt > nFootnoteSttIdx ) || + ( &rEndNd == pFootnoteNd && + nFootnoteSttIdx >= *oEndCnt )) ) + { + if( bDelFootnote ) + { + // delete it + SwTextNode& rTextNd = const_cast<SwTextNode&>(pSrch->GetTextNode()); + SwContentIndex 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, SwPosition &rIdx, sal_Int32 &rStart ) + { + if( !lcl_MayOverwrite( pNode, rStart ) ) + { + // skip all special attributes + do { + rIdx.AdjustContent(+1); + rStart = rIdx.GetContentIndex(); + } while (rStart < pNode->GetText().getLength() + && !lcl_MayOverwrite(pNode, rStart) ); + } + } + + bool lcl_GetTokenToParaBreak( OUString& rStr, OUString& rRet, bool bRegExpRplc ) + { + if( bRegExpRplc ) + { + sal_Int32 nPos = 0; + static constexpr OUString sPara(u"\\n"_ustr); + 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->HasMergedParas() && 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->HasMergedParas() + || 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; } + + // set format redline with extra data for lcl_InsAttr() + void lcl_SetRedline( + SwDoc& rDoc, + const SwPaM &rRg) + { + std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra; + + // check existing redline on the same range, and use its extra data, if it exists + SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( + rRg.Start()->GetNode(), RedlineType::Format ); + if( SwRedlineTable::npos != nRedlPos ) + { + const SwPosition *pRStt, *pREnd; + do { + SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + pRStt = pTmp->Start(); + pREnd = pTmp->End(); + SwComparePosition eCompare = ComparePosition( *rRg.Start(), *rRg.End(), *pRStt, *pREnd ); + if ( eCompare == SwComparePosition::Inside || eCompare == SwComparePosition::Equal ) + { + if (pTmp->GetExtraData()) + { + const SwRedlineExtraData* pExtraData = pTmp->GetExtraData(); + const SwRedlineExtraData_FormatColl* pFormattingChanges = + dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData); + // Check if the extra data is of type 'formatting changes' + if (pFormattingChanges) + { + // Get the item set that holds all the changes properties + const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet(); + xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, pChangesSet)); + break; + } + } + } + } while( pRStt <= rRg.Start() && ++nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size()); + } + + SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::Format, rRg ); + auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline( pRedline, true)); + // store original text attributes to reject formatting change + if (IDocumentRedlineAccess::AppendResult::IGNORED == result) + return; + + // no existing format redline in the range + if (!xExtra) + { + // Apply the first character's attributes to the ReplaceText + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( rDoc.GetAttrPool() ); + SwTextNode * pNode = rRg.Start()->GetNode().GetTextNode(); + pNode->GetParaAttr( aSet, rRg.Start()->GetContentIndex() + 1, rRg.End()->GetContentIndex() ); + + 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 ); + + // After GetParaAttr aSet can contain invalid/dontcare items (true == IsInvalidItem, + // DONTCARE == SfxItemState), e.g. RES_TXTATR_CHARFMT and (a copy of) this + // SfxItemSet can be passed to MSWordExportBase::OutputItemSet + // which doesn't handle invalid/dontcare items so clear them here + aSet.ClearInvalidItems(); + + xExtra.reset(new SwRedlineExtraData_FormatColl("", USHRT_MAX, &aSet)); + } + + if (xExtra) + { + pRedline->SetExtraData(xExtra.get() ); + } + } + + // create format redline(s) for the given range: + // to track the original formatting stored in the + // hints, create redlines for all parts of the + // range partitioned by boundaries of the hints. + void lcl_SetRedlines( + SwDoc& rDoc, + const SwPaM &rRg) + { + SwNodeIndex aIdx( rRg.Start()->GetNode() ); + const SwNodeIndex aEndNd( rRg.End()->GetNode() ); + while( aIdx <= aEndNd ) + { + SwTextNode *pNode = aIdx.GetNode().GetTextNode(); + if( pNode ) + { + const sal_Int32 nStart = aIdx == rRg.Start()->GetNode() + ? rRg.Start()->GetContentIndex() + : 0; + const sal_Int32 nEnd = aIdx < aEndNd + ? pNode->GetText().getLength() + : rRg.End()->GetContentIndex(); + + if( SwpHints *pHints = pNode->GetpSwpHints() ) + { + const size_t nCount = pHints->Count(); + sal_Int32 nRedEnd = nStart; + for( size_t i = 0; i < nCount; ++i ) + { + SwTextAttr *pAttr = pHints->Get( i ); + + if ( pAttr->GetStart() > nEnd ) + { + break; // after the range + } + + if ( !pAttr->GetEnd() || *pAttr->GetEnd() < nStart ) + { + continue; // before the range + } + + // range part before the hint + if ( nRedEnd < pAttr->GetStart() ) + { + SwPaM aPam( *pNode, nRedEnd, *pNode, pAttr->GetStart() ); + lcl_SetRedline(rDoc, aPam); + } + + // range part at the hint + sal_Int32 nRedStart = std::max(pAttr->GetStart(), nStart); + nRedEnd = std::min(*pAttr->GetEnd(), nEnd); + SwPaM aPam2( *pNode, nRedStart, *pNode, nRedEnd ); + lcl_SetRedline(rDoc, aPam2); + } + + // range part after the last hint + if ( nRedEnd < nEnd ) + { + SwPaM aPam( *pNode, nRedEnd, *pNode, nEnd ); + lcl_SetRedline(rDoc, aPam); + } + } + else + { + SwPaM aPam( *pNode, nStart, *pNode, nEnd ); + lcl_SetRedline(rDoc, aPam); + } + } + ++aIdx; + } + } + + /// Insert Hints according to content types; + // Is used in SwDoc::Insert(..., SwFormatHint &rHt) + + bool lcl_InsAttr( + SwDoc& rDoc, + const SwPaM &rRg, + const SfxItemSet& rChgSet, + const SetAttrMode nFlags, + SwUndoAttr *const pUndo, + SwRootFrame const*const pLayout, + 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 SfxItemSetFixed< + RES_CHRATR_BEGIN, RES_CHRATR_END - 1, + RES_TXTATR_AUTOFMT, RES_TXTATR_CHARFMT, + RES_TXTATR_UNKNOWN_CONTAINER, + RES_TXTATR_UNKNOWN_CONTAINER>( rDoc.GetAttrPool() ); + + SfxItemSet* pTmpOtherItemSet = new SfxItemSetFixed< + RES_PARATR_BEGIN, RES_GRFATR_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1, + // FillAttribute support: + XATTR_FILL_FIRST, XATTR_FILL_LAST>( rDoc.GetAttrPool() ); + + 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->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->GetNode().GetContentNode(); + SwContentNode* pCurrentNode = pEndNode; + auto nStartIndex = pNode->GetIndex(); + auto nEndIndex = pEndNode->GetIndex(); + SwNodeIndex aIdx( pEnd->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->GetContentIndex() == 0) && + (pCurrentNode->GetIndex() < nEndIndex || pEnd->GetContentIndex() == pEndNode->Len())) + { + pCurrentNode->ResetAttr(RES_PARATR_LIST_AUTOFMT); + // reset also paragraph marker + pCurrentNode->GetTextNode()->RstTextAttr(pCurrentNode->Len(), 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(o3tl::narrowing<sal_uInt16>(nLevel)); + SwCharFormat * pCharFormat = + rDoc.FindCharFormatByName(aNumFormat.GetCharFormatName()); + + if (pCharFormat) + { + if (pHistory) + pHistory->AddCharFormat(pCharFormat->GetAttrSet(), *pCharFormat); + + if ( pCharSet ) + pCharFormat->SetFormatAttr(*pCharSet); + } + + DELETECHARSETS + return true; + } + + // Attributes without an end do not have a range + if ( !bCharAttr && !bOtherAttr ) + { + SfxItemSetFixed<RES_TXTATR_NOEND_BEGIN, RES_TXTATR_NOEND_END-1> + aTextSet( rDoc.GetAttrPool() ); + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( + aTextSet, pStt->GetContentIndex(), pStt->GetContentIndex(), nFlags, /*ppNewTextAttr*/nullptr ) || bRet; + + if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + SwPaM aPam( pStt->GetNode(), pStt->GetContentIndex()-1, + pStt->GetNode(), pStt->GetContentIndex() ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, true ); + + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aPam ), true); + else + rDoc.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! + SfxItemSetFixed< + RES_TXTATR_REFMARK, RES_TXTATR_METAFIELD, + RES_TXTATR_CJK_RUBY, RES_TXTATR_CJK_RUBY, + RES_TXTATR_INPUTFIELD, RES_TXTATR_CONTENTCONTROL> + aTextSet(rDoc.GetAttrPool()); + + aTextSet.Put( rChgSet ); + if( aTextSet.Count() ) + { + const sal_Int32 nInsCnt = pStt->GetContentIndex(); + const sal_Int32 nEnd = pStt->GetNode() == pEnd->GetNode() + ? pEnd->GetContentIndex() + : pNode->Len(); + SwRegHistory history( pNode, *pNode, pHistory ); + bRet = history.InsertItems( aTextSet, nInsCnt, nEnd, nFlags, ppNewTextAttr ) + || bRet; + + if (bRet && (rDoc.getIDocumentRedlineAccess().IsRedlineOn() || (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()))) + { + // Was text content inserted? (RefMark/TOXMarks without an end) + bool bTextIns = nInsCnt != pStt->GetContentIndex(); + // Was content inserted or set over the selection? + SwPaM aPam( pStt->GetNode(), bTextIns ? nInsCnt + 1 : nEnd, + pStt->GetNode(), nInsCnt ); + if( pUndo ) + pUndo->SaveRedlineData( aPam, bTextIns ); + + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline( + bTextIns ? RedlineType::Insert : RedlineType::Format, aPam ), + true); + else if( bTextIns ) + rDoc.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 = pOtherSet->GetItemIfSet( RES_PAGEDESC, false ); + if( 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->HasMergedParas()) + { + pFirstNode = sw::GetFirstAndLastNode(*pLayout, pStt->GetNode()).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() ) && + (pBreak = pOtherSet->GetItemIfSet( RES_BREAK, false )) ) + { + 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 + sal_uInt16 nPoolId=0; + const SwNumRuleItem* pRule = pOtherSet->GetItemIfSet( RES_PARATR_NUMRULE, false ); + if( pRule && + !rDoc.FindNumRulePtr( pRule->GetValue() ) && + USHRT_MAX != (nPoolId = SwStyleNameMapper::GetPoolIdFromUIName ( pRule->GetValue(), + SwGetPoolIdFromName::NumRule )) ) + rDoc.getIDocumentStylePoolAccess().GetNumRuleFromPool( nPoolId ); + } + } + + SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(rDoc.GetAttrPool()); + if (pOtherSet && pOtherSet->Count()) + { // actually only RES_BREAK is possible here... + firstSet.Put(*pOtherSet); + } + SfxItemSetFixed + <RES_PARATR_BEGIN, RES_PAGEDESC, + RES_BREAK+1, RES_FRMATR_END, + XATTR_FILL_FIRST, XATTR_FILL_LAST+1> propsSet(rDoc.GetAttrPool()); + 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(); + sal_Int32 nMkPos, nPtPos = pStt->GetContentIndex(); + 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(pStt->GetContentIndex(), 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 = pStt->GetContentIndex(); + } + + // 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()) + { + if( pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNd, pHistory ); + pTextNd->GetpSwpHints()->Register( &aRHst ); + pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet ); + if( pTextNd->GetpSwpHints() ) + pTextNd->GetpSwpHints()->DeRegister(); + } + else + pTextNd->RstTextAttr( 0, nPtPos, 0, pCharSet ); + } + + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPaM aPam( *pNode, nMkPos, *pNode, nPtPos ); + + if( pUndo ) + pUndo->SaveRedlineData( aPam, false ); + + lcl_SetRedlines(rDoc, aPam); + } + + // the SwRegHistory inserts the attribute into the TextNode! + SwRegHistory history( pNode, *pNode, pHistory ); + + bRet = history.InsertItems( *pCharSet, nMkPos, nPtPos, nFlags, /*ppNewTextAttr*/nullptr ) + || bRet; + + } + 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); + + rDoc.CheckForUniqueItemForLineFillNameOrIndex(aTempLocalCopy); + bRet = lcl_ApplyOtherSet(*pNode, pHistory, aTempLocalCopy, firstSet, propsSet, pLayout) || bRet; + } + + DELETECHARSETS + return bRet; + } + + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() && pCharSet && pCharSet->Count() ) + { + if( pUndo ) + pUndo->SaveRedlineData( rRg, false ); + + lcl_SetRedlines(rDoc, rRg); + } + + /* now if range */ + sal_uLong nNodes = 0; + + SwNodeIndex aSt( rDoc.GetNodes() ); + SwNodeIndex aEnd( rDoc.GetNodes() ); + SwContentIndex aCntEnd( pEnd->GetContentNode(), pEnd->GetContentIndex() ); + + if( pNode ) + { + const sal_Int32 nLen = pNode->Len(); + if( pStt->GetNode() != pEnd->GetNode() ) + aCntEnd.Assign( pNode, nLen ); + + if( pStt->GetContentIndex() != 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->GetContentIndex(), 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->GetNode() == pEnd->GetNode() ) + { + DELETECHARSETS + return bRet; + } + ++nNodes; + aSt.Assign( pStt->GetNode(), +1 ); + } + else + aSt = pStt->GetNode(); + aCntEnd.Assign(pEnd->GetContentNode(), pEnd->GetContentIndex()); // aEnd was changed! + } + else + aSt.Assign( pStt->GetNode(), +1 ); + + // aSt points to the first full Node now + + /* + * The selection spans more than one Node. + */ + if( pStt->GetNode() < pEnd->GetNode() ) + { + pNode = pEnd->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->GetNode(); + } + else + aEnd.Assign( pEnd->GetNode(), +1 ); + } + else + aEnd = pEnd->GetNode(); + } + else + aEnd.Assign( pEnd->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); + rDoc.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->HasMergedParas() + && 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()) + { + if (SwpHints *pSwpHints = bCreateSwpHints ? &pTNd->GetOrCreateSwpHints() + : pTNd->GetpSwpHints()) + { + pSwpHints->Register( &aRegH ); + } + + pTNd->SetAttr(*pCharSet, 0, pTNd->GetText().getLength(), nFlags); + + // re-fetch as it may be deleted by SetAttr + if (SwpHints *pSwpHints = pTNd->GetpSwpHints()) + 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; + } + } + + DELETECHARSETS + return (nNodes != 0) || bRet; + } +} + +namespace sw +{ + +namespace mark +{ + bool IsFieldmarkOverlap(SwPaM const& rPaM) + { + std::vector<std::pair<SwNodeOffset, 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, + sal_uInt32 nMovedID) const +{ + const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); + + SwDoc& rDoc = rPos.GetNode().GetDoc(); + bool bColumnSel = rDoc.IsClipBoard() && rDoc.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 (&rDoc == &m_rDoc && (flags & SwCopyFlags::CheckPosInFly)) + { + // Correct the Start-/EndNode + SwNodeOffset nStt = pStt->GetNodeIndex(), + nEnd = pEnd->GetNodeIndex(), + nDiff = nEnd - nStt +1; + SwNode* pNd = m_rDoc.GetNodes()[ nStt ]; + if( pNd->IsContentNode() && pStt->GetContentIndex() ) + { + ++nStt; + --nDiff; + } + if( (pNd = m_rDoc.GetNodes()[ nEnd ])->IsContentNode() && + static_cast<SwContentNode*>(pNd)->Len() != pEnd->GetContentIndex() ) + { + --nEnd; + --nDiff; + } + if( nDiff && + lcl_ChkFlyFly( rDoc, nStt, nEnd, rPos.GetNodeIndex() ) ) + { + return false; + } + } + + SwPaM* pRedlineRange = nullptr; + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + (!rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) ) + pRedlineRange = new SwPaM( rPos ); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + + bool bRet = false; + + if( &rDoc != &m_rDoc ) + { // ordinary copy + bRet = CopyImpl(rPam, rPos, flags & ~SwCopyFlags::CheckPosInFly, pRedlineRange); + } + else if( ! ( *pStt <= rPos && rPos < *pEnd && + ( pStt->GetNode() != pEnd->GetNode() || + !pStt->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"); + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + if( pRedlineRange ) + { + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + rDoc.getIDocumentRedlineAccess().AppendRedline( + new SwRangeRedline(RedlineType::Insert, *pRedlineRange, nMovedID), true); + else + rDoc.getIDocumentRedlineAccess().SplitRedline( *pRedlineRange ); + delete pRedlineRange; + } + + return bRet; +} + +static auto GetCorrPosition(SwPaM const& rPam) -> SwPosition +{ + // tdf#152710 target position must be on node that survives deletion + // so that PaMCorrAbs can invalidate SwUnoCursors properly + return rPam.GetPoint()->GetNode().IsContentNode() + ? *rPam.GetPoint() + : rPam.GetMark()->GetNode().IsContentNode() + ? *rPam.GetMark() + // this would be the result in SwNodes::RemoveNode() + : SwPosition(rPam.End()->GetNode(), SwNodeOffset(+1)); +} + +/// 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.GetNode(), aEndIdx.GetNode() ); + m_rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSttNd, true, RedlineType::Any ); + DelBookmarks(aSttIdx.GetNode(), aEndIdx.GetNode()); + + { + // move all Cursor/StackCursor/UnoCursor out of the to-be-deleted area + SwPaM const range(aSttIdx, aEndIdx); + SwPosition const pos(GetCorrPosition(range)); + ::PaMCorrAbs(range, pos); + } + + 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()->AdjustContent(+1); + assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy); + (void) cDummy; + + DeleteRangeImpl(aPam, SwDeleteFlags::Default); + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } +} + +void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam ) +{ + // Seek all redlines that are in that PaM to be deleted.. + SwRedlineTable::size_type nRedlStart = m_rDoc.getIDocumentRedlineAccess().GetRedlinePos( + rPam.Start()->GetNode(), RedlineType::Any); + SwRedlineTable::size_type nRedlEnd = m_rDoc.getIDocumentRedlineAccess().GetRedlineEndPos( + nRedlStart, rPam.End()->GetNode(), RedlineType::Any); + + lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl); + + // update all redlines was in the Pam that is + m_rDoc.getIDocumentRedlineAccess().UpdateRedlineContentNode(nRedlStart, nRedlEnd); + + 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.GetNode(); + SwNodeOffset nSectDiff = pNd->StartOfSectionNode()->EndOfSectionIndex() - + pNd->StartOfSectionIndex(); + SwNodeOffset nNodeDiff = rEnd.GetNodeIndex() - rStt.GetNodeIndex(); + + if ( nSectDiff-SwNodeOffset(2) <= nNodeDiff || m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() || + /* #i9185# Prevent getting the node after the end node (see below) */ + rEnd.GetNodeIndex() + 1 == m_rDoc.GetNodes().Count() ) + { + return false; + } + + { + SwPaM temp(rPam, nullptr); + if (!temp.HasMark()) + { + temp.SetMark(); + } + if (SwTextNode *const pNode = temp.Start()->GetNode().GetTextNode()) + { // rPam may not have nContent set but IsFieldmarkOverlap requires it + temp.Start()->AssignStartIndex(*pNode); + } + if (SwTextNode *const pNode = temp.End()->GetNode().GetTextNode()) + { + temp.End()->AssignEndIndex(*pNode); + } + 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 breaks to the following Node. + bool bSavePageBreak = false, bSavePageDesc = false; + + /* #i9185# This would lead to a segmentation fault if not caught above. */ + SwNodeOffset nNextNd = rEnd.GetNodeIndex() + 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()->Adjust(SwNodeOffset(1)); + + SwContentNode *pTmpNode = rPam.GetPoint()->GetNode().GetContentNode(); + bool bGoNext = (nullptr == pTmpNode); + + if (rPam.GetMark()->GetContentNode()) + rPam.GetMark()->SetContent( 0 ); + + m_rDoc.GetIDocumentUndoRedo().ClearRedo(); + + SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); + { + SwPosition aTmpPos( *aDelPam.GetPoint() ); + if( bGoNext ) + { + m_rDoc.GetNodes().GoNext( &aTmpPos ); + } + ::PaMCorrAbs( aDelPam, aTmpPos ); + } + + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true)); + + *rPam.GetPoint() = *aDelPam.GetPoint(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + rPam.DeleteMark(); + } + else + { + SwNodeRange aRg( rStt.GetNode(), rEnd.GetNode() ); + 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; + } + } + + // must delete all fieldmarks before CorrAbs(), or they'll remain + // moved to wrong node without their CH_TXT_ATR_FIELD* + // (note: deleteMarks() doesn't help here, in case of partially + // selected fieldmarks; let's delete these as re-inserting their chars + // elsewhere looks difficult) + ::std::set<::sw::mark::IFieldmark*> fieldmarks; + for (SwNodeIndex i = aRg.aStart; i <= aRg.aEnd; ++i) + { + if (SwTextNode *const pTextNode = i.GetNode().GetTextNode()) + { + for (sal_Int32 j = 0; j < pTextNode->GetText().getLength(); ++j) + { + switch (pTextNode->GetText()[j]) + { + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDEND: + fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getFieldmarkAt(SwPosition(*pTextNode, j))); + break; + case CH_TXT_ATR_FIELDSEP: + fieldmarks.insert(m_rDoc.getIDocumentMarkAccess()->getInnerFieldmarkFor(SwPosition(*pTextNode, j))); + break; + } + } + } + } + for (auto const pFieldMark : fieldmarks) + { + m_rDoc.getIDocumentMarkAccess()->deleteMark(pFieldMark); + } + + // move bookmarks, redlines etc. + if (aRg.aStart == aRg.aEnd) // only first CorrAbs variant handles this + { + m_rDoc.CorrAbs( aRg.aStart.GetNode(), *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 ) + { + sw::SpzFrameFormat* pFly = (*m_rDoc.GetSpzFrameFormats())[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((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 <= *pAnchorNode && *pAnchorNode <= aRg.aEnd.GetNode() ) + { + m_rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFly ); + --n; + } + } + } + + rPam.DeleteMark(); + m_rDoc.GetNodes().Delete( aRg.aStart, nNodeDiff+1 ); + } + + if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() + && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + m_rDoc.getIDocumentRedlineAccess().CompressRedlines(); + } + + m_rDoc.getIDocumentState().SetModified(); + + return true; +} + +bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags) +{ + if ( lcl_StrLenOverflow( rPam ) ) + return false; + + bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl + : &DocumentContentOperationsManager::DeleteAndJoinImpl ); + + 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->GetNode(), + pEnd->GetNode(), + nullptr, + pStt->GetContentIndex(), + pEnd->GetContentIndex()); + } + + 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->GetNode(), pEnd->GetNode(), rPos.GetNode(), + m_rDoc.GetFootnoteIdxs(), aTmpFntIdx, + pStt->GetContentIndex(), pEnd->GetContentIndex() ); + } + + 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()->GetNode().GetTextNode(); + bool bCorrSavePam = pSrcNd && pStt->GetNode() != pEnd->GetNode(); + + // If one or 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.GetNode().GetTextNode(); + if( pTNd && rPaM.GetPoint()->GetNode() != rPaM.GetMark()->GetNode() && + ( rPos.GetContentIndex() || ( pTNd->Len() && bCorrSavePam )) ) + { + bSplit = true; + const sal_Int32 nMkContent = rPaM.GetMark()->GetContentIndex(); + + const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save( m_rDoc, rPos.GetNodeIndex(), rPos.GetContentIndex(), true ); + + SwTextNode * pOrigNode = pTNd; + assert(*aSavePam.GetPoint() == *aSavePam.GetMark() && + *aSavePam.GetPoint() == rPos); + assert(aSavePam.GetPoint()->GetContentNode() == pOrigNode); + assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode()); + assert(rPos.GetNodeIndex() == pOrigNode->GetIndex()); + + std::function<void (SwTextNode *, sw::mark::RestoreMode, bool)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(m_rDoc, pOrigNode->GetIndex()-SwNodeOffset(1), 0, true, false, eMode); + } + }); + pTNd->SplitContentNode(rPos, &restoreFunc); + + //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()->GetContentNode() == pOrigNode); + assert(aSavePam.GetPoint()->GetNode() == rPos.GetNode()); + assert(rPos.GetNodeIndex() == pOrigNode->GetIndex()); + aSavePam.GetPoint()->SetContent(0); + rPos = *aSavePam.GetMark() = *aSavePam.GetPoint(); + + // correct the PaM! + if( rPos.GetNode() == rPaM.GetMark()->GetNode() ) + { + rPaM.GetMark()->Assign( rPos.GetNodeIndex() - SwNodeOffset(1) ); + rPaM.GetMark()->SetContent( 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()->GetContentIndex() == 0; + if( bNullContent ) + { + aSavePam.GetPoint()->Adjust(SwNodeOffset(-1)); + } + 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->GetNode(), + pEnd->GetNode(), + &aSaveBkmks, + pStt->GetContentIndex(), + pEnd->GetContentIndex()); + + // 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()->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.GetPointNode().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()->Adjust(SwNodeOffset(1)); + } + 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()->GetNode(), + rPaM.GetMark()->GetContentIndex()); + *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.GetNode() ); + + // 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, SwNode& rDestNd, + 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, rDestNd )); + } + else + { + bUpdateFootnote = lcl_SaveFootnote( rRange.aStart.GetNode(), rRange.aEnd.GetNode(), rDestNd, + 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( rDestNd, 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->GetNode() == rDestNd && pRStt->GetNode() < rDestNd ) + { + aSavRedlInsPosArr.push_back( pTmp ); + } + } while( pRStt->GetNode() < rDestNd && ++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.GetNode(), rRange.aEnd.GetNode(), &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( rDestNd, -1 ); + + std::optional<SwNodeIndex> oSaveInsPos; + if( pUndo ) + oSaveInsPos.emplace(rRange.aStart, -1 ); + + // move the Nodes + bool bNoDelFrames = bool(SwMoveFlags::NO_DELFRMS & eMvFlags); + if( m_rDoc.GetNodes().MoveNodes( rRange, m_rDoc.GetNodes(), rDestNd, !bNoDelFrames ) ) + { + ++aIdx; // again back to old position + if( oSaveInsPos ) + ++(*oSaveInsPos); + } + 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.GetNode()); + + if( !aSavRedlInsPosArr.empty() ) + { + for(SwRangeRedline* pTmp : aSavRedlInsPosArr) + { + if( m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().Contains( pTmp ) ) + { + SwPosition* pEnd = pTmp->End(); + pEnd->Assign(aIdx); + } + } + } + + if( !aSaveRedl.empty() ) + lcl_RestoreRedlines( m_rDoc, aIdx.GetIndex(), aSaveRedl ); + + if( pUndo ) + { + pUndo->SetDestRange( aIdx.GetNode(), rDestNd, *oSaveInsPos ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + oSaveInsPos.reset(); + + if( bUpdateFootnote ) + { + if( !aTmpFntIdx.empty() ) + { + m_rDoc.GetFootnoteIdxs().insert( aTmpFntIdx ); + aTmpFntIdx.clear(); + } + + m_rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + } + + m_rDoc.getIDocumentState().SetModified(); + return true; +} + +void DocumentContentOperationsManager::MoveAndJoin( SwPaM& rPaM, SwPosition& rPos ) +{ + SwNodeIndex aIdx( rPaM.Start()->GetNode() ); + bool bJoinText = aIdx.GetNode().IsTextNode(); + bool bOneNode = rPaM.GetPoint()->GetNode() == rPaM.GetMark()->GetNode(); + --aIdx; // in front of the move area! + + bool bRet = MoveRange( rPaM, rPos, SwMoveFlags::DEFAULT ); + if( !bRet || bOneNode ) + return; + + if( bJoinText ) + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aNxtIdx( aIdx ); + if( pTextNd && pTextNd->CanJoinNext( &aNxtIdx ) ) + { + { // Block so SwContentIndex into node is deleted before Join + m_rDoc.CorrRel( aNxtIdx.GetNode(), + SwPosition( *pTextNd, pTextNd->GetText().getLength() ), + 0, true ); + } + pTextNd->JoinNext(); + } +} + +// 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.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 ); + sal_Int32 const nActualStart(rPt.GetContentIndex()); + 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 = rPt.GetContentIndex(); + if (nStart < pNode->GetText().getLength()) + { + lcl_SkipAttr( pNode, rPt, 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()) + rPt.AdjustContent(+1); + pNode->InsertText( OUString(c), rPt, SwInsertFlags::EMPTYEXPAND ); + if( nStart+1 < rPt.GetContentIndex() ) + { + rPt.SetContent(nStart); + pNode->EraseText( rPt, 1 ); + rPt.AdjustContent(+1); + } + } + } + pNode->SetIgnoreDontExpand( bOldExpFlg ); + + const size_t nNewAttrCnt = pNode->GetpSwpHints() + ? pNode->GetpSwpHints()->Count() : 0; + if( nOldAttrCnt != nNewAttrCnt ) + { + const SwUpdateAttr aHint(0,0,0); + pNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint)); + } + + if (!m_rDoc.GetIDocumentUndoRedo().DoesUndo() && + !m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + SwPaM aPam(rPt.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex()); + 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.GetNode(), nActualStart, rPt.GetNode(), rPt.GetContentIndex()); + 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.GetNode().GetTextNode(); + if(!pNode) + return false; + + SwDataChanged aTmp( rRg ); + + if (!bDoesUndo || !m_rDoc.GetIDocumentUndoRedo().DoesGroupUndo()) + { + OUString const ins(pNode->InsertText(rStr, rPos, nInsertMode)); + if (bDoesUndo) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsert>(rPos.GetNode(), + rPos.GetContentIndex(), 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.GetContentIndex(); + + if (!pUndo) + { + pUndo = new SwUndoInsert( rPos.GetNode(), nInsPos, 0, nInsertMode, + !rCC.isLetterNumeric( rStr, 0 ) ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + } + + OUString const ins(pNode->InsertText(rStr, rPos, 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.GetNode(), 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.GetNode(), aTmp.GetContent(), + rPos.GetNode(), rPos.GetContentIndex()); + 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::SetIME(bool bIME) +{ + m_bIME = bIME; +} + +bool DocumentContentOperationsManager::GetIME() const +{ + return m_bIME; +} + +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 )); + + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + SwNodeOffset nSttNd = pStt->GetNodeIndex(), + nEndNd = pEnd->GetNodeIndex(); + sal_Int32 nSttCnt = pStt->GetContentIndex(); + sal_Int32 nEndCnt = pEnd->GetContentIndex(); + + SwTextNode* pTNd = pStt->GetNode().GetTextNode(); + bool bNoSelection = (pStt == pEnd) && pTNd; // no selection? + if ( bNoSelection ) + { + /* Check if cursor is inside of a word */ + 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 ) + { + /* Cursor is inside of a word */ + if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) { + /* set current sentence as 'area of effect' */ + nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence( + pTNd->GetText(), nSttCnt, + g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) ); + nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence( + pTNd->GetText(), nEndCnt, + g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) ); + } else { + /* Set current word as 'area of effect' */ + nSttCnt = aBndry.startPos; + nEndCnt = aBndry.endPos; + } + } else { + /* Cursor is not inside of a word. Nothing should happen. */ + /* Except in the case of change tracking, when the cursor is at the end of the change */ + /* Recognize and reject the previous deleted and inserted words to allow to cycle */ + IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess(); + if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) && + pStt->GetContentIndex() > 0 ) + { + SwPosition aPos(*pStt->GetContentNode(), pStt->GetContentIndex() - 1); + SwRedlineTable::size_type n = 0; + + const SwRangeRedline* pFnd = + rIDRA.GetRedlineTable().FindAtPosition( aPos, n ); + if ( pFnd && RedlineType::Insert == pFnd->GetType() && n > 0 ) + { + const SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[n-1]; + if ( RedlineType::Delete == pFnd2->GetType() && + m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() && + *pFnd2->End() == *pFnd->Start() && + pFnd->GetAuthor() == pFnd2->GetAuthor() ) + { + SwPosition aPos2(*pFnd2->End()); + rIDRA.RejectRedline(*pFnd, true); + rIDRA.RejectRedline(*pFnd2, true); + // positionate the text cursor inside the changed word to allow to cycle + if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>( + m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) ) + { + pWrtShell->GetCursor()->GetPoint()-> + Assign(*aPos2.GetContentNode(), aPos2.GetContentIndex() - 1); + } + } + } + } + return; + } + } + else + { + bool bHasTrackedChange = false; + IDocumentRedlineAccess& rIDRA = m_rDoc.getIDocumentRedlineAccess(); + if ( IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) && + pEnd->GetContentIndex() > 0 ) + { + // search all own redlines within the selected area + SwRedlineTable::size_type n = SwRedlineTable::npos; + const SwRedlineTable& aRedlineTable = rIDRA.GetRedlineTable(); + for( SwRedlineTable::size_type m = 0; m < aRedlineTable.size(); ++m ) + { + const SwRangeRedline* pRedline = aRedlineTable[ m ]; + + if ( *pRedline->Start() > *pEnd ) + break; + + if ( *pRedline->Start() >= *pStt ) + n = m; + } + + if ( n != SwRedlineTable::npos && n > 0 ) + { + SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>( + m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()); + + sal_Int32 nRejectedCharacters = 0; + SwRangeRedline* pFnd = rIDRA.GetRedlineTable()[n]; + SwRangeRedline* pFnd2 = rIDRA.GetRedlineTable()[--n]; + // loop on all redlines of a case changing, and reject them + while ( ( ( RedlineType::Insert == pFnd->GetType() && + RedlineType::Delete == pFnd2->GetType() ) || + ( RedlineType::Delete == pFnd->GetType() && + RedlineType::Insert == pFnd2->GetType() ) ) && + pWrtShell && + // use time stamp to recognize the multiple selections in the text, + // not only the changes from the same author within the (sometimes + // incomplete) selection + ( pFnd2->GetTimeStamp() == pFnd->GetTimeStamp() || + ( pStt->GetContentNode() < pFnd2->Start()->GetContentNode() || + ( pStt->GetContentNode() == pFnd2->Start()->GetContentNode() && + nSttCnt <= pFnd2->Start()->GetContentIndex() ) ) ) && + pFnd->GetAuthor() == pFnd2->GetAuthor() ) + { + bHasTrackedChange = true; + + if ( RedlineType::Insert == pFnd->GetType() ) + nRejectedCharacters += pFnd->GetText().getLength(); + + rIDRA.RejectRedline(*pFnd, true); + + pFnd = pFnd2; + if ( n == 0 ) + break; + pFnd2 = rIDRA.GetRedlineTable()[--n]; + } + + // remove the last item and restore the original selection within the node + if ( bHasTrackedChange ) + { + if ( nSttNd == nEndNd ) + { + pWrtShell->GetCursor()->GetPoint()-> + Assign(*rPaM.Start()->GetContentNode(), nSttCnt); + if ( nEndCnt >= nRejectedCharacters ) + pWrtShell->GetCursor()->GetMark()-> + Assign(*rPaM.End()->GetContentNode(), nEndCnt - nRejectedCharacters); + } + rIDRA.RejectRedline(*pFnd, true); + } + } + } + + // TODO handle title case to lowercase + if ( bHasTrackedChange ) + return; + } + + bool bUseRedlining = m_rDoc.getIDocumentRedlineAccess().IsRedlineOn(); + // as a workaround for a known performance problem, switch off redlining + // to avoid freezing, if transliteration could result too many redlines + if ( bUseRedlining ) + { + const sal_uLong nMaxRedlines = 500; + const bool bIsTitleCase = rTrans.getType() == TransliterationFlags::TITLE_CASE; + sal_uLong nAffectedNodes = 0; + sal_uLong nAffectedChars = nEndCnt; + SwNodeIndex aIdx( pStt->GetNode() ); + for( ; aIdx.GetIndex() <= nEndNd; ++aIdx ) + { + SwTextNode* pAffectedNode = aIdx.GetNode().GetTextNode(); + + // don't count not text nodes or empty text nodes + if( !pAffectedNode || pAffectedNode->GetText().isEmpty() ) + continue; + + nAffectedNodes++; + + // count characters of the node (the last - maybe partially + // selected - node was counted at initialization of nAffectedChars) + if( aIdx.GetIndex() < nEndNd ) + nAffectedChars += pAffectedNode->GetText().getLength(); + + // transliteration creates n redlines for n nodes, except in the + // case of title case, where it creates n redlines for n words + if( nAffectedNodes > nMaxRedlines || + // estimate word count based on the character count, where + // 6 = average English word length is ~5 letters + space + ( bIsTitleCase && (nAffectedChars - nSttCnt)/6 > nMaxRedlines ) ) + { + bUseRedlining = false; + break; + } + } + } + + if( nSttNd != nEndNd ) // is more than one text node involved? + { + // iterate over all affected text nodes, the first and the last one + // may be incomplete because the selection starts and/or ends there + + SwNodeIndex aIdx( pStt->GetNode() ); + if( nSttCnt ) + { + ++aIdx; + if( pTNd ) + { + pTNd->TransliterateText( + rTrans, nSttCnt, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining); + } + } + + for( ; aIdx.GetIndex() < nEndNd; ++aIdx ) + { + pTNd = aIdx.GetNode().GetTextNode(); + if (pTNd) + { + pTNd->TransliterateText( + rTrans, 0, pTNd->GetText().getLength(), pUndo.get(), bUseRedlining); + } + } + + if( nEndCnt && nullptr != ( pTNd = pEnd->GetNode().GetTextNode() )) + { + pTNd->TransliterateText( rTrans, 0, nEndCnt, pUndo.get(), bUseRedlining ); + } + } + else if( pTNd && nSttCnt < nEndCnt ) + { + pTNd->TransliterateText( rTrans, nSttCnt, nEndCnt, pUndo.get(), bUseRedlining ); + } + if( pUndo && pUndo->HasData() ) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + // restore selection after tracked changes + if ( !bNoSelection && bUseRedlining && nSttNd == nEndNd ) + { + if ( SwWrtShell *pWrtShell = dynamic_cast<SwWrtShell*>( + m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) ) + { + *pWrtShell->GetCursor()->GetMark() = *pWrtShell->GetCursor()->End(); + pWrtShell->GetCursor()->GetPoint()->Assign(*pStt->GetContentNode(), nSttCnt); + } + } + + 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( + m_rDoc.GetNodes().GetEndOfAutotext(), + rGrfName, rFltName, pGraphic, + 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( + 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( + 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()->GetNodeIndex() == rPam.GetMark()->GetNodeIndex() ) + && nullptr != ( pGrfNd = rPam.GetPoint()->GetNode().GetGrfNode() )) ) + return; + + 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 = rFlyAttrSet.GetItemIfSet( RES_ANCHOR, false ); + 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 SwPosition* pChkPos = nullptr; + if ( pAnchor == nullptr ) + { + pChkPos = rRg.GetPoint(); + } + else if ( bIsAtContent ) + { + pChkPos = + pAnchor->GetContentAnchor() ? pAnchor->GetContentAnchor() : rRg.GetPoint(); + } + + // allow drawing objects in header/footer, but control objects aren't allowed in header/footer. + if( pChkPos != nullptr + && ::CheckControlLayer( &rDrawObj ) + && m_rDoc.IsInHeaderFooter( pChkPos->GetNode() ) ) + { + // apply at-page anchor format + eAnchorId = RndStdIds::FLY_AT_PAGE; + pFormat->SetFormatAttr( SwFormatAnchor( eAnchorId ) ); + } + else if( pAnchor == nullptr + || ( bIsAtContent + && pAnchor->GetAnchorNode() == nullptr ) ) + { + // apply anchor format + SwFormatAnchor aAnch( pAnchor != nullptr ? *pAnchor : pFormat->GetAnchor() ); + eAnchorId = aAnch.GetAnchorId(); + if ( eAnchorId == RndStdIds::FLY_AT_FLY ) + { + const SwStartNode* pStartNode = rRg.GetPointNode().FindFlyStartNode(); + assert(pStartNode); + SwPosition aPos(*pStartNode); + 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.GetAnchorNode() != nullptr ) + { + SwTextNode* pAnchorTextNode = + rDrawObjAnchorFormat.GetAnchorNode()->GetTextNode(); + if ( pAnchorTextNode != nullptr ) + { + const sal_Int32 nStt = rDrawObjAnchorFormat.GetContentAnchor()->GetContentIndex(); + 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, SwNodeOffset(0), 0) ); + } + + m_rDoc.getIDocumentState().SetModified(); + return pFormat; +} + +bool DocumentContentOperationsManager::SplitNode( const SwPosition &rPos, bool bChkTableStart ) +{ + SwContentNode *pNode = rPos.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.GetNode().GetTextNode(); + const sal_Int32 nPos = rPos.GetContentIndex(); + 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.GetContentIndex() && pNode->IsTextNode() ) + { + SwNodeOffset nPrevPos = rPos.GetNodeIndex() - 1; + 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( + *pTableNd, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + if( pTextNd ) + { + const_cast<SwPosition&>(rPos).Assign( pTableNd->GetIndex() - SwNodeOffset(1) ); + + // 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.GetNodeIndex(), rPos.GetContentIndex(), true ); + assert(pNode->IsTextNode()); + std::function<void (SwTextNode *, sw::mark::RestoreMode, bool bAtStart)> restoreFunc( + [&](SwTextNode *const, sw::mark::RestoreMode const eMode, bool const bAtStart) + { + if (!pContentStore->Empty()) + { // move all bookmarks, TOXMarks, FlyAtCnt + pContentStore->Restore(m_rDoc, rPos.GetNodeIndex()-SwNodeOffset(1), 0, true, bAtStart && (eMode & sw::mark::RestoreMode::Flys), 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.GetNode().GetTextNode(); + if( !pCurNode ) + { + // so then one can be created! + SwNodeIndex aIdx( rPos.GetNode(), 1 ); + pCurNode = m_rDoc.GetNodes().MakeTextNode( aIdx.GetNode(), + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + } + else + pCurNode = pCurNode->AppendNode( rPos )->GetTextNode(); + + rPos.Adjust(SwNodeOffset(1)); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsert>( rPos.GetNode() ) ); + } + + // 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<SwNodeOffset, sal_Int32>> Breaks; + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + aPam.Normalize(false); + if (aPam.GetPoint()->GetNode() != aPam.GetMark()->GetNode()) + { + aPam.Move(fnMoveBackward); + } + OSL_ENSURE((aPam.GetPoint()->GetNode() == aPam.GetMark()->GetNode()), "invalid pam?"); + + sw::CalcBreaks(Breaks, aPam); + + while (!Breaks.empty() // skip over prefix of dummy chars + && (aPam.GetMark()->GetNodeIndex() == Breaks.begin()->first) + && (aPam.GetMark()->GetContentIndex() == Breaks.begin()->second)) + { + // skip! + aPam.GetMark()->AdjustContent(+1); // 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()->Assign(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() ); + SwNodeOffset nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->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.Assign(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); + if (rStart < rEnd) // check if part is empty + { + bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default) + : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default); + nOffset = iter->first - rStart.GetNodeIndex(); // deleted fly nodes... + } + rEnd.Assign(*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; +} + +bool DocumentContentOperationsManager::InsertPoolItem( + const SwPaM &rRg, + const SfxPoolItem &rHt, + const SetAttrMode nFlags, + SwRootFrame const*const pLayout, + SwTextAttr **ppNewTextAttr) +{ + 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, 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, /*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.GetNode().GetTextNode(); + if ( !pTNd ) + return; + + 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()->SetContent(0); + aPam.SetMark(); + aPam.GetMark()->SetContent(nIdx); + DeleteRange( aPam ); + } +} + +void DocumentContentOperationsManager::RemoveLeadingWhiteSpace(SwPaM& rPaM ) +{ + for (SwPaM& rSel :rPaM.GetRingContainer()) + { + SwNodeOffset nStt = rSel.Start()->GetNodeIndex(); + SwNodeOffset nEnd = rSel.End()->GetNodeIndex(); + for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++) + RemoveLeadingWhiteSpace(SwPosition(rSel.GetBound().GetNodes(), nPos)); + } +} + +// 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, + SwNode& 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()->GetNode() == rRg.aEnd.GetNode()); + assert(!pCopiedPaM || pCopiedPaM->second.GetNode() <= rInsPos); + + SwDoc& rDest = rInsPos.GetDoc(); + SwNodeIndex aSavePos( rInsPos ); + + if (rRg.aStart != rRg.aEnd) + { + bool bEndIsEqualEndPos = rInsPos == rRg.aEnd.GetNode(); + --aSavePos; + SaveRedlEndPosForRestore aRedlRest( rInsPos, 0 ); + + // insert behind the already copied start node + m_rDoc.GetNodes().CopyNodes( rRg, rInsPos, false, true ); + aRedlRest.Restore(); + + if (bEndIsEqualEndPos) + { + const_cast<SwNodeIndex&>(rRg.aEnd).Assign(aSavePos.GetNode(), +1); + } + } + + // Also copy all bookmarks + // guess this must be done before the DelDummyNodes below as that + // deletes nodes so would mess up the index arithmetic + // sw_fieldmarkhide: also needs to be done before making frames + if (m_rDoc.getIDocumentMarkAccess()->getAllMarksCount()) + { + SwPaM aRgTmp( rRg.aStart, rRg.aEnd ); + SwPosition targetPos(aSavePos, SwNodeOffset(rRg.aStart != rRg.aEnd ? +1 : 0)); + if (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode()) + { + // there is 1 (partially selected, maybe) paragraph before + assert(SwNodeIndex(rRg.aStart, -1) == pCopiedPaM->first.Start()->GetNode()); + // 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! + targetPos = pCopiedPaM->second; + } + + sw::CopyBookmarks(pCopiedPaM ? pCopiedPaM->first : aRgTmp, targetPos, flags); + } + + if (rRg.aStart != rRg.aEnd) + { + bool isRecreateEndNode(false); + if (bMakeNewFrames) // tdf#130685 only after aRedlRest + { // recreate from previous node (could be merged now) + o3tl::sorted_vector<SwTextFrame*> frames; + SwTextNode * pNode = aSavePos.GetNode().GetTextNode(); + SwTextNode *const pEndNode = rInsPos.GetTextNode(); + if (pEndNode) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pEndNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + frames.insert(pFrame); + // tdf#135061 check if end node is merged to a preceding node + if (pNode == nullptr && pFrame->GetMergedPara() + && pFrame->GetMergedPara()->pFirstNode->GetIndex() < aSavePos.GetIndex()) + { + pNode = pFrame->GetMergedPara()->pFirstNode; + } + } + } + } + if (pNode != nullptr) + { + 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()->HasMergedParas()) + { + 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, + SwNodeOffset((!isRecreateEndNode || isAtStartOfSection) + ? 0 : +1)); + ::MakeFrames(&rDest, aSavePos.GetNode(), end.GetNode()); + } + } + +#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 + SwNodeOffset 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(rDest.GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(rRg, pCopiedPaM ? &pCopiedPaM->first : nullptr, + // see comment below regarding use of pCopiedPaM->second + (pCopiedPaM && rRg.aStart != pCopiedPaM->first.Start()->GetNode()) + ? pCopiedPaM->second.GetNode() + : aSavePos.GetNode(), + bCopyFlyAtFly, + flags); + } + + SwNodeRange aCpyRange( aSavePos.GetNode(), rInsPos ); + + if( bDelRedlines && ( RedlineFlags::DeleteRedlines & rDest.getIDocumentRedlineAccess().GetRedlineFlags() )) + lcl_DeleteRedlines( rRg, aCpyRange ); + + rDest.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, + SwNode& rStartIdx, + const bool bCopyFlyAtFly, + SwCopyFlags const flags) const +{ + assert(!pCopiedPaM || pCopiedPaM->End()->GetNode() == rRg.aEnd.GetNode()); + + // 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& rDest = rStartIdx.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(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if ( !pAnchorNode ) + continue; + bool bAdd = false; + SwNodeOffset nSkipAfter = pAnchorNode->GetIndex(); + SwNodeOffset 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(*pAnchor->GetContentAnchor(), + 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(*pAnchor->GetContentAnchor(), + 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 (*pAnchorNode > rRg.aEnd.GetNode()) + 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 (*pAnchorNode < rRg.aEnd.GetNode()) + 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()->GetContentIndex(); + } + } + } + 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()->GetNode() : rRg.aStart.GetNode()); + while ( !bAnchorTextNdFound && aIdx <= rRg.aEnd ) + { + if ( aIdx.GetNode().IsTextNode() ) + { + ++nAnchorTextNdNumInRange; + bAnchorTextNdFound = *aAnchor.GetAnchorNode() == aIdx.GetNode(); + } + + ++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.Assign( aAnchorNdIdx ); + } + else + { + SwNodeOffset nOffset = newPos.GetNodeIndex() - rRg.aStart.GetIndex(); + newPos.Assign( rStartIdx, nOffset ); + } + // Set the character bound Flys back at the original character + if ((RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) && + newPos.GetNode().IsTextNode() ) + { + // only if pCopiedPaM: care about partially selected start node + sal_Int32 const nContent = pCopiedPaM && pCopiedPaM->Start()->GetNode() == *aAnchor.GetAnchorNode() + ? newPos.GetContentIndex() - pCopiedPaM->Start()->GetContentIndex() + : newPos.GetContentIndex(); + newPos.SetContent(nContent); + } + aAnchor.SetAnchor( &newPos ); + + // Check recursion: if copying content inside the same frame, then don't copy the format. + if( &rDest == &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( rDest.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() ) + return; + + size_t n = 0; + for (const auto& rFlyN : aSet) + { + const SwFrameFormat *pFormatN = rFlyN.GetFormat(); + const SwFormatChain &rChain = pFormatN->GetChain(); + 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]) ); + } + else if ( rChain.GetNext() == pFormatK ) + { + ::lcl_ChainFormats( static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[n]), + static_cast< SwFlyFrameFormat* >(aVecSwFrameFormat[k]) ); + } + ++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( SwNode* pNd, void* pArgs ) +{ + ParaRstFormat* pPara = static_cast<ParaRstFormat*>(pArgs); + if (pPara->pLayout && pPara->pLayout->HasMergedParas() + && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; // skip hidden, since new items aren't applied + } + SwTextNode * pTextNode = pNd->GetTextNode(); + if( pTextNode && pTextNode->GetpSwpHints() ) + { + SwContentIndex aSt( pTextNode, 0 ); + sal_Int32 nEnd = pTextNode->Len(); + + if( &pPara->pSttNd->GetNode() == pTextNode && + pPara->pSttNd->GetContentIndex() ) + aSt = pPara->pSttNd->GetContentIndex(); + + if( &pPara->pEndNd->GetNode() == pNd ) + nEnd = pPara->pEndNd->GetContentIndex(); + + if( pPara->pHistory ) + { + // Save all attributes for the Undo. + SwRegHistory aRHst( *pTextNode, pPara->pHistory ); + pTextNode->GetpSwpHints()->Register( &aRHst ); + pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + if( pTextNode->GetpSwpHints() ) + pTextNode->GetpSwpHints()->DeRegister(); + } + else + pTextNode->RstTextAttr( aSt.GetIndex(), nEnd - aSt.GetIndex(), pPara->nWhich, + pPara->pDelSet, pPara->bInclRefToxMark, pPara->bExactRange ); + } + return true; +} + +DocumentContentOperationsManager::~DocumentContentOperationsManager() +{ +} +//Private methods + +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags) +{ + 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<std::unique_ptr<SwRangeRedline>> redlines; + { + auto pRedline(std::make_unique<SwRangeRedline>(RedlineType::Delete, rPam)); + if (pRedline->HasValidRange()) + { + redlines.push_back(std::move(pRedline)); + } + 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 (std::unique_ptr<SwRangeRedline> & pRedline : redlines) + { + assert(pRedline->HasValidRange()); + undos.emplace_back(std::make_unique<SwUndoRedlineDelete>( + *pRedline, SwUndoId::DELETE, flags)); + } + 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 (std::unique_ptr<SwRangeRedline> & 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.release(), 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, SwDeleteFlags const flags) +{ + bool bJoinText, bJoinPrev; + ::sw_GetJoinFlags( rPam, bJoinText, bJoinPrev ); + + bool const bSuccess( DeleteRangeImpl(rPam, flags) ); + 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, SwDeleteFlags const flags) +{ + // 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() ); + { + SwPosition const pos(GetCorrPosition(aDelPam)); + ::PaMCorrAbs(aDelPam, pos); + } + + bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) ); + if (bSuccess) + { // now copy position from temp copy to given PaM + *rPam.GetPoint() = *aDelPam.GetPoint(); + } + + return bSuccess; +} + +bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags) +{ + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + 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->GetNode() != pEnd->GetNode() || + pStt->GetContentIndex() + 1 != pEnd->GetContentIndex() || + !m_rDoc.GetAutoCorrExceptWord()->CheckDelChar( *pStt )) + { m_rDoc.DeleteAutoCorrExceptWord(); } + } + + { + // Delete all empty TextHints at the Mark's position + SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode(); + SwpHints* pHts; + if( pTextNd && nullptr != ( pHts = pTextNd->GetpSwpHints()) && pHts->Count() ) + { + const sal_Int32 nMkCntPos = rPam.GetMark()->GetContentIndex(); + 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, flags)); + } + + 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 + if (!(flags & SwDeleteFlags::ArtificialSelection)) + { + DelFlyInRange(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode(), + rPam.GetMark()->GetContentIndex(), rPam.GetPoint()->GetContentIndex()); + } + DelBookmarks( + pStt->GetNode(), + pEnd->GetNode(), + nullptr, + pStt->GetContentIndex(), + pEnd->GetContentIndex(), + bool(flags & SwDeleteFlags::ArtificialSelection)); + + SwNodeIndex aSttIdx( pStt->GetNode() ); + 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->GetNode() == pEnd->GetNode(); + const sal_Int32 nLen = ( bOneNd ? pEnd->GetContentIndex() + : pCNd->Len() ) + - pStt->GetContentIndex(); + + // Don't call again, if already empty + if( nLen ) + { + pStartTextNode->EraseText( *pStt, 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->GetNode().GetContentNode(); + if( pCNd ) + { + SwTextNode * pEndTextNode( pCNd->GetTextNode() ); + if( pEndTextNode ) + { + // if already empty, don't call again + if( pEnd->GetContentIndex() ) + { + SwContentIndex aIdx( pCNd, 0 ); + pEndTextNode->EraseText( aIdx, pEnd->GetContentIndex() ); + + 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 + SwNodeOffset nEnd = pEnd->GetNodeIndex(); + if( pCNd == nullptr ) + nEnd++; + + if( aSttIdx != nEnd ) + { + // tdf#134436 delete section nodes like SwUndoDelete::SwUndoDelete + SwNode *pTmpNd; + while (pEnd == rPam.GetPoint() + && nEnd + SwNodeOffset(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 NodesArray + 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. + if (pStt->GetNode().GetContentNode()) + pStt->SetContent( pStt->GetContentIndex() ); + + // 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.End() ); + + auto [pStt, pEnd] = aDelPam.StartEnd(); // SwPosition* + bool bOneNode = pStt->GetNode() == pEnd->GetNode(); + + // Own Undo? + OUString sRepl( rStr ); + SwTextNode* pTextNd = pStt->GetNode().GetTextNode(); + sal_Int32 nStt = pStt->GetContentIndex(); + 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->GetNode().GetTextNode(); + nStt = pStt->GetContentIndex(); + } + + if( !sRepl.isEmpty() ) + { + // Apply the first character's attributes to the ReplaceText + SfxItemSetFixed + <RES_CHRATR_BEGIN, RES_TXTATR_WITHEND_END - 1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1> aSet( m_rDoc.GetAttrPool() ); + 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()->GetNode(), -1 ); + const sal_Int32 nPtCnt = aDelPam.GetPoint()->GetContentIndex(); + + bool bFirst = true; + OUString sIns; + while ( lcl_GetTokenToParaBreak( sRepl, sIns, bRegExReplace ) ) + { + InsertString( aDelPam, sIns ); + if( bFirst ) + { + SwNodeIndex aMkNd( aDelPam.GetMark()->GetNode(), -1 ); + const sal_Int32 nMkCnt = aDelPam.GetMark()->GetContentIndex(); + + SplitNode( *aDelPam.GetPoint(), false ); + + ++aMkNd; + aDelPam.GetMark()->Assign( aMkNd, nMkCnt ); + bFirst = false; + } + else + SplitNode( *aDelPam.GetPoint(), false ); + } + if( !sIns.isEmpty() ) + { + InsertString( aDelPam, sIns ); + } + + SwPaM aTmpRange( *aDelPam.GetPoint() ); + aTmpRange.SetMark(); + + ++aPtNd; + aDelPam.GetPoint()->Assign(aPtNd, nPtCnt); + *aTmpRange.GetMark() = *aDelPam.GetPoint(); + + m_rDoc.RstTextAttrs( aTmpRange ); + InsertItemSet( aTmpRange, aSet ); + } + + // tdf#139982: Appending the redline may immediately delete flys + // anchored in the previous text if it's inside an insert redline. + // Also flys will be deleted if the redline is accepted. Move them + // to the position between the previous text and the new text, + // there the chance of surviving both accept and reject is best. + SaveFlyArr flys; + SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRedlineDelete>( aDelPam, SwUndoId::REPLACE )); + } + // add redline similar to DeleteAndJoinWithRedlineImpl() + std::shared_ptr<SwUnoCursor> const pCursor(m_rDoc.CreateUnoCursor(*aDelPam.GetMark())); + pCursor->SetMark(); + *pCursor->GetPoint() = *aDelPam.GetPoint(); + m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true); + RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->GetNode(), true); + sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor); + + *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); + + aDelPam.GetPoint()->Assign( SwNodeOffset(0) ); + aDelPam.GetMark()->Assign( SwNodeOffset(0) ); + rPam.GetPoint()->Assign( SwNodeOffset(0) ); + *rPam.GetMark() = *rPam.GetPoint(); + m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + + *rPam.GetPoint() = pBkmk->GetMarkPos(); + *rPam.GetMark() = pBkmk->IsExpanded() ? pBkmk->GetOtherMarkPos() : pBkmk->GetMarkPos(); + + m_rDoc.getIDocumentMarkAccess()->deleteMark(pBkmk); + } + bJoinText = false; + } + else + { + assert((pStt->GetNode() == pEnd->GetNode() || + ( pStt->GetNodeIndex() + 1 == pEnd->GetNodeIndex() && + !pEnd->GetContentIndex() )) && + "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->GetNode(), -1 ); + const sal_Int32 nPtCnt = pStt->GetContentIndex(); + + // Set the values again, if Frames or footnotes on the Text have been removed. + nStt = nPtCnt; + nEnd = bOneNode ? pEnd->GetContentIndex() + : 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, 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, nEnd - nStt, sIns ); + } + } + + *rPam.GetPoint() = *aDelPam.GetMark(); + ++aPtNd; + rPam.GetMark()->Assign( aPtNd, 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 \ + std::shared_ptr<SwNumRuleItem> aNumRuleItemHolderIfSet; \ + std::shared_ptr<SfxStringItem> aListIdItemHolderIfSet; \ + +#define PUSH_NUMRULE_STATE \ + lcl_PushNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd ); + +#define POP_NUMRULE_STATE \ + lcl_PopNumruleState( aNumRuleItemHolderIfSet, aListIdItemHolderIfSet, pDestTextNd, rPam ); + +static void lcl_PushNumruleState( + std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet, + std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet, + const SwTextNode *pDestTextNd ) +{ + // Safe numrule item at destination. + // #i86492# - Safe also <ListId> item of destination. + const SfxItemSet * pAttrSet = pDestTextNd->GetpSwAttrSet(); + if (pAttrSet == nullptr) + return; + + if (const SwNumRuleItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false)) + { + aNumRuleItemHolderIfSet.reset(pItem->Clone()); + } + + if (const SfxStringItem* pItem = pAttrSet->GetItemIfSet(RES_PARATR_LIST_ID, false)) + { + aListIdItemHolderIfSet.reset(pItem->Clone()); + } +} + +static void lcl_PopNumruleState( + const std::shared_ptr<SwNumRuleItem>& aNumRuleItemHolderIfSet, + const std::shared_ptr<SfxStringItem>& aListIdItemHolderIfSet, + 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) ) + return; + + if (aNumRuleItemHolderIfSet) + { + pDestTextNd->SetAttr(*aNumRuleItemHolderIfSet); + } + else + { + pDestTextNd->ResetAttr(RES_PARATR_NUMRULE); + } + + if (aListIdItemHolderIfSet) + { + pDestTextNd->SetAttr(*aListIdItemHolderIfSet); + } + 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<SwNodeOffset, 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() ); + SwNodeOffset nOffset(0); + SwNodes const& rNodes(rPam.GetPoint()->GetNodes()); + SwPaM aPam( rSelectionEnd, rSelectionEnd ); // end node! + SwPosition & rEnd( *aPam.End() ); + SwPosition & rStart( *aPam.Start() ); + SwPaM copyRange(rPos, rPos); + + while (iter != Breaks.rend()) + { + rStart.Assign(*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.GetNodeIndex(); // fly nodes... + if (pCopyRange) + { + if (bFirst) + { + pCopyRange->SetMark(); + *pCopyRange->GetMark() = *copyRange.End(); + } + *pCopyRange->GetPoint() = *copyRange.Start(); + } + bFirst = false; + } + rEnd.Assign(*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& rDoc = rPos.GetNode().GetDoc(); + const bool bColumnSel = rDoc.IsClipBoard() && rDoc.IsColumnSelection(); + + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + // 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 + ( &rDoc == &m_rDoc && *pStt <= rPos && rPos < *pEnd )) + { + return false; + } + + const bool bEndEqualIns = &rDoc == &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(rDoc.CreateUnoCursor(rPos)); + + SwTableNumFormatMerge aTNFM( m_rDoc, rDoc ); + std::optional<std::vector<SwFrameFormat*>> pFlys; + std::vector<SwFrameFormat*> const* pFlysAtInsPos; + + if (rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + pUndo = new SwUndoCpyDoc(*pCopyPam); + pFlysAtInsPos = pUndo->GetFlysAnchoredAt(); + } + else + { + pFlys = sw::GetFlysAnchoredAt(rDoc, rPos.GetNodeIndex()); + pFlysAtInsPos = pFlys ? &*pFlys : nullptr; + } + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.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.GetNodeIndex() - pCopyPam->GetPoint()->GetNodeIndex()) > SwNodeOffset(1)) + { + // First go back to the original place + *(pCopyPam->GetPoint()) = rPos; + + bCanMoveBack = false; + bAfterTable = true; + } + if( !bCanMoveBack ) + { + pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1)); + assert(pCopyPam->GetPoint()->GetContentIndex() == 0); + } + + SwNodeRange aRg( pStt->GetNode(), pEnd->GetNode() ); + SwNodeIndex aInsPos( rPos.GetNode() ); + const bool bOneNode = pStt->GetNode() == pEnd->GetNode(); + SwTextNode* pSttTextNd = pStt->GetNode().GetTextNode(); + SwTextNode* pEndTextNd = pEnd->GetNode().GetTextNode(); + SwTextNode* pDestTextNd = aInsPos.GetNode().GetTextNode(); + bool bCopyCollFormat = !rDoc.IsInsOnlyTextGlossary() && + ( (pDestTextNd && !pDestTextNd->GetText().getLength()) || + ( !bOneNode && !rPos.GetContentIndex() ) ); + bool bCopyBookmarks = true; + bool bCopyPageSource = false; + SwNodeOffset nDeleteTextNodes(0); + + // #i104585# copy outline num rule to clipboard (for ASCII filter) + if (rDoc.IsClipBoard() && m_rDoc.GetOutlineNumRule()) + { + rDoc.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 = + rDoc.SearchNumRule( rPos, false, true, false, 0, aListIdToPropagate, nullptr, true ); + if ( !pNumRuleToPropagate ) + { + pNumRuleToPropagate = + rDoc.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 + // or + // - source is a table + if ( pNumRuleToPropagate && + ((pDestTextNd && !pDestTextNd->GetText().getLength() && + !pDestTextNd->IsInList() && + !lcl_ContainsOnlyParagraphsInList(rPam)) || + rPam.GetBound().nNode.GetNode().GetNodeType() == SwNodeType::Table) ) + { + 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->GetContentIndex() ) + { + SwContentIndex aDestIdx( rPos.GetContentNode(), rPos.GetContentIndex() ); + bool bCopyOk = false; + if( !pDestTextNd ) + { + if( pStt->GetContentIndex() || bOneNode ) + pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD)); + else + { + pDestTextNd = pSttTextNd->MakeCopy(rDoc, aInsPos.GetNode(), true)->GetTextNode(); + bCopyOk = true; + } + aDestIdx.Assign( pDestTextNd, 0 ); + bCopyCollFormat = true; + } + else if( !bOneNode || bColumnSel ) + { + const sal_Int32 nContentEnd = pEnd->GetContentIndex(); + { + ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo()); + rDoc.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 = rDoc.GetNodes()[ aInsPos.GetIndex()-SwNodeOffset(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->Adjust(SwNodeOffset(-1)); + pEnd->SetContent( nContentEnd ); + } + // tdf#63022 always reset pEndTextNd after SplitNode + aRg.aEnd = pEnd->GetNode(); + pEndTextNd = pEnd->GetNode().GetTextNode(); + } + + NUMRULE_STATE + if( bCopyCollFormat && bOneNode ) + { + PUSH_NUMRULE_STATE + } + + if( !bCopyOk ) + { + const sal_Int32 nCpyLen = ( bOneNode + ? pEnd->GetContentIndex() + : pSttTextNd->GetText().getLength()) + - pStt->GetContentIndex(); + pSttTextNd->CopyText( pDestTextNd, aDestIdx, *pStt, nCpyLen ); + if( bEndEqualIns ) + pEnd->AdjustContent( -nCpyLen ); + } + + ++aRg.aStart; + + if( bOneNode ) + { + if (bCopyCollFormat) + { + // tdf#138897 no Undo for applying style, SwUndoInserts does it + pSttTextNd->CopyCollFormat(*pDestTextNd, false); + POP_NUMRULE_STATE + } + + // Copy at-char flys in rPam. + // Update to new (start) node for flys. + // tdf#126626 prevent duplicate Undos. + ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo()); + CopyFlyInFlyImpl(aRg, &rPam, *pDestTextNd, 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.GetContentIndex() == 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.GetContentIndex() ) + { // Insertion in the middle of a text node, it has to be split + // (and joined from undo) + ++nDeleteTextNodes; + + const sal_Int32 nContentEnd = pEnd->GetContentIndex(); + { + ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo()); + rDoc.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.Adjust(SwNodeOffset(-1)); + rPos.SetContent( nContentEnd ); + --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()->Adjust(SwNodeOffset(-1)); + } + } + + pDestTextNd = aInsPos.GetNode().GetTextNode(); + if (pEndTextNd) + { + SwContentIndex aDestIdx( aInsPos.GetNode().GetContentNode(), rPos.GetContentIndex() ); + if( !pDestTextNd ) + { + pDestTextNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode(), + rDoc.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, SwContentIndex( pEndTextNd ), + pEnd->GetContentIndex() ); + + // Also copy all format templates + if( bCopyCollFormat && ( bOneNode || bEmptyDestNd )) + { + // tdf#138897 no Undo for applying style, SwUndoInserts does it + pEndTextNd->CopyCollFormat(*pDestTextNd, false); + if ( bOneNode ) + { + POP_NUMRULE_STATE + } + } + } + + SfxItemSet aBrkSet( rDoc.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(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1)); + 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.GetNode().IsContentNode()); + std::pair<SwPaM const&, SwPosition const&> tmp(rPam, startPos); + if( aInsPos == pEnd->GetNode() ) + { + SwNodeIndex aSaveIdx( aInsPos, -1 ); + assert(pStt->GetNode() != pEnd->GetNode()); + pEnd->SetContent(0); // TODO why this? + CopyWithFlyInFly(aRg, aInsPos.GetNode(), &tmp, /*bMakeNewFrames*/true, false, /*bCopyFlyAtFly=*/false, flags); + ++aSaveIdx; + pEnd->Assign(aSaveIdx); + } + else + CopyWithFlyInFly(aRg, aInsPos.GetNode(), &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(pCopyPam->GetPoint()->GetNode(), SwNodeOffset(+1)); + 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.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 ); + + bool bSplitFly = false; + if (pFly->GetFlySplit().GetValue()) + { + SwIterator<SwFrame, SwModify> aIter(*pFly); + bSplitFly = aIter.First() && aIter.Next(); + } + if (bSplitFly) + { + // This fly format has multiple frames, and we change the anchor. Remove the + // old frames, which were based on the old anchor position. + pFly->DelFrames(); + } + + pFly->SetFormatAttr(anchor); + + if (bSplitFly) + { + // Re-create the frames now that the new anchor is set. + pFly->MakeFrames(); + } + } + } + } + + if ((flags & SwCopyFlags::CopyAll) || aRg.aStart != aRg.aEnd) + { + // Put the breaks back into the first node + if( aBrkSet.Count() && nullptr != ( pDestTextNd = rDoc.GetNodes()[ + pCopyPam->GetPoint()->GetNodeIndex()+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 (rDoc.IsClipBoard() && (rPam.GetPageNum(pStt == rPam.GetPoint()) == 1) && !bCopyPageSource) + { + if (pDestTextNd) + { + 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.GetNode().GetContentNode(), + rPos.GetContentIndex() ); + + if( rPos.GetNode() != aInsPos.GetNode() ) + { + if (aInsPos < rPos.GetNode()) + { // tdf#134250 decremented in (pEndTextNd && !pDestTextNd) above + pCopyPam->GetMark()->AssignEndIndex(*aInsPos.GetNode().GetContentNode()); + } + else // incremented in (!pSttTextNd && pDestTextNd) above + { + pCopyPam->GetMark()->Assign(aInsPos); + } + rPos = *pCopyPam->GetMark(); + } + else + *pCopyPam->GetMark() = rPos; + + if ( !bAfterTable ) + pCopyPam->Move( fnMoveForward, bCanMoveBack ? GoInContent : GoInNode ); + else + { + // Reset the offset to 0 as it was before the insertion + pCopyPam->GetPoint()->Adjust(SwNodeOffset(+1)); + + // 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()->GetNode().IsStartNode()) + pCopyPam->GetPoint()->Adjust(SwNodeOffset(-1)); + + } + 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 (rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + // append it after styles have been copied when copying nodes + rDoc.GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pUndo) ); + 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. + rDoc.SetNumRule( *pCopyPam, *pNumRuleToPropagate, false, nullptr, + aListIdToPropagate, true, /*bResetIndentAttrs=*/false ); + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + rDoc.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 0000000000..b90474082c --- /dev/null +++ b/sw/source/core/doc/DocumentDeviceManager.cxx @@ -0,0 +1,370 @@ +/* -*- 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 <osl/diagnose.h> +#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 ) + return; + + 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<SfxItemSetFixed< + 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>>(m_rDoc.GetAttrPool()); + 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; + } + assert(mpPrtData && "this will always be set by now"); + 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::WITHOUT_ALPHA); +#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" ); + + // We create a default SfxPrinter. + // The ItemSet is deleted by Sfx! + auto pSet = std::make_unique<SfxItemSetFixed< + 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>>(m_rDoc.GetAttrPool()); + + 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::optional<SwWait> oWait; + 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() ) + oWait.emplace( *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 0000000000..7f3098ef66 --- /dev/null +++ b/sw/source/core/doc/DocumentDrawModelManager.cxx @@ -0,0 +1,357 @@ +/* -*- 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> +#include <osl/diagnose.h> + +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(); + } + + rtl::Reference<SdrPage> pMasterPage = mpDrawModel->AllocPage( false ); + mpDrawModel->InsertPage( pMasterPage.get() ); + 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 ) + return; + + 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.get(); + 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 SwPosFlyFrame& rPosFlyFrame : aFrames) + { + // Filter for at-paragraph anchored draw frames. + const SwFrameFormat& rFrameFormat = rPosFlyFrame.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 = DynCastSdrTextObj(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 0000000000..3e751a3168 --- /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 0000000000..66f65438c0 --- /dev/null +++ b/sw/source/core/doc/DocumentFieldsManager.cxx @@ -0,0 +1,1775 @@ +/* -*- 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 <config_fuzzers.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 <node2lay.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 <osl/diagnose.h> +#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 && !ENABLE_FUZZERS + + 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 || ENABLE_FUZZERS + (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()) + return; + + 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 + { + // coverity[leaked_storage] - at this point DB fields are ref-counted and delete themselves + (*mpFieldTypes)[nField].release(); + } + + mpFieldTypes->erase( mpFieldTypes->begin() + nField ); + m_rDoc.getIDocumentState().SetModified(); +} + +// All have to be re-evaluated. +void DocumentFieldsManager::UpdateFields(bool bCloseDB) +{ + // Tell all types to update their fields + for(auto const& pFieldType: *mpFieldTypes) + pFieldType->UpdateFields(); + + if(!IsExpFieldsLocked()) + UpdateExpFields(nullptr, false); // update expression fields + + // Tables + UpdateTableFields(nullptr); + + // References + UpdateRefFields(); + if(bCloseDB) + { +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + 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, bool bUpdateFields) +{ + //static const sw::RefmarkFieldUpdate aRefMarkHint; + 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(), pDstTextField->GetStart() ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoFieldFromDoc>(aPosition, *pDstField, rSrcField, 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 = + SwDoc::IsIdxInTable(aTableNdIdx); + if( pTableNd ) + { + if (bUpdateFields) + UpdateTableFields(&pTableNd->GetTable()); + else + pNewField->GetTyp()->CallSwClientNotify(sw::LegacyModifyHint(nullptr, nullptr)); + + if (! bUpdateFields) + bTableSelBreak = true; + } + } + break; + + case SwFieldIds::Macro: + if( bUpdateFields && pDstTextField->GetpTextNode() ) + pDstTextField->GetpTextNode()->TriggerNodeUpdate(sw::LegacyModifyHint(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 && !ENABLE_FUZZERS + { + // 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->ForceUpdateTextNode(); + } + + // 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()) + static_cast<SwGetRefFieldType*>(pFieldType.get())->UpdateGetReferences(); +} + +void DocumentFieldsManager::UpdateTableFields(const SwTable* pTable) +{ + auto pFieldType = GetFieldType( SwFieldIds::Table, OUString(), false ); + if(pFieldType) + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + for(auto pFormatField : vFields) + { + if(!pFormatField->GetTextField()->GetTextNode().FindTableNode()) + continue; + SwTableField* pField = static_cast<SwTableField*>(pFormatField->GetField()); + // re-set the value flag + // JP 17.06.96: internal representation of all formulas + // (reference to other table!!!) + if(pTable && nsSwExtendedSubType::SUB_CMD & pField->GetSubType()) + pField->PtrToBoxNm(pTable); + 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 = const_cast<SwTableBoxFormula*>(pItem->DynamicWhichCast(RES_BOXATR_FORMULA)); + if(pBoxFormula && pBoxFormula->GetDefinedIn()) + pBoxFormula->ChangeState(); + } + + SwRootFrame const* pLayout(nullptr); + for (SwRootFrame const*const pLay : m_rDoc.GetAllLayouts()) + { + assert(!pLayout || pLay->IsHideRedlines() == pLayout->IsHideRedlines()); // TODO + pLayout = pLay; + } + + std::optional<SwCalc> oCalc; + + 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(pTable && &pTableNd->GetTable() != pTable) + continue; + + if( !oCalc ) + oCalc.emplace( 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( *oCalc, SetGetExpField( + aPos.GetNode(), pFormatField->GetTextField(), + aPos.GetContentIndex(), pFrame->GetPhyPageNum()), + pLayout); + } + else + pFrame = nullptr; + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwFrame const*const pFrame2 = ::sw::FindNeighbourFrameForNode(rTextNd); + FieldsToCalc( *oCalc, + SetGetExpField(rTextNd, pFormatField->GetTextField(), + std::nullopt, + pFrame2 ? pFrame2->GetPhyPageNum() : 0), + pLayout); + } + + SwTableCalcPara aPara(*oCalc, 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"); + } + oCalc->SetCalcError( SwCalcError::NONE ); + } + pFormatField->ForceUpdateTextNode(); + } + } + + // calculate the formula at the boxes + for (const SfxPoolItem* pItem : m_rDoc.GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + auto pFormula = const_cast<SwTableBoxFormula*>(pItem->DynamicWhichCast(RES_BOXATR_FORMULA)); + if(!pFormula || !pFormula->GetDefinedIn() || pFormula->IsValid()) + continue; + SwTableBox* pBox = pFormula->GetTableBox(); + if(!pBox || !pBox->GetSttNd() || !pBox->GetSttNd()->GetNodes().IsDocNodes()) + continue; + const SwTableNode* pTableNd = pBox->GetSttNd()->FindTableNode(); + if(pTable && &pTableNd->GetTable() != pTable) + continue; + double nValue; + if( !oCalc ) + oCalc.emplace( 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! + SwNodeIndex aCNdIdx( *pTableNd, +2 ); + SwContentNode* pCNd = aCNdIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = m_rDoc.GetNodes().GoNext( &aCNdIdx ); + + if (pCNd) + { + Point aPt; // return the first frame of the layout - Tab.Headline!! + 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(*oCalc, SetGetExpField(aPos.GetNode(), + nullptr, std::nullopt, pFrame->GetPhyPageNum()), + pLayout); + } + else + pFrame = nullptr; + } + } + } + if( !pFrame ) + { + // create index to determine the TextNode + SwFrame const*const pFrame2 = ::sw::FindNeighbourFrameForNode(*pTableNd); + FieldsToCalc(*oCalc, SetGetExpField(*pTableNd, nullptr, std::nullopt, + pFrame2 ? pFrame2->GetPhyPageNum() : 0), + pLayout); + } + + SwTableCalcPara aPara(*oCalc, 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(); + SfxItemSetFixed<RES_BOXATR_BEGIN,RES_BOXATR_END-1> aTmp( m_rDoc.GetAttrPool() ); + + if( oCalc->IsCalcError() ) + nValue = DBL_MAX; + aTmp.Put( SwTableBoxValue( nValue )); + if( SfxItemState::SET != pFormat->GetItemState( RES_BOXATR_FORMAT )) + aTmp.Put( SwTableBoxNumFormat( 0 )); + pFormat->SetFormatAttr( aTmp ); + + oCalc->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. + std::unordered_map<OUString, OUString> aHashStrTable; + + { + const SwFieldType* pFieldType; + // process separately: + for( auto n = mpFieldTypes->size(); n; ) + { + pFieldType = (*mpFieldTypes)[ --n ].get(); + switch( pFieldType->Which() ) + { + case SwFieldIds::User: + { + // Entry present? + const OUString& rNm = pFieldType->GetName(); + OUString sExpand(const_cast<SwUserFieldType*>(static_cast<const SwUserFieldType*>(pFieldType))->Expand(nsSwGetSetExpType::GSE_STRING, 0, LANGUAGE_SYSTEM)); + auto pFnd = aHashStrTable.find( rNm ); + if( pFnd != aHashStrTable.end() ) + // modify entry in the hash table + pFnd->second = sExpand; + else + // insert the new entry + aHashStrTable.insert( { rNm, sExpand } ); + } + break; + default: break; + } + } + } + + // The array is filled with all fields; start calculation. + SwCalc aCalc( m_rDoc ); + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + 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.GetLocaleData(); + 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; + SwNodeOffset nContentStart = m_rDoc.GetNodes().GetEndOfContent().StartOfSectionIndex() + 1; + SwNodeOffset 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; + } + ::sw::mark::IBookmark *const pBookmark( + const_cast<::sw::mark::IBookmark *>(it->GetBookmark())); + if (pBookmark) + { + SwSbxValue const aValue(aCalc.Calculate(pBookmark->GetHideCondition())); + if (!aValue.IsVoidValue()) + { + pBookmark->Hide(aValue.GetBool()); + } + 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 && !ENABLE_FUZZERS + { + 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 && !ENABLE_FUZZERS + { + 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 && !ENABLE_FUZZERS + // 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? + auto pFnd = aHashStrTable.find( rName ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd != aHashStrTable.end() ) + { + // Modify entry in the hash table + pFnd->second = value; + } + else + { + // insert new entry + aHashStrTable.insert( { rName, value } ); + } +#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? + auto pFnd = aHashStrTable.find( aNew ); + if( pFnd != aHashStrTable.end() ) + // Modify entry in the hash table + pFnd->second = pSField->GetExpStr(pLayout); + else + // insert new entry + pFnd = aHashStrTable.insert( { aNew, pSField->GetExpStr(pLayout) } ).first; + + // Extension for calculation with Strings + SwSbxValue aValue; + aValue.PutString( pFnd->second ); + 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->ForceUpdateTextNode(); + } + + 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 && !ENABLE_FUZZERS + 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(const SwTwips nDocPos) +{ + 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->UpdateDocPos(nDocPos); + break; + case SwFieldIds::DocStat: + pFieldType->CallSwClientNotify(sw::LegacyModifyHint(nullptr, nullptr)); + break; + case SwFieldIds::GetRef: + static_cast<SwGetRefFieldType*>(pFieldType)->UpdateStyleReferences(); + // Style references can vary across different pages (e.g. in header/footer) + // so they must be updated when page fields are + 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, SwNodeOffset 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; + SwNodeOffset 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->ForceUpdateTextNode(); + } + } + } + + 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 || ENABLE_FUZZERS + 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 && !ENABLE_FUZZERS + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToCalc(SwCalc& rCalc, + SwNodeOffset const nLastNd, sal_Int32 const nLastCnt) +{ + // create the sorted list of all SetFields + mpUpdateFields->MakeFieldList( m_rDoc, mbNewFieldLst, GETFLD_CALC ); + mbNewFieldLst = false; + +#if !HAVE_FEATURE_DBCONNECTIVITY || ENABLE_FUZZERS + 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 && !ENABLE_FUZZERS + pMgr->CloseAll(false); +#endif +} + +void DocumentFieldsManager::FieldsToExpand( std::unordered_map<OUString, OUString> & 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()); + + 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? + auto pFnd = rHashTable.find( aNew ); + if( pFnd != rHashTable.end() ) + // modify entry in the hash table + pFnd->second = pSField->GetExpStr(&rLayout); + else + // insert the new entry + rHashTable.insert( { aNew, pSField->GetExpStr(&rLayout) } ); + } + break; + case SwFieldIds::Database: + { + const OUString& rName = pField->GetTyp()->GetName(); + + // Insert entry in the hash table + // Entry present? + auto pFnd = rHashTable.find( rName ); + OUString const value(pField->ExpandField(m_rDoc.IsClipBoard(), nullptr)); + if( pFnd != rHashTable.end() ) + // modify entry in the hash table + pFnd->second = value; + else + // insert the new entry + rHashTable.insert( { rName, value } ); + } + 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.GetNode().GetTextNode(); + + return (pNode != nullptr) + ? pNode->GetFieldTextAttrAt(rPos.GetContentIndex(), ::sw::GetTextAttrMode::Default) + : 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 || ENABLE_FUZZERS + (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 0000000000..6481f104c7 --- /dev/null +++ b/sw/source/core/doc/DocumentLayoutManager.cxx @@ -0,0 +1,497 @@ +/* -*- 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 <textboxhelper.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <frameformats.hxx> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <svx/svdobj.hxx> +#include <svx/svdpage.hxx> +#include <osl/diagnose.h> + +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() ); + + const SwNode& rEndOfAutotext( m_rDoc.GetNodes().GetEndOfAutotext() ); + SwStartNode* pSttNd = + m_rDoc.GetNodes().MakeTextSection + ( rEndOfAutotext, + bHeader ? SwHeaderStartNode : SwFooterStartNode, + m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(o3tl::narrowing<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, SwNodeOffset(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) + { + sw::SpzFrameFormats* pSpzs = pFormat->GetDoc()->GetSpzFrameFormats(); + if ( pSpzs ) + { + std::vector<SwFrameFormat*> aToDeleteFrameFormats; + const SwNodeOffset nNodeIdxOfFlyFormat( pContentIdx->GetIndex() ); + + for(sw::SpzFrameFormat* pSpz: *pSpzs) + { + const SwFormatAnchor &rAnch = pSpz->GetAnchor(); + if ( rAnch.GetAnchorId() == RndStdIds::FLY_AT_FLY && + rAnch.GetAnchorNode()->GetIndex() == nNodeIdxOfFlyFormat ) + { + aToDeleteFrameFormats.push_back(pSpz); + } + } + + // 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.GetAnchorNode()) + { + SwTextNode *pTextNd = rAnchor.GetAnchorNode()->GetTextNode(); + + // attribute is still in text node, delete it + if ( pTextNd ) + { + SwTextFlyCnt* const pAttr = static_cast<SwTextFlyCnt*>( + pTextNd->GetTextAttrForCharAt( rAnchor.GetAnchorContentOffset(), + RES_TXTATR_FLYCNT )); + if ( pAttr && (pAttr->GetFlyCnt().GetFrameFormat() == pFormat) ) + { + // don't delete, set pointer to 0 + const_cast<SwFormatFlyCnt&>(pAttr->GetFlyCnt()).SetFlyFormat(); + pTextNd->EraseText( *rAnchor.GetContentAnchor(), 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; + const SwNode* pCAnchor = rNewAnchor.GetAnchorNode(); + bool bInHeaderFooter = pCAnchor && m_rDoc.IsInHeaderFooter(*pCAnchor); + if(bDraw) + { + 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())) && + bInHeaderFooter; + } + + // 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, SwNodeOffset(1), *rCSttNd.EndOfSectionNode() ); + + SwStartNode* pSttNd = SwNodes::MakeEmptySection( m_rDoc.GetNodes().GetEndOfAutotext(), SwFlyStartNode ); + + // Set the Anchor/ContentIndex first. + // Within the copying part, we can access the values (DrawFormat in Headers and Footers) + SwNodeIndex 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() && !bInHeaderFooter) || m_rDoc.IsInMailMerge() ) + pDest->SetFormatName( 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->SetFormatName( OUString() ); + if( m_rDoc.FindFlyByName( sOld, nNdTyp ) ) // found one + switch( nNdTyp ) + { + case SwNodeType::Grf: sOld = m_rDoc.GetUniqueGrfName(sOld); break; + case SwNodeType::Ole: sOld = m_rDoc.GetUniqueOLEName(); break; + default: sOld = m_rDoc.GetUniqueFrameName(); break; + } + + pDest->SetFormatName( sOld ); + } + } + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsLayFormat>(pDest,SwNodeOffset(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.GetNode(), 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,SwNodeOffset(0),0)); + } + } + + if (bSetTextFlyAtt && (RndStdIds::FLY_AS_CHAR == rNewAnchor.GetAnchorId())) + { + SwNode* pAnchorNode = rNewAnchor.GetAnchorNode(); + SwFormatFlyCnt aFormat( pDest ); + assert(pAnchorNode->GetTextNode() && "sw.core: text node expected"); + if (SwTextNode *pTextNd = pAnchorNode->GetTextNode()) + pTextNd->InsertItem( aFormat, rNewAnchor.GetAnchorContentOffset(), 0 ); + } + + if( bMakeFrames ) + pDest->MakeFrames(); + + 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->SetFormatName(pObj->GetName()); + } + + // If the draw format has a TextBox, then copy its fly format as well. + if (const auto& pTextBoxes = rSource.GetOtherTextBoxFormats()) + pTextBoxes->Clone(&m_rDoc, rNewAnchor, pDest, bSetTextFlyAtt, bMakeFrames); + + 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 0000000000..2d491021c3 --- /dev/null +++ b/sw/source/core/doc/DocumentLinksAdministrationManager.cxx @@ -0,0 +1,508 @@ +/* -*- 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 <bookmark.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> +#include <utility> + +using namespace ::com::sun::star; + +//Helper functions for this file +namespace +{ + ::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; + } + + + SwSectionNode* lcl_FindSection(const SwDoc& rDoc, const OUString& rItem, bool bCaseSensitive) + { + const OUString sCompare = bCaseSensitive ? rItem : GetAppCharClass().lowercase(rItem); + for (const SwSectionFormat* pSectFormat : rDoc.GetSections()) + { + SwSection* pSect = pSectFormat->GetSection(); + if (pSect) + { + OUString sNm(bCaseSensitive ? pSect->GetSectionName() + : GetAppCharClass().lowercase(pSect->GetSectionName())); + 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 + return pIdx->GetNode().GetSectionNode(); + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + } + } + return nullptr; + } + + SwTableNode* lcl_FindTable(const SwDoc& rDoc, const OUString& rItem) + { + const OUString& aItem = GetAppCharClass().lowercase(rItem); + for (const SwFrameFormat* pTableFormat : *rDoc.GetTableFrameFormats()) + { + OUString sNm(GetAppCharClass().lowercase(pTableFormat->GetName())); + if (sNm == aItem) + { + 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 + return const_cast<SwTableNode*>(pFBox->GetSttNd()->FindTableNode()); + } + } + // If the name is already correct, but not the rest then we don't have them. + // The names are always unique. + } + } + return nullptr; + } + +} + + +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? + if (SwSectionNode* pSectNd = lcl_FindSection(m_rDoc, rItem, bCaseSensitive)) + { + // found, so get the data + return SwServerObject(*pSectNd).GetData( rValue, rMimeType ); + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + if (SwTableNode* pTableNd = lcl_FindTable(m_rDoc, rItem)) + { + return SwServerObject(*pTableNd).GetData( rValue, rMimeType ); + } + + return false; +} + +// TODO/FIXME: do something with the found items? For now, it's just an expensive no-op. +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? + if (lcl_FindSection(m_rDoc, rItem, bCaseSensitive)) + { + // found, so get the data + return; + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + (void)lcl_FindTable(m_rDoc, rItem); +} + +::sfx2::SvLinkSource* DocumentLinksAdministrationManager::CreateLinkSource(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 ) + { + // bookmarks + ::sw::mark::DdeBookmark* const pBkmk = lcl_FindDdeBookmark(*m_rDoc.getIDocumentMarkAccess(), rItem, bCaseSensitive); + if(pBkmk && pBkmk->IsExpanded()) + { + SwServerObject* pObj = pBkmk->GetRefObject(); + if( !pObj ) + { + // mark found, but no link yet -> create hotlink + pObj = new SwServerObject(*pBkmk); + pBkmk->SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + return pObj; + } + + // sections + if (SwSectionNode* pSectNd = lcl_FindSection(m_rDoc, rItem, bCaseSensitive)) + { + SwServerObject* pObj = pSectNd->GetSection().GetObject(); + if( !pObj ) + { + // section found, but no link yet -> create hotlink + pObj = new SwServerObject(*pSectNd); + pSectNd->GetSection().SetRefObject( pObj ); + GetLinkManager().InsertServer(pObj); + } + return pObj; + } + if( !bCaseSensitive ) + break; + bCaseSensitive = false; + } + + // tables + if (SwTableNode* pTableNd = lcl_FindTable(m_rDoc, rItem)) + { + SwServerObject* pObj = pTableNd->GetTable().GetObject(); + if( !pObj ) + { + // table found, but no link yet -> create hotlink + pObj = new SwServerObject(*pTableNd); + pTableNd->GetTable().SetRefObject(pObj); + GetLinkManager().InsertServer(pObj); + } + return pObj; + } + return nullptr; +} + +/// 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( std::u16string_view rStr, SwPaM*& rpPam, std::optional<SwNodeRange>& roRange ) const +{ + // Do we actually have the Item? + rpPam = nullptr; + roRange.reset(); + + OUString sItem( INetURLObject::decode( rStr, + INetURLObject::DecodeMechanism::WithCharset )); + + sal_Int32 nPos = sItem.indexOf( cMarkSeparator ); + + // Extension for sections: not only link bookmarks/sections + // but also frames (text!), tables, outlines: + if( -1 != nPos ) + { + OUString sName( sItem.copy( 0, nPos ) ); + std::u16string_view sCmp( sItem.subView( nPos + 1 )); + + if( sCmp == u"table" ) + { + if (SwTableNode* pTableNd = lcl_FindTable(m_rDoc, sName)) + { + roRange.emplace( *pTableNd, SwNodeOffset(0), + *pTableNd->EndOfSectionNode(), SwNodeOffset(1) ); + } + return roRange.has_value(); + } + else if( sCmp == u"frame" ) + { + const SwFlyFrameFormat* pFlyFormat = m_rDoc.FindFlyByName( sName ); + if( pFlyFormat ) + { + SwNodeIndex* pIdx = const_cast<SwNodeIndex*>(pFlyFormat->GetContent().GetContentIdx()); + if( pIdx ) + { + SwNode* pNd = &pIdx->GetNode(); + if( !pNd->IsNoTextNode() ) + { + roRange.emplace( *pNd, SwNodeOffset(1), *pNd->EndOfSectionNode() ); + } + } + } + return roRange.has_value(); + } + else if( sCmp == u"region" ) + { + sItem = sName; // Is being dealt with further down! + } + else if( sCmp == u"outline" ) + { + SwPosition aPos( m_rDoc.GetNodes() ); + if (m_rDoc.GotoOutline(aPos, sName, nullptr)) + { + SwNode* pNd = &aPos.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 ); + roRange.emplace( aPos.GetNode(), SwNodeOffset(0), aPos.GetNode() ); + + // 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() ) + roRange->aEnd = *rOutlNds[ nTmpPos ]; + else + roRange->aEnd = m_rDoc.GetNodes().GetEndOfContent(); + } + return roRange.has_value(); + } + } + + // 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); + } + + if( !m_rDoc.GetSections().empty() ) + { + if (SwSectionNode* pSectNd = lcl_FindSection(m_rDoc, sItem, bCaseSensitive)) + { + roRange.emplace( *pSectNd, SwNodeOffset(1), + *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 0000000000..2a8f0691d3 --- /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 0000000000..16cb540710 --- /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 ) +{ +} + +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 ) +{ + tools::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 0000000000..8d02377276 --- /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->HasMergedParas()) + { + 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 (SwNodeOffset 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 0000000000..8d52c814e8 --- /dev/null +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -0,0 +1,3940 @@ +/* -*- 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 <wrtsh.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> +#include <osl/diagnose.h> +#include <editeng/prntitem.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<SwContentIndexReg*>(&pPos->GetNode()) + == pPos->GetContentNode()); + + SwTextNode* pTextNode = pPos->GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + assert(pPos->GetContentIndex() == 0); + } + else + { + assert(pPos->GetContentIndex() >= 0 && pPos->GetContentIndex() <= 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( const 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) +{ + if (rDoc.IsClipBoard()) + { + return; + } + // no need to call UpdateFootnoteNums for FTNNUM_PAGE: + // the AppendFootnote/RemoveFootnote will do it by itself! + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->GetNode()); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode()); + while (!pStartNode) + { + // note: branch only taken for redlines, not fieldmarks + SwStartNode *const pTableOrSectionNode( + currentStart.GetNode().IsTableNode() + ? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode()) + : static_cast<SwStartNode*>(currentStart.GetNode().GetSectionNode())); + if ( !pTableOrSectionNode ) + { + SAL_WARN("sw.core", "UpdateFramesForAddDeleteRedline:: known pathology (or ChangesInRedline mode)"); + return; + } + for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + for (SwRootFrame const*const pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->HasMergedParas()) + { + if (pTableOrSectionNode->IsTableNode()) + { + static_cast<SwTableNode*>(pTableOrSectionNode)->DelFrames(pLayout); + } + else + { + static_cast<SwSectionNode*>(pTableOrSectionNode)->DelFrames(pLayout); + } + } + } + currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 ); + pStartNode = currentStart.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + // deleted text node: remove it from "hidden" list + // to update numbering in Show Changes mode + SwPosition aPos( *pNode, pNode->Len() ); + if ( pNode->GetNumRule() && aPos < *rPam.End() ) + pNode->RemoveFromListRLHidden(); + + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + frames.push_back(pFrame); + } + // set anchored objects as deleted + pFrame->SetDrawObjsAsDeleted(true); + } + 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()->GetNodeIndex()); + } + // 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) +{ + // tdf#147006 fieldmark command may be empty => do not call AppendAllObjs() + if (rDoc.IsClipBoard() || *rPam.GetPoint() == *rPam.GetMark()) + { + return; + } + bool isAppendObjsCalled(false); + rDoc.GetFootnoteIdxs().UpdateFootnote(rPam.Start()->GetNode()); + SwPosition currentStart(*rPam.Start()); + SwTextNode * pStartNode(rPam.Start()->GetNode().GetTextNode()); + while (!pStartNode) + { + // note: branch only taken for redlines, not fieldmarks + SwStartNode *const pTableOrSectionNode( + currentStart.GetNode().IsTableNode() + ? static_cast<SwStartNode*>(currentStart.GetNode().GetTableNode()) + : static_cast<SwStartNode*>(currentStart.GetNode().GetSectionNode())); + assert(pTableOrSectionNode); // known pathology + for (SwNodeOffset j = pTableOrSectionNode->GetIndex(); j <= pTableOrSectionNode->EndOfSectionIndex(); ++j) + { + pTableOrSectionNode->GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::None); + } + if (rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->HasMergedParas()) + { + // note: this will also create frames for all currently hidden flys + // because it calls AppendAllObjs + ::MakeFrames(&rDoc, currentStart.GetNode(), *pTableOrSectionNode->EndOfSectionNode()); + isAppendObjsCalled = true; + } + currentStart.Assign( pTableOrSectionNode->EndOfSectionIndex() + 1 ); + pStartNode = currentStart.GetNode().GetTextNode(); + } + if (currentStart < *rPam.End()) + { + SwTextNode * pNode(pStartNode); + do + { + // undeleted text node: add it to the "hidden" list + // to update numbering in Show Changes mode + SwPosition aPos( *pNode, pNode->Len() ); + if ( pNode->GetNumRule() && aPos < *rPam.End() ) + pNode->AddToListRLHidden(); + + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNode); + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + frames.push_back(pFrame); + } + // set anchored objects as not deleted + pFrame->SetDrawObjsAsDeleted(false); + } + if (frames.empty()) + { + // in SwUndoSaveSection::SaveSection(), DelFrames() preceded this call + if (!pNode->FindTableBoxStartNode() && !pNode->FindFlyStartNode()) + { + auto const& layouts(rDoc.GetAllLayouts()); + assert(std::none_of(layouts.begin(), layouts.end(), + [](SwRootFrame const*const pLayout) { return pLayout->IsHideRedlines(); })); + (void) layouts; + } + isAppendObjsCalled = true; // skip that! + break; + } + + // no nodes can be unmerged by this - skip MakeFrames() etc. + if (rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()) + { + break; // continue with AppendAllObjs() + } + + // 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! + // update pNode so MakeFrames starts on 2nd node + pNode = &rFirstNode; + } + } + 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.GetNode(), end.GetNode()); + 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()->GetNodeIndex()); + } + + if (!isAppendObjsCalled) + { // recreate flys in the one node the hard way... + for (auto const& pLayout : rDoc.GetAllLayouts()) + { + if (pLayout->HasMergedParas()) + { + 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.GetContentIndex() ) + return false; + if( rPos2.GetNodeIndex() - 1 != rPos1.GetNodeIndex() ) + return false; + pCNd = rPos1.GetNode().GetContentNode(); + return pCNd && rPos1.GetContentIndex() == 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.GetNode().GetTextNode(); + SwTextNode* pFromNode = rFrom.GetNode().GetTextNode(); + if (pToNode != nullptr && pFromNode != nullptr && pToNode != pFromNode) + { + const SwPaM aPam(*pToNode); + SwDoc& rDoc = aPam.GetDoc(); + // using Undo, copy paragraph style + SwTextFormatColl* pFromColl = pFromNode->GetTextColl(); + SwTextFormatColl* pToColl = pToNode->GetTextColl(); + if (bCopy && pFromColl != pToColl) + rDoc.SetTextFormatColl(aPam, pFromColl); + + // using Undo, remove direct paragraph formatting of the "To" paragraph, + // and apply here direct paragraph formatting of the "From" paragraph + SfxItemSetFixed< + 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> + aTmp(rDoc.GetAttrPool()); + 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.GetWhichByOffset(nItem); + if( SfxItemState::SET == aTmp.GetItemState( nWhich, false ) && + SfxItemState::SET != aTmp2.GetItemState( nWhich, false ) ) + aTmp2.Put( aTmp.GetPool()->GetDefaultItem(nWhich), nWhich ); + } + } + + if (bCopy && !bSameSet) + rDoc.getIDocumentContentOperations().InsertItemSet(aPam, aTmp2); + else if (!bCopy && (!bSameSet || pFromColl != pToColl)) + return new SwRedlineExtraData_FormatColl( pFromColl->GetName(), USHRT_MAX, &aTmp2 ); + } + return nullptr; + } + + // delete the empty tracked table row (i.e. if it's last tracked deletion was accepted) + void lcl_DeleteTrackedTableRow ( const SwPosition* pPos ) + { + const SwTableBox* pBox = pPos->GetNode().GetTableBox(); + if ( !pBox ) + return; + + // tracked column deletion + + const SvxPrintItem *pHasBoxTextChangesOnlyProp = + pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + // empty table cell with property "HasTextChangesOnly" = false + if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() ) + { + SwCursor aCursor( *pPos, nullptr ); + if ( pBox->IsEmpty() ) + { + // tdf#155747 remove table cursor + pPos->GetDoc().GetDocShell()->GetWrtShell()->EnterStdMode(); + // TODO check the other cells of the column + // before removing the column + pPos->GetDoc().DeleteCol( aCursor ); + return; + } + else + { + SvxPrintItem aHasTextChangesOnly(RES_PRINT, false); + pPos->GetDoc().SetBoxAttr( aCursor, aHasTextChangesOnly ); + } + } + + // tracked row deletion + + const SwTableLine* pLine = pBox->GetUpper(); + const SvxPrintItem *pHasTextChangesOnlyProp = + pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + // empty table row with property "HasTextChangesOnly" = false + if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() ) + { + if ( pLine->IsEmpty() ) + { + SwCursor aCursor( *pPos, nullptr ); + pPos->GetDoc().DeleteRow( aCursor ); + } + else + { + // update property "HasTextChangesOnly" + SwRedlineTable::size_type nPos = 0; + (void)pLine->UpdateTextChangesOnly(nPos); + } + } + } + + // at rejection of a deletion in a table, remove the tracking of the table row + // (also at accepting the last redline insertion of a tracked table row insertion) + void lcl_RemoveTrackingOfTableRow( const SwPosition* pPos, bool bRejectDeletion ) + { + const SwTableBox* pBox = pPos->GetNode().GetTableBox(); + if ( !pBox ) + return; + + // tracked column deletion + + const SvxPrintItem *pHasBoxTextChangesOnlyProp = + pBox->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + // table cell property "HasTextChangesOnly" is set and its value is false + if ( pHasBoxTextChangesOnlyProp && !pHasBoxTextChangesOnlyProp->GetValue() ) + { + SvxPrintItem aUnsetTracking(RES_PRINT, true); + SwCursor aCursor( *pPos, nullptr ); + pPos->GetDoc().SetBoxAttr( aCursor, aUnsetTracking ); + } + + // tracked row deletion + + const SwTableLine* pLine = pBox->GetUpper(); + const SvxPrintItem *pHasTextChangesOnlyProp = + pLine->GetFrameFormat()->GetAttrSet().GetItem<SvxPrintItem>(RES_PRINT); + // table row property "HasTextChangesOnly" is set and its value is false + if ( pHasTextChangesOnlyProp && !pHasTextChangesOnlyProp->GetValue() ) + { + bool bNoMoreInsertion = false; + if ( !bRejectDeletion ) + { + SwRedlineTable::size_type nPos = 0; + SwRedlineTable::size_type nInsert = pLine->UpdateTextChangesOnly(nPos, /*bUpdateProperty=*/false); + + if ( SwRedlineTable::npos == nInsert ) + bNoMoreInsertion = true; + } + if ( bRejectDeletion || bNoMoreInsertion ) + { + SvxPrintItem aUnsetTracking(RES_PRINT, true); + SwCursor aCursor( *pPos, nullptr ); + pPos->GetDoc().SetRowNotTracked( aCursor, aUnsetTracking ); + } + } + } + + 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: + { + bool bInsert = RedlineType::Insert == pRedl->GetType(); + SwPosition aPos(pRedl->Start()->GetNode()); + rArr.DeleteAndDestroy( rPos-- ); + + // remove tracking of the table row, if needed + if ( bInsert ) + lcl_RemoveTrackingOfTableRow( &aPos, /*bRejectDelete=*/false ); + } + 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->GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->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->GetContentIndex() == 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 ); + lcl_DeleteTrackedTableRow( aPam.End() ); + } + 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->GetNode().GetContentNode(); + SwContentNode* pCEndNd = pDelEnd->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 ); + lcl_DeleteTrackedTableRow( aPam.End() ); + } + else if (pCSttNd && !pCEndNd) + { + aPam.GetBound().nContent.Assign( nullptr, 0 ); + aPam.GetBound( false ).nContent.Assign( nullptr, 0 ); + if (aPam.End()->GetNode().IsStartNode()) + { // end node will be deleted too! see nNodeDiff+1 + aPam.End()->Adjust(SwNodeOffset(-1)); + } + assert(!aPam.End()->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 ); + + // remove tracking of the table row, if needed + lcl_RemoveTrackingOfTableRow( updatePaM.End(), /*bRejectDelete=*/true ); + + 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->GetNode().GetTextNode(); + if( pTNd ) + { + // expand range to the whole paragraph + // and reset only the paragraph attributes + SwPaM aPam( *pTNd, pTNd->GetText().getLength() ); + o3tl::sorted_vector<sal_uInt16> aResetAttrsArray; + + constexpr std::pair<sal_uInt16, sal_uInt16> aResetableSetRange[] = { + { RES_PARATR_BEGIN, RES_PARATR_END - 1 }, + { RES_PARATR_LIST_BEGIN, RES_FRMATR_END - 1 }, + }; + + for (const auto& [nBegin, nEnd] : aResetableSetRange) + { + for (sal_uInt16 i = nBegin; i <= nEnd; ++i) + aResetAttrsArray.insert( i ); + } + + 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; + } + + bool lcl_AcceptInnerInsertRedline(SwRedlineTable& rArr, SwRedlineTable::size_type& rPos, + int nDepth) + { + SwRangeRedline* pRedl = rArr[rPos]; + SwDoc& rDoc = pRedl->GetDoc(); + SwPaM const updatePaM(*pRedl->Start(), *pRedl->End()); + + pRedl->PopAllDataAfter(nDepth); + sw::UpdateFramesForRemoveDeleteRedline(rDoc, updatePaM); + return true; + } + + 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 = rPam.End(); + 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. + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + SwDoc& rDoc = rPam.GetDoc(); + if( !pStt->GetContentIndex() && + !rDoc.GetNodes()[ pStt->GetNodeIndex() - 1 ]->IsContentNode() ) + { + const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pStt, nullptr ); + if( pRedl ) + { + const SwPosition* pRStt = pRedl->Start(); + if( !pRStt->GetContentIndex() && pRStt->GetNodeIndex() == + pStt->GetNodeIndex() - 1 ) + *pStt = *pRStt; + } + } + if( pEnd->GetNode().IsContentNode() && + !rDoc.GetNodes()[ pEnd->GetNodeIndex() + 1 ]->IsContentNode() && + pEnd->GetContentIndex() == pEnd->GetNode().GetContentNode()->Len() ) + { + const SwRangeRedline* pRedl = rDoc.getIDocumentRedlineAccess().GetRedline( *pEnd, nullptr ); + if( pRedl ) + { + const SwPosition* pREnd = pRedl->End(); + if( !pREnd->GetContentIndex() && pREnd->GetNodeIndex() == + pEnd->GetNodeIndex() + 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()->Assign(rDoc.GetNodes().GetEndOfContent()); + } + m_rRedline.GetPoint()->Assign(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) + , mbIsRedlineMove(false) + , mnAutoFormatRedlnCommentNo(0) +{ +} + +RedlineFlags DocumentRedlineManager::GetRedlineFlags() const +{ + return meRedlineFlags; +} + +void DocumentRedlineManager::SetRedlineFlags( RedlineFlags eMode ) +{ + if( meRedlineFlags == eMode ) + return; + + 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, bool); // 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 < maRedlineTable.size(); ++i) + { + SwRangeRedline *const pRedline = maRedlineTable[i]; + (pRedline->*pFnc)(nLoop, i, false); + while (maRedlineTable.size() <= i + || maRedlineTable[i] != pRedline) + { // ensure current position + --i; // a previous redline may have been deleted + } + } + + //SwRangeRedline::MoveFromSection routinely changes + //the keys that mpRedlineTable is sorted by + maRedlineTable.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 maRedlineTable; +} + +SwRedlineTable& DocumentRedlineManager::GetRedlineTable() +{ + return maRedlineTable; +} + +const SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() const +{ + return maExtraRedlineTable; +} + +SwExtraRedlineTable& DocumentRedlineManager::GetExtraRedlineTable() +{ + return maExtraRedlineTable; +} + +bool DocumentRedlineManager::IsInRedlines(const SwNode & rNode) const +{ + if (&rNode.GetNodes() != &m_rDoc.GetNodes()) + return false; + + 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, + sal_uInt32 nMoveIDToDelete) +{ + CHECK_REDLINE( *this ) + + if (!IsRedlineOn() || IsShowOriginal(meRedlineFlags)) + { + 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 AppendResult::IGNORED; + } + + // Collect MoveID's of the redlines we delete. + // If there is only 1, then we should use its ID. (continuing the move) + std::set<sal_uInt32> deletedMoveIDs; + + bool bMerged = false; + + pNewRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + + if( m_rDoc.IsAutoFormatRedline() ) + { + pNewRedl->SetAutoFormat(); + if( moAutoFormatRedlnComment && !moAutoFormatRedlnComment->isEmpty() ) + { + pNewRedl->SetComment( *moAutoFormatRedlnComment ); + pNewRedl->SetSeqNo( mnAutoFormatRedlnCommentNo ); + } + } + + auto [pStt, pEnd] = pNewRedl->StartEnd(); // SwPosition* + { + SwTextNode* pTextNode = pStt->GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pStt->GetContentIndex() > 0 ) + { + OSL_ENSURE( false, "Redline start: non-text-node with content" ); + pStt->SetContent( 0 ); + } + } + else + { + if( pStt->GetContentIndex() > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline start: index after text" ); + pStt->SetContent( pTextNode->Len() ); + } + } + pTextNode = pEnd->GetNode().GetTextNode(); + if( pTextNode == nullptr ) + { + if( pEnd->GetContentIndex() > 0 ) + { + OSL_ENSURE( false, "Redline end: non-text-node with content" ); + pEnd->SetContent(0); + } + } + else + { + if( pEnd->GetContentIndex() > pTextNode->Len() ) + { + OSL_ENSURE( false, "Redline end: index after text" ); + pEnd->SetContent( 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; + const SwRedlineTable::size_type nStartPos = n; + bool bDec = false; + + for( ; pNewRedl && n < maRedlineTable.size(); bDec ? n : ++n ) + { + bDec = false; + + SwRangeRedline* pRedl = maRedlineTable[ n ]; + auto [pRStt, pREnd] = pRedl->StartEnd(); + + // #i8518# remove empty redlines while we're at it + if( ( *pRStt == *pREnd ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + maRedlineTable.DeleteAndDestroy(n); + continue; + } + + SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ); + + if ( SwComparePosition::Before == eCmpPos && !IsPrevPos( *pEnd, *pRStt )) + break; + + switch( pNewRedl->GetType() ) + { + case RedlineType::Insert: + switch( pRedl->GetType() ) + { + case RedlineType::Insert: + if( pRedl->IsOwnRedline( *pNewRedl ) && + // don't join inserted characters with moved text + !pRedl->IsMoved() ) + { + bool bDelete = false; + bool bMaybeNotify = false; + + // Merge if applicable? + if( (( SwComparePosition::Behind == eCmpPos && + IsPrevPos( *pREnd, *pStt ) ) || + ( SwComparePosition::CollideStart == eCmpPos ) || + ( SwComparePosition::OverlapBehind == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( n+1 >= maRedlineTable.size() || + ( *maRedlineTable[ n+1 ]->Start() >= *pEnd && + *maRedlineTable[ n+1 ]->Start() != *pREnd ) ) ) + { + pRedl->SetEnd( *pEnd, pREnd ); + if( !pRedl->HasValidRange() ) + { + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl ); + } + + bMerged = true; + bDelete = true; + } + else if( (( SwComparePosition::Before == eCmpPos && + IsPrevPos( *pEnd, *pRStt ) ) || + ( SwComparePosition::CollideEnd == eCmpPos ) || + ( SwComparePosition::OverlapBefore == eCmpPos ) ) && + pRedl->CanCombine( *pNewRedl ) && + ( !n || + *maRedlineTable[ n-1 ]->End() != *pRStt )) + { + pRedl->SetStart( *pStt, pRStt ); + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl ); + + bMerged = true; + bDelete = true; + } + else if ( SwComparePosition::Outside == eCmpPos ) + { + // own insert-over-insert redlines: + // just scrap the inside ones + maRedlineTable.DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + { + *pStt = *pREnd; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = bMaybeNotify = true; + } + else if( SwComparePosition::OverlapBefore == eCmpPos ) + { + *pEnd = *pRStt; + if( ( *pStt == *pEnd ) && + ( pNewRedl->GetContentIdx() == nullptr ) ) + bDelete = bMaybeNotify = true; + } + else if( SwComparePosition::Inside == eCmpPos ) + { + bDelete = bMaybeNotify = true; + bMerged = true; + } + else if( SwComparePosition::Equal == eCmpPos ) + bDelete = bMaybeNotify = true; + + if( bDelete ) + { + delete pNewRedl; + pNewRedl = nullptr; + bCompress = true; + + if (bMaybeNotify) + MaybeNotifyRedlineModification(*pRedl, m_rDoc); + + // set IsMoved checking nearby redlines + if (n < maRedlineTable.size()) // in case above 're-insert' failed + maRedlineTable.isMoved(n); + } + } + else if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + maRedlineTable.Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + maRedlineTable.DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.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 ); + maRedlineTable.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; + + MaybeNotifyRedlineModification(*pRedl, m_rDoc); + } + } + break; + case RedlineType::Delete: + if( SwComparePosition::Inside == eCmpPos ) + { + // split up + if( *pEnd != *pREnd ) + { + SwRangeRedline* pCpy = new SwRangeRedline( *pRedl ); + pCpy->SetStart( *pEnd ); + maRedlineTable.Insert( pCpy ); + } + pRedl->SetEnd( *pStt, pREnd ); + if( ( *pStt == *pRStt ) && + ( pRedl->GetContentIdx() == nullptr ) ) + { + maRedlineTable.DeleteAndDestroy( n ); + bDec = true; + } + else if( !pRedl->HasValidRange() ) + { + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.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 ); + maRedlineTable.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 + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl, n ); + bDec = true; + break; + + case SwComparePosition::OverlapBehind: + pRedl->SetEnd( *pStt, pREnd ); + if( *pStt == *pRStt && pRedl->GetContentIdx() == nullptr ) + { + maRedlineTable.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 + maRedlineTable.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 ) + maRedlineTable.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; + + MaybeNotifyRedlineModification(*pRedl, m_rDoc); + 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 ); + maRedlineTable.DeleteAndDestroy( n ); + bDec = true; + } + else if( SwComparePosition::OverlapBehind == eCmpPos ) + pNewRedl->SetStart( *pREnd, pStt ); + else + pNewRedl->SetEnd( *pRStt, pEnd ); + break; + + case SwComparePosition::CollideEnd: + if (pRStt->GetContentIndex() != 0 + && pRStt->GetNode() != pREnd->GetNode()) + { // tdf#147466 HACK: don't combine in this case to avoid the tdf#119571 code from *undeleting* section nodes + break; + } + [[fallthrough]]; + case SwComparePosition::CollideStart: + 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. + maRedlineTable.Insert(pNewRedl); + pRedl->Show(0, maRedlineTable.GetPos(pRedl)); + maRedlineTable.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; + } + + maRedlineTable.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 ) && + // tdf#116084 tdf#121176 don't combine anonymized deletion + // and anonymized insertion, i.e. with the same dummy timestamp + !pRedl->GetRedlineData(0).IsAnonymized() ) + { + // Collect MoveID's of the redlines we delete. + if (nMoveIDToDelete > 1 && maRedlineTable[n]->GetMoved() > 0 + && (eCmpPos == SwComparePosition::Equal + || eCmpPos == SwComparePosition::Inside + || eCmpPos == SwComparePosition::Outside + || eCmpPos == SwComparePosition::OverlapBefore + || eCmpPos == SwComparePosition::OverlapBehind)) + { + deletedMoveIDs.insert(maRedlineTable[n]->GetMoved()); + } + + // 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; + maRedlineTable.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->GetContentIndex() == 0) && + pEnd->GetNode().IsEndNode() ) + { + pEnd->Adjust(SwNodeOffset(-1)); + m_rDoc.getIDocumentContentOperations().DelFullPara( *pNewRedl ); + } + else + m_rDoc.getIDocumentContentOperations().DeleteAndJoin( *pNewRedl ); + + bCompress = true; + } + delete pNewRedl; + pNewRedl = nullptr; + + // No need to call MaybeNotifyRedlineModification, because a notification + // was already sent in DocumentRedlineManager::DeleteRedline + break; + + case SwComparePosition::Outside: + { + maRedlineTable.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 ) + maRedlineTable.DeleteAndDestroy( n ); + else + { + pRedl->SetStart( *pEnd, pRStt ); + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.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 ) + { + maRedlineTable.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, maRedlineTable.GetPos(pRedl)); + } + bCompress = true; + + // set IsMoved checking nearby redlines + SwRedlineTable::size_type nRIdx = maRedlineTable.GetPos(pRedl); + if (nRIdx < maRedlineTable.size()) // in case above 're-insert' failed + maRedlineTable.isMoved(nRIdx); + + } + 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 + maRedlineTable.Remove( n ); + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.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 )) + { + maRedlineTable.Insert(pNewRedl); + pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); + maRedlineTable.Remove( pNewRedl ); + } + } + else + { + pNew = new SwRangeRedline( *pRedl ); + pNew->PushData( *pNewRedl ); + pNew->SetEnd( *pEnd ); + pNewRedl->SetEnd( *pRStt, pEnd ); + pRedl->SetStart( *pNew->End(), pRStt ) ; + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl ); + bDec = true; + } + } + break; + + case SwComparePosition::OverlapBehind: + { + if( *pStt == *pRStt ) + { + pRedl->PushData( *pNewRedl ); + pNewRedl->SetStart( *pREnd, pStt ); + if( IsHideChanges( meRedlineFlags )) + { + maRedlineTable.Insert( pNewRedl ); + pRedl->Hide(0, maRedlineTable.GetPos(pRedl)); + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl ); + } + } + } + break; + default: + break; + } + + // insert the pNew part (if it exists) + if( pNew ) + { + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.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 + maRedlineTable.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 ) ) + maRedlineTable.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; + + MaybeNotifyRedlineModification(*pRedl, m_rDoc); + 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 + maRedlineTable.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; + + MaybeNotifyRedlineModification(*pRedl, m_rDoc); + } + 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 + maRedlineTable.Remove( n ); + maRedlineTable.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 ); + maRedlineTable.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 == 0 || *maRedlineTable[ n-1 ]->End() < *pStt)) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetEnd( *pREnd, pEnd ); + maRedlineTable.DeleteAndDestroy( n ); + bDec = true; + } + break; + case SwComparePosition::CollideStart: + if( pRedl->IsOwnRedline( *pNewRedl ) && + pRedl->CanCombine( *pNewRedl ) && + (n+1 >= maRedlineTable.size() || + (*maRedlineTable[ n+1 ]->Start() >= *pEnd && + *maRedlineTable[ n+1 ]->Start() != *pREnd))) + { + // If that's the case we can merge it, meaning + // the new one covers this well + pNewRedl->SetStart( *pRStt, pStt ); + maRedlineTable.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->GetContentIndex() != 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 (except paragraphs of the removed tables). + + SwContentNode* pDelNd = pStt->GetNode().GetContentNode(); + // start copying the style of the first paragraph from the end of the range + SwContentNode* pTextNd = pEnd->GetNode().GetContentNode(); + SwNodeIndex aIdx( pEnd->GetNode() ); + bool bFirst = true; + + while (pTextNd != nullptr && pDelNd->GetIndex() < pTextNd->GetIndex()) + { + if( pTextNd->IsTextNode() ) + { + 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() ); + } + + // skip empty redlines without ExtraData + // FIXME: maybe checking pExtraData is redundant here + if ( pExtraData || *pPar->Start() != *pPar->End() ) + maRedlineTable.Insert( pPar ); + else + delete pPar; + } + + // modify paragraph formatting + lcl_CopyStyle(*pStt, aPos); + } + + if (bFirst) + bFirst = false; + + // Jump to the previous paragraph and if needed, skip paragraphs of + // the removed table(s) in the range to avoid leaving empty tables + // because of the non-continuous redline range over the table. + // FIXME: this is not enough for tables with inner redlines, where + // tracked deletion of the text containing such a table leaves an + // empty table at the place of the table (a problem inherited from OOo). + pTextNd = nullptr; + while( --aIdx > *pDelNd && !aIdx.GetNode().IsContentNode() ) + { + // possible table end + if( aIdx.GetNode().IsEndNode() && aIdx.GetNode().FindTableNode() ) + { + SwNodeIndex aIdx2 = aIdx; + // search table start and skip table paragraphs + while ( pDelNd->GetIndex() < aIdx2.GetIndex() ) + { + SwTableNode* pTable = aIdx2.GetNode().GetTableNode(); + if( pTable && + pTable->EndOfSectionNode()->GetIndex() == aIdx.GetIndex() ) + { + aIdx = aIdx2; + break; + } + --aIdx2; + } + } + } + + if (aIdx.GetNode().IsContentNode()) + pTextNd = aIdx.GetNode().GetContentNode(); + } + } + + // delete tables of the deletion explicitly, to avoid + // remaining empty tables after accepting the rejection + // and visible empty tables in Hide Changes mode + // (this was the case, if tables have already contained + // other tracked changes) + // FIXME: because of recursive nature of AppendRedline, + // this doesn't work for selections with multiple tables + if ( m_rDoc.GetIDocumentUndoRedo().DoesUndo() ) + { + SwNodeIndex aSttIdx( pStt->GetNode() ); + SwNodeIndex aEndIdx( pEnd->GetNode() ); + while ( aSttIdx < aEndIdx ) + { + if ( aSttIdx.GetNode().IsTableNode() ) + { + SvxPrintItem aHasTextChangesOnly(RES_PRINT, false); + SwCursor aCursor( SwPosition(aSttIdx), nullptr ); + m_rDoc.SetRowNotTracked( aCursor, aHasTextChangesOnly, /*bAll=*/true ); + } + ++aSttIdx; + } + } + } + bool const ret = maRedlineTable.Insert( pNewRedl ); + assert(ret || !pNewRedl); + if (ret && !pNewRedl) + { + bMerged = true; // treat InsertWithValidRanges as "merge" + } + } + } + + // If we deleted moved redlines, and there was only 1 MoveID, then we should use that + // We overwrite those that was given right now, so it cannot be deeper under other redline + if (nMoveIDToDelete > 1 && deletedMoveIDs.size() == 1) + { + sal_uInt32 nNewMoveID = *(deletedMoveIDs.begin()); + if (nNewMoveID > 1) // MoveID==1 is for old, unrecognised moves, leave them alone + { + for (n = 0; n < maRedlineTable.size(); ++n) + { + if (maRedlineTable[n]->GetMoved() == nMoveIDToDelete) + { + maRedlineTable[n]->SetMoved(nNewMoveID); + } + } + } + } + + if( bCompress ) + CompressRedlines(nStartPos); + + 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 + + maExtraRedlineTable.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 + + maExtraRedlineTable.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(size_t nStartIndex) +{ + CHECK_REDLINE( *this ) + + void (SwRangeRedline::*pFnc)(sal_uInt16, size_t, bool) = 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 + if (nStartIndex == 0) + nStartIndex = 1; + for( SwRedlineTable::size_type n = nStartIndex; n < maRedlineTable.size(); ++n ) + { + SwRangeRedline* pPrev = maRedlineTable[ n-1 ], + * pCur = maRedlineTable[ n ]; + auto [pPrevStt,pPrevEnd] = pPrev->StartEnd(); + auto [pCurStt, pCurEnd] = pCur->StartEnd(); + + if( *pPrevEnd == *pCurStt && pPrev->CanCombine( *pCur ) && + pPrevStt->GetNode().StartOfSectionNode() == + pCurEnd->GetNode().StartOfSectionNode() && + !pCurEnd->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() ); + maRedlineTable.DeleteAndDestroy( n ); + --n; + if( pFnc ) + (pPrev->*pFnc)(0, nPrevIndex, false); + } + } + CHECK_REDLINE( *this ) + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +bool DocumentRedlineManager::SplitRedline( const SwPaM& rRange ) +{ + bool bChg = false; + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + SwRedlineTable::size_type n = 0; + //FIXME overlapping problem GetRedline( *pStt, &n ); + for ( ; n < maRedlineTable.size(); ++n) + { + SwRangeRedline * pRedline = maRedlineTable[ n ]; + auto [pRedlineStart, pRedlineEnd] = pRedline->StartEnd(); + if (*pRedlineStart <= *pStt && *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); + maRedlineTable.DeleteAndDestroy( n-- ); + pRedline = nullptr; + break; + } + if (pRedline && !pRedline->HasValidRange()) + { + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedline, n ); + } + if( pNew ) + maRedlineTable.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)); + } + } + + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + SwRedlineTable::size_type n = 0; + GetRedline( *pStt, &n ); + for( ; n < maRedlineTable.size() ; ++n ) + { + SwRangeRedline* pRedl = maRedlineTable[ n ]; + if( RedlineType::Any != nDelType && nDelType != pRedl->GetType() ) + continue; + + auto [pRStt, pREnd] = pRedl->StartEnd(); // SwPosition* + switch( ComparePosition( *pStt, *pEnd, *pRStt, *pREnd ) ) + { + case SwComparePosition::Equal: + case SwComparePosition::Outside: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + maRedlineTable.DeleteAndDestroy( n-- ); + bChg = true; + break; + + case SwComparePosition::OverlapBefore: + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + pRedl->SetStart( *pEnd, pRStt ); + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Add); + // re-insert + maRedlineTable.Remove( n ); + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.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 + maRedlineTable.Remove( n ); + maRedlineTable.Insert( pRedl ); + --n; + } + if( pCpy ) + maRedlineTable.Insert( pCpy ); + } + } + break; + + case SwComparePosition::CollideEnd: + // remove (not hidden) empty redlines created for fixing tdf#119571 + // (Note: hidden redlines are all empty, i.e. start and end are equal.) + if ( pRedl->HasMark() && *pRedl->GetMark() == *pRedl->GetPoint() ) + { + pRedl->InvalidateRange(SwRangeRedline::Invalidation::Remove); + maRedlineTable.DeleteAndDestroy( n-- ); + bChg = true; + break; + } + [[fallthrough]]; + + case SwComparePosition::Before: + n = maRedlineTable.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 SwNodeOffset nNdIdx = rNd.GetIndex(); + // if the table only contains good (i.e. non-overlapping) data, we can do a binary search + if (!maRedlineTable.HasOverlappingElements()) + { + // binary search to the first redline with end >= the needle + auto it = std::lower_bound(maRedlineTable.begin(), maRedlineTable.end(), rNd, + [&nNdIdx](const SwRangeRedline* lhs, const SwNode& /*rhs*/) + { + return lhs->End()->GetNodeIndex() < nNdIdx; + }); + for( ; it != maRedlineTable.end(); ++it) + { + const SwRangeRedline* pTmp = *it; + auto [pStart, pEnd] = pTmp->StartEnd(); // SwPosition* + SwNodeOffset nStart = pStart->GetNodeIndex(), + nEnd = pEnd->GetNodeIndex(); + + if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && + nStart <= nNdIdx && nNdIdx <= nEnd ) + return std::distance(maRedlineTable.begin(), it); + + if( nStart > nNdIdx ) + break; + } + } + else + { + for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n ) + { + const SwRangeRedline* pTmp = maRedlineTable[ n ]; + SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(), + nMk = pTmp->GetMark()->GetNodeIndex(); + if( nPt < nMk ) + std::swap( nMk, nPt ); + + if( ( RedlineType::Any == nType || nType == pTmp->GetType()) && + nMk <= nNdIdx && nNdIdx <= nPt ) + return n; + + if( nMk > nNdIdx ) + break; + } + } + return SwRedlineTable::npos; + + // #TODO - add 'SwExtraRedlineTable' also ? +} + +SwRedlineTable::size_type +DocumentRedlineManager::GetRedlineEndPos(SwRedlineTable::size_type nStartPos, const SwNode& rNd, + RedlineType nType) const +{ + //if the start is already invalid + if (nStartPos >= maRedlineTable.size()) + return nStartPos; + + const SwNodeOffset nNdIdx = rNd.GetIndex(); + SwRedlineTable::size_type nEndPos = nStartPos; + SwRedlineTable::size_type nEndPosTry = nEndPos + 1; + + while (nEndPosTry < maRedlineTable.size() + && maRedlineTable[nEndPosTry]->Start()->GetNodeIndex() <= nNdIdx) + { + if (RedlineType::Any == nType || nType == maRedlineTable[nEndPosTry]->GetType()) + { + nEndPos = nEndPosTry; + } + nEndPosTry++; + } + return nEndPos; +} + +void DocumentRedlineManager::UpdateRedlineContentNode(SwRedlineTable::size_type nStartPos, + SwRedlineTable::size_type nEndPos) const +{ + for (SwRedlineTable::size_type n = nStartPos; n <= nEndPos; ++n) + { + //just in case we got wrong input + if (n >= maRedlineTable.size()) + return; + + SwPosition* pStart = maRedlineTable[n]->Start(); + SwPosition* pEnd = maRedlineTable[n]->End(); + SwContentNode* pCont = pStart->GetNode().GetContentNode(); + if (pCont) + { + pStart->nContent.Assign(pCont, pStart->nContent.GetIndex()); + } + pCont = pEnd->GetNode().GetContentNode(); + if (pCont) + { + pEnd->nContent.Assign(pCont, pEnd->nContent.GetIndex()); + } + } +} + +bool DocumentRedlineManager::HasRedline( const SwPaM& rPam, RedlineType nType, bool bStartOrEndInRange ) const +{ + SwPosition currentStart(*rPam.Start()); + SwPosition currentEnd(*rPam.End()); + const SwNode& rEndNode(currentEnd.GetNode()); + + for( SwRedlineTable::size_type n = GetRedlinePos( rPam.Start()->GetNode(), nType ); + n < maRedlineTable.size(); ++n ) + { + const SwRangeRedline* pTmp = maRedlineTable[ n ]; + + if ( pTmp->Start()->GetNode() > rEndNode ) + break; + + if( RedlineType::Any != nType && nType != pTmp->GetType() ) + continue; + + // redline over the range + if ( currentStart < *pTmp->End() && *pTmp->Start() <= currentEnd && + // starting or ending within the range + ( !bStartOrEndInRange || + ( currentStart < *pTmp->Start() || *pTmp->End() < currentEnd ) ) ) + { + return true; + } + } + return false; +} + +const SwRangeRedline* DocumentRedlineManager::GetRedline( const SwPosition& rPos, + SwRedlineTable::size_type* pFndPos ) const +{ + SwRedlineTable::size_type nO = maRedlineTable.size(), nM, nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + nM = nU + ( nO - nU ) / 2; + const SwRangeRedline* pRedl = maRedlineTable[ nM ]; + auto [pStt, pEnd] = pRedl->StartEnd(); + if( pEnd == pStt + ? *pStt == rPos + : ( *pStt <= rPos && rPos < *pEnd ) ) + { + while( nM && rPos == *maRedlineTable[ nM - 1 ]->End() && + rPos == *maRedlineTable[ nM - 1 ]->Start() ) + { + --nM; + pRedl = maRedlineTable[ 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 >= *maRedlineTable[ nM - 1 ]->Start() && + rPos <= *maRedlineTable[ nM - 1 ]->End() && + ( RedlineType::Insert == maRedlineTable[ nM - 1 ]->GetType() ) ) + { + --nM; + pRedl = maRedlineTable[ nM ]; + } + else if( ( nM + 1 ) <= nO && rPos >= *maRedlineTable[ nM + 1 ]->Start() && + rPos <= *maRedlineTable[ nM + 1 ]->End() && + ( RedlineType::Insert == maRedlineTable[ nM + 1 ]->GetType() ) ) + { + ++nM; + pRedl = maRedlineTable[ 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::AcceptRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, + bool bCallDelete) +{ + bool bRet = false; + + SwRangeRedline* pTmp = maRedlineTable[nPosOrigin]; + SwRedlineTable::size_type nRdlIdx = nPosEnd + 1; + SwRedlineData aOrigData = pTmp->GetRedlineData(0); + + SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex(); + sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex(); + SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex(); + sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex(); + do + { + nRdlIdx--; + pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->Start()->GetNodeIndex() < nPamStartNI + || (pTmp->Start()->GetNodeIndex() == nPamStartNI + && pTmp->Start()->GetContentIndex() < nPamStartCI)) + break; + + if (pTmp->End()->GetNodeIndex() > nPamEndtNI + || (pTmp->End()->GetNodeIndex() == nPamEndtNI + && pTmp->End()->GetContentIndex() > nPamEndCI)) + { + } + else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp)); + } + nPamEndtNI = pTmp->Start()->GetNodeIndex(); + nPamEndCI = pTmp->Start()->GetContentIndex(); + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + nRdlIdx++; //we will decrease it in the loop anyway. + } + else if (aOrigData.GetType() == RedlineType::Insert + && pTmp->GetType() == RedlineType::Delete && pTmp->GetStackCount() > 1 + && pTmp->GetType(1) == RedlineType::Insert + && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData)) + { + // The Insert redline we want to accept has a deletion redline too + // we should leave the deletion redline, and only accept the inner insert. + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp, 1)); + } + nPamEndtNI = pTmp->Start()->GetNodeIndex(); + nPamEndCI = pTmp->Start()->GetContentIndex(); + bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1); + nRdlIdx++; //we will decrease it in the loop anyway. + } + } while (nRdlIdx > 0); + return bRet; +} + +bool DocumentRedlineManager::AcceptMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete) +{ + assert(nMovedID > 1); // 0, and 1 is reserved + bool bRet = false; + SwRedlineTable::size_type nRdlIdx = maRedlineTable.size(); + + while (nRdlIdx > 0) + { + nRdlIdx--; + SwRangeRedline* pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->GetMoved(0) == nMovedID + || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp)); + } + + if (pTmp->GetMoved(0) == nMovedID) + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + else + bRet |= lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1); + + nRdlIdx++; //we will decrease it in the loop anyway. + } + } + return bRet; +} + +bool DocumentRedlineManager::AcceptRedline(SwRedlineTable::size_type nPos, bool bCallDelete, + bool bRange) +{ + 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 = maRedlineTable[ nPos ]; + bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized(); + + pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true); + pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true); + 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(); + + if (bRange && !nSeqNo && !bAnonym + && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode()) + { + sal_uInt32 nMovedID = pTmp->GetMoved(0); + if (nMovedID > 1) + { + // Accept all redlineData with this unique move id + bRet |= AcceptMovedRedlines(nMovedID, bCallDelete); + } + else + { + SwRedlineTable::size_type nPosStart = nPos; + SwRedlineTable::size_type nPosEnd = nPos; + + maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true); + + // Accept redlines between pPamStart-pPamEnd. + // but only those that can be combined with the selected. + bRet |= AcceptRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete); + } + } + else do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pTmp) ); + } + + bRet |= lcl_AcceptRedline( maRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos ) + : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = maRedlineTable[ 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, sal_Int8 nDepth ) +{ + // 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. + std::shared_ptr<SwUnoCursor> const pPam(m_rDoc.CreateUnoCursor(*rPam.GetPoint(), false)); + if (rPam.HasMark()) + { + pPam->SetMark(); + *pPam->GetMark() = *rPam.GetMark(); + } + lcl_AdjustRedlineRange(*pPam); + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::ACCEPT_REDLINE, nullptr ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoAcceptRedline>(*pPam, nDepth)); + } + + int nRet = 0; + if (nDepth == 0) + { + nRet = lcl_AcceptRejectRedl(lcl_AcceptRedline, maRedlineTable, bCallDelete, *pPam); + } + else + { + // For now it is called only if it is an Insert redline in a delete redline. + SwRedlineTable::size_type nRdlIdx = 0; + maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx); + if (lcl_AcceptInnerInsertRedline(maRedlineTable, nRdlIdx, 1)) + nRet = 1; + } + 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 ) +{ + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + const SwNodeOffset nSttIdx = pStt->GetNodeIndex(); + const SwNodeOffset nEndIdx = pEnd->GetNodeIndex(); + + for( SwRedlineTable::size_type n = 0; n < maRedlineTable.size() ; ++n ) + { + const SwRangeRedline* pTmp = maRedlineTable[ n ]; + SwNodeOffset nPt = pTmp->GetPoint()->GetNodeIndex(), + nMk = pTmp->GetMark()->GetNodeIndex(); + if( nPt < nMk ) + std::swap( nMk, nPt ); + + if( RedlineType::ParagraphFormat == pTmp->GetType() && + ( (nSttIdx <= nMk && nMk <= nEndIdx) || (nSttIdx <= nPt && nPt <= nEndIdx) ) ) + AcceptRedline( n, false ); + + if( nMk > nEndIdx ) + break; + } +} + +bool DocumentRedlineManager::RejectRedlineRange(SwRedlineTable::size_type nPosOrigin, + SwRedlineTable::size_type& nPosStart, + SwRedlineTable::size_type& nPosEnd, + bool bCallDelete) +{ + bool bRet = false; + + SwRangeRedline* pTmp = maRedlineTable[nPosOrigin]; + SwRedlineTable::size_type nRdlIdx = nPosEnd + 1; + SwRedlineData aOrigData = pTmp->GetRedlineData(0); + + SwNodeOffset nPamStartNI = maRedlineTable[nPosStart]->Start()->GetNodeIndex(); + sal_Int32 nPamStartCI = maRedlineTable[nPosStart]->Start()->GetContentIndex(); + SwNodeOffset nPamEndtNI = maRedlineTable[nPosEnd]->End()->GetNodeIndex(); + sal_Int32 nPamEndCI = maRedlineTable[nPosEnd]->End()->GetContentIndex(); + do + { + nRdlIdx--; + pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->Start()->GetNodeIndex() < nPamStartNI + || (pTmp->Start()->GetNodeIndex() == nPamStartNI + && pTmp->Start()->GetContentIndex() < nPamStartCI)) + break; + + if (pTmp->End()->GetNodeIndex() > nPamEndtNI + || (pTmp->End()->GetNodeIndex() == nPamEndtNI + && pTmp->End()->GetContentIndex() > nPamEndCI)) + { + } + else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoRejectRedline> pUndoRdl + = std::make_unique<SwUndoRejectRedline>(*pTmp); +#if OSL_DEBUG_LEVEL > 0 + pUndoRdl->SetRedlineCountDontCheck(true); +#endif + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl)); + } + nPamEndtNI = pTmp->Start()->GetNodeIndex(); + nPamEndCI = pTmp->Start()->GetContentIndex(); + bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete); + nRdlIdx++; //we will decrease it in the loop anyway. + } + else if (aOrigData.GetType() == RedlineType::Insert + && pTmp->GetType() == RedlineType::Delete && pTmp->GetStackCount() > 1 + && pTmp->GetType(1) == RedlineType::Insert + && pTmp->GetRedlineData(1).CanCombineForAcceptReject(aOrigData)) + { + // The Insert redline we want to reject has a deletion redline too + // without the insert, the delete is meaningless + // so we rather just accept the deletion redline + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoRejectRedline> pUndoRdl + = std::make_unique<SwUndoRejectRedline>(*pTmp, 1); +#if OSL_DEBUG_LEVEL > 0 + pUndoRdl->SetRedlineCountDontCheck(true); +#endif + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl)); + } + nPamEndtNI = pTmp->Start()->GetNodeIndex(); + nPamEndCI = pTmp->Start()->GetContentIndex(); + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + nRdlIdx++; //we will decrease it in the loop anyway. + } + + } while (nRdlIdx > 0); + return bRet; +} + +bool DocumentRedlineManager::RejectMovedRedlines(sal_uInt32 nMovedID, bool bCallDelete) +{ + assert(nMovedID > 1); // 0, and 1 is reserved + bool bRet = false; + SwRedlineTable::size_type nRdlIdx = maRedlineTable.size(); + + while (nRdlIdx > 0) + { + nRdlIdx--; + SwRangeRedline* pTmp = maRedlineTable[nRdlIdx]; + if (pTmp->GetMoved(0) == nMovedID + || (pTmp->GetStackCount() > 1 && pTmp->GetMoved(1) == nMovedID)) + { + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + std::unique_ptr<SwUndoRejectRedline> pUndoRdl + = std::make_unique<SwUndoRejectRedline>(*pTmp); +#if OSL_DEBUG_LEVEL > 0 + pUndoRdl->SetRedlineCountDontCheck(true); +#endif + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::move(pUndoRdl)); + } + + if (pTmp->GetMoved(0) == nMovedID) + bRet |= lcl_RejectRedline(maRedlineTable, nRdlIdx, bCallDelete); + else + bRet |= lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete); + + nRdlIdx++; //we will decrease it in the loop anyway. + } + } + return bRet; +} + +bool DocumentRedlineManager::RejectRedline(SwRedlineTable::size_type nPos, + bool bCallDelete, bool bRange) +{ + 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 = maRedlineTable[ nPos ]; + bool bAnonym = pTmp->GetRedlineData(0).IsAnonymized(); + + pTmp->Show(0, maRedlineTable.GetPos(pTmp), /*bForced=*/true); + pTmp->Show(1, maRedlineTable.GetPos(pTmp), /*bForced=*/true); + 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(); + + if (bRange && !nSeqNo && !bAnonym + && !pTmp->Start()->GetNode().StartOfSectionNode()->IsTableNode()) + { + sal_uInt32 nMovedID = pTmp->GetMoved(0); + if (nMovedID > 1) + { + // Reject all redlineData with this unique move id + bRet |= RejectMovedRedlines(nMovedID, bCallDelete); + } + else + { + SwRedlineTable::size_type nPosStart = nPos; + SwRedlineTable::size_type nPosEnd = nPos; + maRedlineTable.getConnectedArea(nPos, nPosStart, nPosEnd, true); + + // Reject items between pPamStart-pPamEnd + // but only those that can be combined with the selected. + + bRet |= RejectRedlineRange(nPos, nPosStart, nPosEnd, bCallDelete); + } + } + else do { + + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRejectRedline>( *pTmp ) ); + } + + bRet |= lcl_RejectRedline( maRedlineTable, nPos, bCallDelete ); + + if( nSeqNo ) + { + if( SwRedlineTable::npos == nPos ) + nPos = 0; + SwRedlineTable::size_type nFndPos = 2 == nLoopCnt + ? maRedlineTable.FindNextSeqNo( nSeqNo, nPos ) + : maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ); + if( SwRedlineTable::npos != nFndPos || ( 0 != ( --nLoopCnt ) && + SwRedlineTable::npos != ( nFndPos = + maRedlineTable.FindPrevSeqNo( nSeqNo, nPos ))) ) + { + nPos = nFndPos; + pTmp = maRedlineTable[ 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, sal_Int8 nDepth ) +{ + // 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, nDepth) ); + } + + int nRet = 0; + if (nDepth == 0) + { + nRet = lcl_AcceptRejectRedl(lcl_RejectRedline, maRedlineTable, bCallDelete, aPam); + } + else + { + // For now it is called only if it is an Insert redline in a delete redline. + SwRedlineTable::size_type nRdlIdx = 0; + maRedlineTable.FindAtPosition(*rPam.Start(), nRdlIdx); + if (lcl_AcceptRedline(maRedlineTable, nRdlIdx, bCallDelete)) + nRet = 1; + } + + 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 (maRedlineTable.size() > 1) + { + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(maRedlineTable.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 (!maRedlineTable.empty() && bSuccess) + { + if (bAccept) + bSuccess = AcceptRedline(maRedlineTable.size() - 1, true); + else + bSuccess = RejectRedline(maRedlineTable.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->GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pEnd->GetNode() ); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.GetNode() && + pCNd->Len() == rSttPos.GetContentIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->End(); + } + + do { + bRestart = false; + + for( ; !pFnd && n < maRedlineTable.size(); ++n ) + { + pFnd = maRedlineTable[ 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 < maRedlineTable.size() ) + { + const SwRangeRedline* pTmp = maRedlineTable[ 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; + SwPosition* pPos = rPam.GetMark(); + if( !pPos->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pPos ); + if( pCNd ) + { + if( pPos->GetNode() <= rPam.GetPoint()->GetNode() ) + pPos->Assign( *pCNd, 0 ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pPos = rPam.GetPoint(); + if( !pPos->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pPos ); + if( pCNd ) + { + if( pPos->GetNode() >= rPam.GetMark()->GetNode() ) + pPos->Assign( *pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + } + + if( !pFnd || *rPam.GetMark() == *rPam.GetPoint() ) + { + if( n < maRedlineTable.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->GetNode().IsContentNode() ) + { + SwNodeIndex aTmp( pStt->GetNode() ); + SwContentNode* pCNd = m_rDoc.GetNodes().GoNextSection( &aTmp ); + if( !pCNd || ( aTmp == rSttPos.GetNode() && + !rSttPos.GetContentIndex() )) + pFnd = nullptr; + } + if( pFnd ) + rSttPos = *pFnd->Start(); + } + + do { + bRestart = false; + + while( !pFnd && 0 < n ) + { + pFnd = maRedlineTable[ --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 = maRedlineTable[ --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; + SwPosition* pPos = rPam.GetMark(); + if( !pPos->GetNode().IsContentNode() ) + { + pCNd = SwNodes::GoPrevSection( pPos ); + if( pCNd ) + { + if( pPos->GetNode() >= rPam.GetPoint()->GetNode() ) + pPos->Assign( *pCNd, pCNd->Len() ); + else + pFnd = nullptr; + } + } + + if( pFnd ) + { + pPos = rPam.GetPoint(); + if( !pPos->GetNode().IsContentNode() ) + { + pCNd = m_rDoc.GetNodes().GoNextSection( pPos ); + if( pCNd ) + { + if( pPos->GetNode() <= rPam.GetMark()->GetNode() ) + pPos->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; + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + SwRedlineTable::size_type n = 0; + if( GetRedlineTable().FindAtPosition( *pStt, n ) ) + { + for( ; n < maRedlineTable.size(); ++n ) + { + bRet = true; + SwRangeRedline* pTmp = maRedlineTable[ 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 ) + { + moAutoFormatRedlnComment = *pText; + } + else + { + moAutoFormatRedlnComment.reset(); + } + + mnAutoFormatRedlnCommentNo = nSeqNo; +} + +void DocumentRedlineManager::HideAll( bool bDeletion ) +{ + const SwRedlineTable& rTable = GetRedlineTable(); + for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i) + { + SwRangeRedline* pRedline = rTable[i-1]; + if ( pRedline->GetType() == RedlineType::Delete ) + { + if ( bDeletion && pRedline->IsVisible() ) + { + pRedline->Hide(0, rTable.GetPos(pRedline), false); + pRedline->Hide(1, rTable.GetPos(pRedline), false); + } + else if ( !bDeletion && !pRedline->IsVisible() ) + { + pRedline->Show(0, rTable.GetPos(pRedline), true); + pRedline->Show(1, rTable.GetPos(pRedline), true); + } + } + else if ( pRedline->GetType() == RedlineType::Insert ) + { + if ( !bDeletion && pRedline->IsVisible() ) + { + pRedline->ShowOriginal(0, rTable.GetPos(pRedline), false); + pRedline->ShowOriginal(1, rTable.GetPos(pRedline), false); + } + else if ( bDeletion && !pRedline->IsVisible() ) + { + pRedline->Show(0, rTable.GetPos(pRedline), true); + pRedline->Show(1, rTable.GetPos(pRedline), true); + } + } + } +} + +void DocumentRedlineManager::ShowAll() +{ + const SwRedlineTable& rTable = GetRedlineTable(); + for (SwRedlineTable::size_type i = rTable.size(); i > 0; --i) + { + SwRangeRedline* pRedline = rTable[i-1]; + if ( !pRedline->IsVisible() ) + { + pRedline->Show(0, rTable.GetPos(pRedline), true); + pRedline->Show(1, rTable.GetPos(pRedline), true); + } + } +} + +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 0000000000..ebe116c682 --- /dev/null +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -0,0 +1,1089 @@ +/* -*- 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/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <osl/diagnose.h> +#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), + 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), + mbTabOverSpacing(false), + mbTreatSingleColumnBreakAsPageBreak(false), + mbSurroundTextWrapSmall(false), + mbPropLineSpacingShrinksFirstLine(true), + mbSubtractFlys(false), + mApplyParagraphMarkFormatToNumbering(false), + mbLastBrowseMode( false ), + mbDisableOffPagePositioning ( false ), + mbProtectBookmarks(false), + mbProtectFields(false), + mbHeaderSpacingBelowLastPara(false), + mbFrameAutowidthWithMorePara(false), + mbGutterAtTop(false), + mbFootnoteInColumnToPageEnd(false), + mnImagePreferredDPI(0), + mbAutoFirstLineIndentDisregardLineSpace(true), + mbNoNumberingShowFollowBy(false), + mbDropCapPunctuation(true), + mbUseVariableWidthNBSP(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 = true; + 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); + mbUseVariableWidthNBSP + = aOptions.GetDefault(SvtCompatibilityEntry::Index::UseVariableWidthNBSP); + } + 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; + mbUseVariableWidthNBSP = false; + } + + // 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::TAB_OVER_SPACING: return mbTabOverSpacing; + 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::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; + case DocumentSettingId::FRAME_AUTOWIDTH_WITH_MORE_PARA: return mbFrameAutowidthWithMorePara; + case DocumentSettingId::GUTTER_AT_TOP: + return mbGutterAtTop; + case DocumentSettingId::FOOTNOTE_IN_COLUMN_TO_PAGEEND: return mbFootnoteInColumnToPageEnd; + case DocumentSettingId::AUTO_FIRST_LINE_INDENT_DISREGARD_LINE_SPACE: + return mbAutoFirstLineIndentDisregardLineSpace; + case DocumentSettingId::HYPHENATE_URLS: return mbHyphenateURLs; + case DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES: + return mbDoNotBreakWrappedTables; + case DocumentSettingId::ALLOW_TEXT_AFTER_FLOATING_TABLE_BREAK: + return mbAllowTextAfterFloatingTableBreak; + case DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING: + return mbJustifyLinesWithShrinking; + case DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY: return mbNoNumberingShowFollowBy; + case DocumentSettingId::DROP_CAP_PUNCTUATION: return mbDropCapPunctuation; + case DocumentSettingId::USE_VARIABLE_WIDTH_NBSP: return mbUseVariableWidthNBSP; + 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(m_rDoc); + // 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::TAB_OVER_SPACING: + mbTabOverSpacing = 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; + + case DocumentSettingId::AUTO_FIRST_LINE_INDENT_DISREGARD_LINE_SPACE: + mbAutoFirstLineIndentDisregardLineSpace = value; + break; + + case DocumentSettingId::HYPHENATE_URLS: + mbHyphenateURLs = value; + break; + + case DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES: + mbDoNotBreakWrappedTables = value; + break; + + case DocumentSettingId::ALLOW_TEXT_AFTER_FLOATING_TABLE_BREAK: + mbAllowTextAfterFloatingTableBreak = value; + break; + + case DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING: + mbJustifyLinesWithShrinking = value; + break; + + case DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY: + mbNoNumberingShowFollowBy = value; + break; + + case DocumentSettingId::DROP_CAP_PUNCTUATION: + mbDropCapPunctuation = value; + break; + + case DocumentSettingId::USE_VARIABLE_WIDTH_NBSP: + mbUseVariableWidthNBSP = value; + break; + + // COMPATIBILITY FLAGS END + + case DocumentSettingId::BROWSE_MODE: //can be used temporary (load/save) when no SwViewShell is available + // Can't render in webview successfully. + if (comphelper::LibreOfficeKit::isActive()) + mbLastBrowseMode = false; + else + 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::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; + case DocumentSettingId::FRAME_AUTOWIDTH_WITH_MORE_PARA: + mbFrameAutowidthWithMorePara = value; + break; + case DocumentSettingId::GUTTER_AT_TOP: + mbGutterAtTop = value; + break; + case DocumentSettingId::FOOTNOTE_IN_COLUMN_TO_PAGEEND: + mbFootnoteInColumnToPageEnd = 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 ) + return; + + 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: this is the subset of mbLastBrowseMode, which can be temporarily enabled even + // for non-SwWebDocShells. + // No mbIsGlobalDoc: this is true for SwGlobalDocShells. + mbGlblDocSaveLinks = rSource.mbGlblDocSaveLinks; + mbIsLabelDoc = rSource.mbIsLabelDoc; + mbPurgeOLE = rSource.mbPurgeOLE; + mbKernAsianPunctuation = rSource.mbKernAsianPunctuation; + mbParaSpaceMax = rSource.mbParaSpaceMax; + mbParaSpaceMaxAtPages = rSource.mbParaSpaceMaxAtPages; + mbTabCompat = rSource.mbTabCompat; + mbUseVirtualDevice = rSource.mbUseVirtualDevice; + mbAddFlyOffsets = rSource.mbAddFlyOffsets; + mbAddVerticalFlyOffsets = rSource.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; + mbMathBaselineAlignment = rSource.mbMathBaselineAlignment; + mbStylesNoDefault = rSource.mbStylesNoDefault; + 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; + mbProtectForm = rSource.mbProtectForm; + mbMsWordCompTrailingBlanks = rSource.mbMsWordCompTrailingBlanks; + mbMsWordCompMinLineHeightByFly = rSource.mbMsWordCompMinLineHeightByFly; + mbInvertBorderSpacing = rSource.mbInvertBorderSpacing; + mbCollapseEmptyCellPara = rSource.mbCollapseEmptyCellPara; + mbTabAtLeftIndentForParagraphsInList = rSource.mbTabAtLeftIndentForParagraphsInList; + mbSmallCapsPercentage66 = rSource.mbSmallCapsPercentage66; + mbTabOverflow = rSource.mbTabOverflow; + mbUnbreakableNumberings = rSource.mbUnbreakableNumberings; + mbClippedPictures = rSource.mbClippedPictures; + mbBackgroundParaOverDrawings = rSource.mbBackgroundParaOverDrawings; + mbTabOverMargin = rSource.mbTabOverMargin; + mbTabOverSpacing = rSource.mbTabOverSpacing; + mbTreatSingleColumnBreakAsPageBreak = rSource.mbTreatSingleColumnBreakAsPageBreak; + mbSurroundTextWrapSmall = rSource.mbSurroundTextWrapSmall; + mbPropLineSpacingShrinksFirstLine = rSource.mbPropLineSpacingShrinksFirstLine; + mbSubtractFlys = rSource.mbSubtractFlys; + // No mbLastBrowseMode: this is false by default everywhere + mbDisableOffPagePositioning = rSource.mbDisableOffPagePositioning; + mbEmptyDbFieldHidesPara = rSource.mbEmptyDbFieldHidesPara; + mbContinuousEndnotes = rSource.mbContinuousEndnotes; + // No mbProtectBookmarks: this is false by default everywhere + // No mbProtectFields: this is false by default everywhere + mbHeaderSpacingBelowLastPara = rSource.mbHeaderSpacingBelowLastPara; + mbFrameAutowidthWithMorePara = rSource.mbFrameAutowidthWithMorePara; + mbFootnoteInColumnToPageEnd = rSource.mbFootnoteInColumnToPageEnd; + mbDropCapPunctuation = rSource.mbDropCapPunctuation; + mbUseVariableWidthNBSP = rSource.mbUseVariableWidthNBSP; +} + +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 +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("DocumentSettingManager")); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbHTMLMode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHTMLMode).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsGlobalDoc")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsGlobalDoc).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbGlblDocSaveLinks")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbGlblDocSaveLinks).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbIsLabelDoc")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIsLabelDoc).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbPurgeOLE")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPurgeOLE).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbKernAsianPunctuation")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbKernAsianPunctuation).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMax")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMax).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbParaSpaceMaxAtPages")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbParaSpaceMaxAtPages).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabCompat")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabCompat).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseVirtualDevice")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseVirtualDevice).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddFlyOffsets")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddFlyOffsets).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddVerticalFlyOffsets")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddVerticalFlyOffsets).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddExternalLeading")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddExternalLeading).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseHiResolutionVirtualDevice")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseHiResolutionVirtualDevice).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldLineSpacing")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldLineSpacing).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaSpacingToTableCells")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaSpacingToTableCells).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAddParaLineSpacingToTableCells")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAddParaLineSpacingToTableCells).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerObjectPos")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerObjectPos).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseFormerTextWrapping")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseFormerTextWrapping).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbConsiderWrapOnObjPos")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbConsiderWrapOnObjPos).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMathBaselineAlignment")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMathBaselineAlignment).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbStylesNoDefault")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbStylesNoDefault).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbOldNumbering")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbOldNumbering).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreFirstLineIndentInNumbering")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreFirstLineIndentInNumbering).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotJustifyLinesWithManualBreak")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotJustifyLinesWithManualBreak).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotResetParaAttrsForNumFont")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotResetParaAttrsForNumFont).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTableRowKeep")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTableRowKeep).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbIgnoreTabsAndBlanksForLineCalculation")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbIgnoreTabsAndBlanksForLineCalculation).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotCaptureDrawObjsOnPage")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotCaptureDrawObjsOnPage).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbClipAsCharacterAnchoredWriterFlyFrames")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClipAsCharacterAnchoredWriterFlyFrames).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnixForceZeroExtLeading")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnixForceZeroExtLeading).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabRelativeToIndent")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabRelativeToIndent).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbProtectForm")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbProtectForm).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompTrailingBlanks")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompTrailingBlanks).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinLineHeightByFly")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbMsWordCompMinLineHeightByFly).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbInvertBorderSpacing")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbInvertBorderSpacing).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbCollapseEmptyCellPara")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbCollapseEmptyCellPara).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabAtLeftIndentForParagraphsInList")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabAtLeftIndentForParagraphsInList).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbSmallCapsPercentage66")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSmallCapsPercentage66).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverflow")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverflow).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUnbreakableNumberings")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUnbreakableNumberings).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbClippedPictures")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbClippedPictures).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbBackgroundParaOverDrawings")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbBackgroundParaOverDrawings).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverMargin")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTabOverSpacing")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTabOverSpacing).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbTreatSingleColumnBreakAsPageBreak")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbTreatSingleColumnBreakAsPageBreak).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbSurroundTextWrapSmall")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSurroundTextWrapSmall).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbPropLineSpacingShrinksFirstLine")); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbPropLineSpacingShrinksFirstLine).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbSubtractFlys")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbSubtractFlys).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbLastBrowseMode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbLastBrowseMode).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDisableOffPagePositioning")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDisableOffPagePositioning).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbEmptyDbFieldHidesPara")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbEmptyDbFieldHidesPara).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbUseVariableWidthNBSP")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbUseVariableWidthNBSP).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbContinuousEndnotes")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbContinuousEndnotes).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbHeaderSpacingBelowLastPara")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHeaderSpacingBelowLastPara).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbFrameAutowidthWithMorePara")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbFrameAutowidthWithMorePara).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbGutterAtTop")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbGutterAtTop).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbFootnoteInColumnToPageEnd")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbFootnoteInColumnToPageEnd).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbHyphenateURLs")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHyphenateURLs).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotBreakWrappedTables")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbDoNotBreakWrappedTables).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbAllowTextAfterFloatingTableBreak")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbAllowTextAfterFloatingTableBreak).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbJustifyLinesWithShrinking")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbJustifyLinesWithShrinking).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mnImagePreferredDPI")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::number(mnImagePreferredDPI).getStr())); + + (void)xmlTextWriterEndElement(pWriter); + + (void)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 0000000000..bf965d54ba --- /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 0000000000..79df62ff1d --- /dev/null +++ b/sw/source/core/doc/DocumentStatisticsManager.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <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, "sw::DocumentStatisticsManager maStatsUpdateIdle" ) +{ + maStatsUpdateIdle.SetPriority( TaskPriority::LOWEST ); + maStatsUpdateIdle.SetInvokeHandler( LINK( this, DocumentStatisticsManager, DoIdleStatsUpdate ) ); +} + +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) + return; + + if (!bCompleteAsync) + { + maStatsUpdateIdle.Stop(); + while (IncrementalDocStatCalculate( + std::numeric_limits<tools::Long>::max(), bFields)) {} + } + else if (IncrementalDocStatCalculate(5000, bFields)) + maStatsUpdateIdle.Start(); + else + maStatsUpdateIdle.Stop(); +} + +// returns true while there is more to do +bool DocumentStatisticsManager::IncrementalDocStatCalculate(tools::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( SwNodeOffset i = m_rDoc.GetNodes().Count(); i > SwNodeOffset(0) && nChars > 0; ) + { + SwNode* pNd = m_rDoc.GetNodes()[ --i ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + { + tools::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); + auto pStat = aStat.getArray(); + sal_Int32 n=0; + pStat[n].Name = "TableCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nTable); + pStat[n].Name = "ImageCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nGrf); + pStat[n].Name = "ObjectCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nOLE); + if ( mpDocStat->nPage ) + { + pStat[n].Name = "PageCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nPage); + } + pStat[n].Name = "ParagraphCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nPara); + pStat[n].Name = "WordCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nWord); + pStat[n].Name = "CharacterCount"; + pStat[n++].Value <<= static_cast<sal_Int32>(mpDocStat->nChar); + pStat[n].Name = "NonWhitespaceCharacterCount"; + pStat[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 0000000000..56c1274a87 --- /dev/null +++ b/sw/source/core/doc/DocumentStylePoolManager.cxx @@ -0,0 +1,2768 @@ +/* -*- 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 <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 <o3tl/unit_conversion.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> +#include <unotools/syslocale.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/lok.hxx> + +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 +{ + const sal_uInt16 PT_3 = 3 * 20; // 3 pt + const sal_uInt16 PT_6 = 6 * 20; // 6 pt + const sal_uInt16 PT_7 = 7 * 20; // 7 pt + const sal_uInt16 PT_9 = 9 * 20; // 9 pt + const sal_uInt16 PT_10 = 10 * 20; // 10 pt + const sal_uInt16 PT_12 = 12 * 20; // 12 pt + const sal_uInt16 PT_13 = 13 * 20; // 13 pt + const sal_uInt16 PT_14 = 14 * 20; // 14 pt + const sal_uInt16 PT_16 = 16 * 20; // 16 pt + const sal_uInt16 PT_18 = 18 * 20; // 18 pt + const sal_uInt16 PT_24 = 24 * 20; // 24 pt + const sal_uInt16 PT_28 = 28 * 20; // 28 pt + + const sal_uInt16 HTML_PARSPACE = o3tl::convert(5, o3tl::Length::mm, o3tl::Length::twip); + + const sal_uInt16 aHeadlineSizes[ 2 * MAXLEVEL ] = { + // we do everything percentual now: + PT_18, PT_16, PT_14, PT_13, PT_12, + PT_12, PT_10, PT_10, PT_9, PT_9, // normal + + PT_24, PT_18, PT_14, PT_12, PT_10, + PT_7, PT_7, PT_7, PT_7, PT_7 // HTML mode + }; + + tools::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 tools::Long nLeft = rLR.GetLeft(); + const tools::Long nRight = rLR.GetRight(); + const tools::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& rDoc, 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 = rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE); + if( bHTMLMode ) + aHItem.SetHeight( aHeadlineSizes[ MAXLEVEL + nLevel ] ); + else + aHItem.SetHeight( 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 = rDoc.GetOutlineNumRule(); + const SwNumFormat& rNFormat = pOutlineRule->Get( nLevel ); + + if ( rNFormat.GetPositionAndSpaceMode() == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION && + ( rNFormat.GetAbsLSpace() || rNFormat.GetFirstLineOffset() ) ) + { + SvxFirstLineIndentItem firstLine(pColl->GetFormatAttr(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem leftMargin(pColl->GetFormatAttr(RES_MARGIN_TEXTLEFT)); + firstLine.SetTextFirstLineOffsetValue(rNFormat.GetFirstLineOffset()); + //TODO: overflow + leftMargin.SetTextLeft(rNFormat.GetAbsLSpace()); + pColl->SetFormatAttr(firstLine); + pColl->SetFormatAttr(leftMargin); + } + + // 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( *rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT )); + } + + void lcl_SetRegister( SwDoc& rDoc, SfxItemSet& rSet, sal_uInt16 nFact, + bool bHeader, bool bTab ) + { + sal_uInt16 nLeft = o3tl::convert(5 * nFact, o3tl::Length::mm, o3tl::Length::twip); + SvxFirstLineIndentItem const firstLine(0, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(nLeft, RES_MARGIN_TEXTLEFT); + rSet.Put(firstLine); + rSet.Put(leftMargin); + if( bHeader ) + { + SetAllScriptItem( rSet, SvxWeightItem( WEIGHT_BOLD, RES_CHRATR_WEIGHT ) ); + SetAllScriptItem( rSet, SvxFontHeightItem( PT_16, 100, RES_CHRATR_FONTSIZE ) ); + } + if( bTab ) + { + tools::Long nRightMargin = lcl_GetRightMargin( rDoc ); + SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( nRightMargin - nLeft, + SvxTabAdjust::Right, + cDfltDecimalChar, '.' )); + rSet.Put( aTStops ); + } + } + + void lcl_SetNumBul( SwDoc& rDoc, SwTextFormatColl* pColl, + SfxItemSet& rSet, + sal_uInt16 nNxt, SwTwips nEZ, SwTwips nLeft, + SwTwips nUpper, SwTwips nLower ) + { + SvxFirstLineIndentItem const firstLine(sal_uInt16(nEZ), RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(sal_uInt16(nLeft), RES_MARGIN_TEXTLEFT); + rSet.Put(firstLine); + rSet.Put(leftMargin); + SvxULSpaceItem aUL( RES_UL_SPACE ); + aUL.SetUpper( sal_uInt16(nUpper) ); + aUL.SetLower( sal_uInt16(nLower) ); + rSet.Put( aUL ); + + if( pColl ) + pColl->SetNextTextFormatColl( *rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( nNxt )); + } + + void lcl_PutStdPageSizeIntoItemSet( SwDoc& rDoc, SfxItemSet& rSet ) + { + SwPageDesc* pStdPgDsc = rDoc.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 ); + } +} + +const TranslateId 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 +}; + +const TranslateId 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 +const TranslateId 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_ENVELOPE_ADDRESS, + STR_POOLCOLL_SEND_ADDRESS, + STR_POOLCOLL_ENDNOTE, + STR_POOLCOLL_LABEL_DRAWING, + STR_POOLCOLL_COMMENT +}; + +const TranslateId 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 +}; + +const TranslateId STR_POOLCOLL_DOC_ARY[] = +{ + // Category Chapter/Document + STR_POOLCOLL_DOC_TITLE, + STR_POOLCOLL_DOC_SUBTITLE, + STR_POOLCOLL_DOC_APPENDIX +}; + +const TranslateId 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 +}; + +const TranslateId 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 +}; + +const TranslateId STR_POOLCHR_HTML_ARY[] = +{ + STR_POOLCHR_HTML_EMPHASIS, + STR_POOLCHR_HTML_CITATION, + 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 +}; + +const TranslateId STR_POOLFRM_ARY[] = +{ + STR_POOLFRM_FRAME, + STR_POOLFRM_GRAPHIC, + STR_POOLFRM_OLE, + STR_POOLFRM_FORMEL, + STR_POOLFRM_MARGINAL, + STR_POOLFRM_WATERSIGN, + STR_POOLFRM_LABEL +}; + +const TranslateId STR_POOLPAGE_ARY[] = +{ + // Page styles + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_ENVELOPE, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE +}; + +const TranslateId STR_POOLNUMRULE_NUM_ARY[] = +{ + // Numbering styles + STR_POOLNUMRULE_NOLIST, + 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 +const TranslateId 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() ) + { + // in online we can have multiple languages, use translated name + if (comphelper::LibreOfficeKit::isActive()) + { + OUString aName; + SwStyleNameMapper::GetUIName(nId, aName); + if (!aName.isEmpty()) + pNewColl->SetFormatName(aName); + } + + return pNewColl; + } + + if( pNewColl->IsAssignedToListLevelOfOutlineStyle()) + nOutLvlBits |= ( 1 << pNewColl->GetAssignedOutlineStyleLevel() ); + } + + // Didn't find it until here -> create anew + TranslateId pResId; + 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 ); + + { + + if(::IsConditionalByPoolId( nId )) + pNewColl = new SwConditionTextFormatColl( m_rDoc.GetAttrPool(), aNm, !nParent + ? m_rDoc.GetDfltTextFormatColl() + : GetTextCollFromPool( nParent )); + else + 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 + { + auto const first(o3tl::convert(5, o3tl::Length::mm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(first, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(0, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + } + break; + case RES_POOLCOLL_TEXT_NEGIDENT: // Text body neg. indentation + { + auto const first(-o3tl::convert(5, o3tl::Length::mm, o3tl::Length::twip)); + auto const left(o3tl::convert(1, o3tl::Length::cm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(first, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + + SvxTabStopItem aTStops(RES_PARATR_TABSTOP); + aTStops.Insert( SvxTabStop( 0 )); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_TEXT_MOVE: // Text body move + { + auto const left(o3tl::convert(5, o3tl::Length::mm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(0, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + } + break; + + case RES_POOLCOLL_CONFRONTATION: // Text body confrontation + { + auto const first(-o3tl::convert(45, o3tl::Length::mm, o3tl::Length::twip)); + auto const left(o3tl::convert(5, o3tl::Length::cm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(first, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + + SvxTabStopItem aTStops( RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( 0 )); + aSet.Put( aTStops ); + } + break; + case RES_POOLCOLL_MARGINAL: // Text body marginal + { + auto const left(o3tl::convert(4, o3tl::Length::cm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(0, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + } + 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: // Table 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 ); + + tools::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 + { + auto const first(-o3tl::convert(6, o3tl::Length::mm, o3tl::Length::twip)); + auto const left(o3tl::convert(6, o3tl::Length::mm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(first, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + + SetAllScriptItem( aSet, SvxFontHeightItem( PT_10, 100, RES_CHRATR_FONTSIZE ) ); + 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_ENVELOPE_ADDRESS: // 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_SEND_ADDRESS: // 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; + + case RES_POOLCOLL_COMMENT: // Comment + { + SetAllScriptItem(aSet, SvxFontHeightItem(PT_10, 100, RES_CHRATR_FONTSIZE)); + } + 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: + { + auto const left(o3tl::convert(1, o3tl::Length::cm, o3tl::Length::twip)); + auto const right(o3tl::convert(1, o3tl::Length::cm, o3tl::Length::twip)); + SvxFirstLineIndentItem const firstLine(0, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + SvxRightMarginItem const rightMargin(right, RES_MARGIN_RIGHT); + aSet.Put(firstLine); + aSet.Put(leftMargin); + aSet.Put(rightMargin); + + 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: + { + // We indent by 1 cm. The IDs are always 2 away from each other! + auto const left(o3tl::convert(1, o3tl::Length::cm, o3tl::Length::twip)); + SvxTextLeftMarginItem const leftMargin(left, RES_MARGIN_TEXTLEFT); + aSet.Put(leftMargin); + } + break; + case RES_POOLCOLL_HTML_DT: + { + { + pNewColl->SetNextTextFormatColl( *GetTextCollFromPool( RES_POOLCOLL_HTML_DD )); + } + // We indent by 0 cm. The IDs are always 2 away from each other! + SvxTextLeftMarginItem const leftMargin(0, RES_MARGIN_TEXTLEFT); + aSet.Put(leftMargin); + } + 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; + TranslateId pRCId; + WhichRangesContainer const* pWhichRange; + + 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 ) ); + } + break; + case RES_POOLCHR_INET_VISIT: + { + aSet.Put( SvxColorItem( COL_RED, RES_CHRATR_COLOR ) ); + aSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE ) ); + } + 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: + { + tools::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_CITATION: + 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_deg10, 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, SvxBorderLineWidth::Hairline ); + 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, 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( 0, 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, + o3tl::toTwips(35, o3tl::Length::mm), + o3tl::toTwips(5, o3tl::Length::mm))); + } + 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, RES_LR_SPACE ) ); + + SvxProtectItem aProtect( RES_PROTECT ); + aProtect.SetSizeProtect( true ); + aProtect.SetPosProtect( true ); + aSet.Put( aProtect ); + + pNewFormat->SetAutoUpdateOnDirectFormat(); + } + 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(o3tl::convert(2, o3tl::Length::cm, o3tl::Length::twip)); + aLR.SetRight( aLR.GetLeft() ); + } + SvxULSpaceItem aUL( RES_UL_SPACE ); + { + aUL.SetUpper( o3tl::narrowing<sal_uInt16>(aLR.GetLeft()) ); + aUL.SetLower( o3tl::narrowing<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_ENVELOPE: // "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(o3tl::convert(1, o3tl::Length::cm, o3tl::Length::twip)); + aUL.SetUpper( o3tl::narrowing<sal_uInt16>(aLR.GetRight()) ); + aUL.SetLower( o3tl::narrowing<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 ); + + 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) + { + aFormat.SetListFormat("", ".", n); + 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 ); + + 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) + { + aFormat.SetListFormat("", ".", 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 ); + + tools::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) + { + aFormat.SetListFormat("", ".", 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 ) + { + tools::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.SetNumAdjust( SvxAdjust::Right ); + + static const sal_uInt16 aAbsSpace[ MAXLEVEL ] = + { +// cm: 1.33 cm intervals + 754, 1508, 2262, 3016, 3771, 4525, 5279, 6033, 6787, 7541 + }; + 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) + { + aFormat.SetListFormat("", ".", n); + 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.33cm + 174, 1508, // 0.33, 2.66cm + 397, 2262 // 0.70, 4.00cm + }; + + const sal_uInt16* pArr0to2 = aAbsSpace0to2; + SwNumFormat aFormat; + + aFormat.SetPositionAndSpaceMode( eNumberFormatPositionAndSpaceMode ); + aFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); + aFormat.SetStart( 1 ); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetNumAdjust( SvxAdjust::Right ); + aFormat.SetListFormat("", ".", 0); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + } + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset(- pArr0to2[0]); // num ends at 1.00 cm + aFormat.SetAbsLSpace(pArr0to2[1]); // text starts at 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.SetNumberingType(SVX_NUM_ROMAN_UPPER); + aFormat.SetIncludeUpperLevels( 1 ); + aFormat.SetListFormat("", ".", 1); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset(- pArr0to2[2]); // num ends at 2.33 cm + aFormat.SetAbsLSpace(pArr0to2[3]); // text starts at 2.66 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.SetIncludeUpperLevels( 1 ); + aFormat.SetListFormat("", u")"_ustr, 2); + aFormat.SetNumAdjust( SvxAdjust::Left ); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset(- pArr0to2[4]); // num starts at 3.30 cm + aFormat.SetAbsLSpace(pArr0to2[5]); // text starts at 4.00 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 = o3tl::convert(660, o3tl::Length::mm100, o3tl::Length::twip), + nOffs2 = o3tl::convert(4000, o3tl::Length::mm100, o3tl::Length::twip); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetFirstLineOffset( - nOffs ); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + aFormat.SetFirstLineIndent( - nOffs ); + } + + for (sal_uInt16 n = 3; n < MAXLEVEL; ++n) + { + aFormat.SetStart( n+1 ); + aFormat.SetListFormat("", "", n); + + if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFormat.SetAbsLSpace(nOffs2 + ((n - 2) * static_cast<tools::Long>(nOffs))); + } + else if ( eNumberFormatPositionAndSpaceMode == SvxNumberFormat::LABEL_ALIGNMENT ) + { + tools::Long nPos = nOffs2 + ((n - 2) * static_cast<tools::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 = o3tl::convert(4, o3tl::Length::mm, o3tl::Length::twip); + + 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 ) + { + tools::Long nPos = ((n & 1) +1) * static_cast<tools::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; + + bool isUsed = false; + sw::AutoFormatUsedHint aHint(isUsed, m_rDoc.GetNodes()); + pNewColl->CallSwClientNotify(aHint); + return isUsed; +} + +/// 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() ) + return false; + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + return pNewFormat->IsUsed(); +} + +/// 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) + bool isUsed = false; + sw::AutoFormatUsedHint aHint(isUsed, m_rDoc.GetNodes()); + pNewPgDsc->CallSwClientNotify(aHint); + return isUsed; +} + +DocumentStylePoolManager::~DocumentStylePoolManager() +{ +} + +} + +static std::vector<OUString> +lcl_NewUINameArray(const TranslateId* 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() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aTextUINameArray; + + auto it = s_aTextUINameArray.find(rCurrentLanguage); + if (it == s_aTextUINameArray.end()) + it = s_aTextUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_TEXT_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_TEXT_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetListsUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aListsUINameArray; + + auto it = s_aListsUINameArray.find(rCurrentLanguage); + if (it == s_aListsUINameArray.end()) + it = s_aListsUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_LISTS_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_LISTS_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetExtraUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aExtraUINameArray; + + auto it = s_aExtraUINameArray.find(rCurrentLanguage); + if (it == s_aExtraUINameArray.end()) + it = s_aExtraUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_EXTRA_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_EXTRA_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetRegisterUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aRegisterUINameArray; + + auto it = s_aRegisterUINameArray.find(rCurrentLanguage); + if (it == s_aRegisterUINameArray.end()) + it = s_aRegisterUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_REGISTER_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_REGISTER_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetDocUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aDocUINameArray; + + auto it = s_aDocUINameArray.find(rCurrentLanguage); + if (it == s_aDocUINameArray.end()) + it = s_aDocUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_DOC_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_DOC_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aHTMLUINameArray; + + auto it = s_aHTMLUINameArray.find(rCurrentLanguage); + if (it == s_aHTMLUINameArray.end()) + it = s_aHTMLUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCOLL_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCOLL_HTML_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetFrameFormatUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aFrameFormatUINameArray; + + auto it = s_aFrameFormatUINameArray.find(rCurrentLanguage); + if (it == s_aFrameFormatUINameArray.end()) + it = s_aFrameFormatUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLFRM_ARY, SAL_N_ELEMENTS(STR_POOLFRM_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetChrFormatUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aChrFormatUINameArray; + + auto it = s_aChrFormatUINameArray.find(rCurrentLanguage); + if (it == s_aChrFormatUINameArray.end()) + it = s_aChrFormatUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCHR_ARY, SAL_N_ELEMENTS(STR_POOLCHR_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetHTMLChrFormatUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aHTMLChrFormatUINameArray; + + auto it = s_aHTMLChrFormatUINameArray.find(rCurrentLanguage); + if (it == s_aHTMLChrFormatUINameArray.end()) + it = s_aHTMLChrFormatUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLCHR_HTML_ARY, SAL_N_ELEMENTS(STR_POOLCHR_HTML_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetPageDescUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aPageDescUINameArray; + + auto it = s_aPageDescUINameArray.find(rCurrentLanguage); + if (it == s_aPageDescUINameArray.end()) + it = s_aPageDescUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLPAGE_ARY, SAL_N_ELEMENTS(STR_POOLPAGE_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetNumRuleUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aNumRuleUINameArray; + + auto it = s_aNumRuleUINameArray.find(rCurrentLanguage); + if (it == s_aNumRuleUINameArray.end()) + it = s_aNumRuleUINameArray.emplace(rCurrentLanguage, + lcl_NewUINameArray(STR_POOLNUMRULE_NUM_ARY, SAL_N_ELEMENTS(STR_POOLNUMRULE_NUM_ARY))).first; + + return it->second; +} + +const std::vector<OUString>& SwStyleNameMapper::GetTableStyleUINameArray() +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, std::vector<OUString>> s_aTableStyleUINameArray; + + auto it = s_aTableStyleUINameArray.find(rCurrentLanguage); + if (it == s_aTableStyleUINameArray.end()) + it = s_aTableStyleUINameArray.emplace(rCurrentLanguage, + // 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))).first; + + return it->second; +} + +/* 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 0000000000..13f85a2026 --- /dev/null +++ b/sw/source/core/doc/DocumentTimerManager.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <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, "sw::DocumentTimerManager m_aDocIdle") + , m_aFireIdleJobsTimer("sw::DocumentTimerManager m_aFireIdleJobsTimer") + , m_bWaitForLokInit(true) +{ + m_aDocIdle.SetPriority(TaskPriority::LOWEST); + m_aDocIdle.SetInvokeHandler(LINK(this, DocumentTimerManager, DoIdleJobs)); + + 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 ); + + auto pChapterFieldType = m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Chapter ); + pChapterFieldType->CallSwClientNotify(sw::LegacyModifyHint( 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 (SwEditShell* pSh = m_rDoc.GetEditShell()) + pSh->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 0000000000..8c37642899 --- /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, const char * pDebugIdleName ) + : Idle(pDebugIdleName), 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 0000000000..17f732d6d3 --- /dev/null +++ b/sw/source/core/doc/SwStyleNameMapper.cxx @@ -0,0 +1,775 @@ +/* -*- 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> +#include <unotools/syslocale.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <map> + +#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) +{ + // Interesting, why the rest must be longer than 1 character? It is so + // since commit 4fbc9dd48b7cebb304010e7337b1bbc3936c7923 (2001-08-16) + return rString.getLength() > 8 && rString.endsWith(" (user)"); +} + +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; + } + + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + static std::map<LanguageTag, NameToIdHash> s_aUIMap; + + auto it = s_aUIMap.find(rCurrentLanguage); + if (it == s_aUIMap.end()) + it = s_aUIMap.emplace(rCurrentLanguage, initFunc(false)).first; + + return it->second; + } +}; + +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", + "Comment", // RES_POOLCOLL_COMMENT + }; + 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 = { + "No List", + "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 0000000000..38011750bd --- /dev/null +++ b/sw/source/core/doc/acmplwrd.cxx @@ -0,0 +1,420 @@ +/* -*- 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 <acmplwrd.hxx> +#include <doc.hxx> +#include <pagedesc.hxx> +#include <poolfmt.hxx> +#include <svl/listener.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 SvtListener +{ + 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 Notify(const SfxHint&) override; +private: + void DocumentDying(); +}; + +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) +{ + StartListening(m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +#if OSL_DEBUG_LEVEL > 0 + ++s_nSwAutoCompleteClientCount; +#endif +} + +SwAutoCompleteClient::SwAutoCompleteClient(const SwAutoCompleteClient& rClient) : + SvtListener(), + m_pAutoCompleteWord(rClient.m_pAutoCompleteWord), + m_pDoc(rClient.m_pDoc) +{ + StartListening(m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier()); +#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; + CopyAllBroadcasters(rClient); + return *this; +} + +void SwAutoCompleteClient::DocumentDying() +{ + EndListeningAll(); + m_pAutoCompleteWord->DocumentDying(*m_pDoc); +} + +void SwAutoCompleteClient::Notify(const SfxHint& rHint) +{ + switch(rHint.GetId()) + { + case SfxHintId::Dying: + DocumentDying(); + return; + case SfxHintId::SwLegacyModify: + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + switch(pLegacy->GetWhich()) + { + case RES_REMOVE_UNO_OBJECT: + case RES_OBJECTDYING: + DocumentDying(); + return; + default: + return; + } + } + default: + return; + } +} + +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.subView(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()) + { + auto 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); + 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_at(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(std::u16string_view 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_at(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 ) + return; + + // 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_at(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 0000000000..1de4b71535 --- /dev/null +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -0,0 +1,820 @@ +/* -*- 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 <map> +#include <node.hxx> +#include <ndtxt.hxx> +#include <ndhints.hxx> +#include <txatbase.hxx> +#include <pam.hxx> +#include <docary.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 <cstdio> + +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) +{ + return dbg_out(OUString::number(reinterpret_cast<sal_uIntPtr>(pVoid), 16)); +} + +const char * dbg_out(std::u16string_view aStr) +{ + aDbgOutResult = OUStringToOString(aStr, RTL_TEXTENCODING_ASCII_US); + + if (bDbgOutStdErr) + fprintf(stderr, "%s", aDbgOutResult.getStr()); + + return aDbgOutResult.getStr(); +} + +static std::map<sal_uInt16,OUString> & GetItemWhichMap() +{ + static std::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_CONTENTCONTROL , "RES_TXTATR_CONTENTCONTROL" }, + { 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_LINEBREAK , "RES_TXTATR_LINEBREAK" }, + { RES_TXTATR_DUMMY1 , "TXTATR_DUMMY1" }, + { 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_MARGIN_FIRSTLINE, "FIRSTLINE" }, + { RES_MARGIN_TEXTLEFT, "TEXTLEFT" }, + { RES_MARGIN_RIGHT, "RIGHT" }, + { RES_MARGIN_LEFT, "LEFT" }, + { RES_MARGIN_GUTTER, "GUTTER" }, + { RES_MARGIN_GUTTER_RIGHT, "GUTTER_RIGHT" }, + { 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("[ "); + + auto & rWhichMap = GetItemWhichMap(); + auto it = rWhichMap.find(rItem.Which()); + if ( it != rWhichMap.end()) + aStr += it->second; + 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(" " + lcl_dbg_out(*rHints.Get(i)) + "\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(sal_Int32(rPos.GetNodeIndex())) + + ", " + + OUString::number(rPos.GetContentIndex()) + + ": " + + OUString::number(reinterpret_cast<sal_IntPtr>(rPos.GetContentNode()), 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) +{ + OUString aResult = "[ " + + OUString::number(reinterpret_cast<sal_uIntPtr>(&rFrameFormat), 16) + + "(" + + 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& rDoc = rNode.GetDoc(); + const sw::SpzFrameFormats* pSpzs = rDoc.GetSpzFrameFormats(); + + if (pSpzs) + { + bool bFirst = true; + for(const sw::SpzFrameFormat* pSpz: *pSpzs) + { + const SwFormatAnchor& rAnchor = pSpz->GetAnchor(); + const SwNode * pPos = rAnchor.GetAnchorNode(); + + if (pPos && *pPos == rNode) + { + if (! bFirst) + aResult.append(", "); + + if (pSpz) + aResult.append(lcl_dbg_out(*pSpz)); + 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) +{ + OUString aTmpStr = "<node " + "index=\"" + + OUString::number(sal_Int32(rNode.GetIndex())) + + "\"" + " serial=\"" + + OUString::number(rNode.GetSerial()) + + "\"" + " type=\"" + + OUString::number(sal_Int32( rNode.GetNodeType() ) ) + + "\"" + " pointer=\"" + + OUString::number(reinterpret_cast<sal_uIntPtr>(&rNode), 16) + + "\">"; + + 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 SwNumRuleItem * pItem = nullptr; + + if (pAttrSet && + (pItem = pAttrSet->GetItemIfSet(RES_PARATR_NUMRULE, false))) + { + aTmpStr += "(" + 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 = rNode.GetStartNode(); + if (pStartNode != nullptr) + aTmpStr += OUString::number(sal_Int32(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]) + "\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("[ " + rRule.GetName() + " ["); + + for (sal_uInt8 n = 0; n < MAXLEVEL; n++) + { + if (n > 0) + aResult.append(", "); + + aResult.append(lcl_dbg_out(rRule.Get(n))); + } + + 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 sw::FrameFormats<sw::SpzFrameFormat*>& rFrameFormats) +{ + return lcl_dbg_out_SvPtrArr<sw::FrameFormats<sw::SpzFrameFormat*>>(rFrameFormats); +} + +const char * dbg_out(const sw::FrameFormats<sw::SpzFrameFormat*>& 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()); + + aResult.append("(" + OUString::number(reinterpret_cast<sal_uIntPtr>(rTable[n]), 16) + ")"); + } + + 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) + ": " + 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 0000000000..4bcb2a35e8 --- /dev/null +++ b/sw/source/core/doc/doc.cxx @@ -0,0 +1,1951 @@ +/* -*- 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 <officecfg/Office/Writer.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 <rolbck.hxx> +#include <UndoAttribute.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 <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <osl/interlck.h> +#include <vbahelper/vbaaccesshelper.hxx> +#include <editeng/langitem.hxx> +#include <calbck.hxx> +#include <crsrsh.hxx> + +/* @@@MAINTAINABILITY-HORROR@@@ + Probably unwanted dependency on SwDocShell +*/ +#include <docsh.hxx> + +#include <com/sun/star/text/XTextRange.hpp> +#include <editeng/unoprnms.hxx> +#include <unotextrange.hxx> +#include <unoprnms.hxx> +#include <unomap.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; +} + +::SwContentControlManager& SwDoc::GetContentControlManager() +{ + return *m_pContentControlManager; +} + +::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 SwNode& rNd, const SwTextField* pField ) + : SetGetExpField( rNd, pField, std::nullopt ) {} + + sal_uInt16 GetPageNo( const StringRangeEnumerator &rRangeEnum, + const o3tl::sorted_vector< sal_Int32 > &rPossiblePages, + sal_uInt16& rVirtPgNo, sal_Int32& rLineNo ); + + const SwPostItField* GetPostIt() const + { + return static_cast<const SwPostItField*>( GetTextField()->GetFormatField().GetField() ); + } +}; + +} + +sal_uInt16 PostItField_::GetPageNo( + const StringRangeEnumerator &rRangeEnum, + const o3tl::sorted_vector< sal_Int32 > &rPossiblePages, + /* out */ sal_uInt16& rVirtPgNo, /* out */ sal_Int32& 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 = o3tl::narrowing<sal_Int32>(pFrame->GetLineCount( nPos ) + + pFrame->GetAllLines() - pFrame->GetThisLines()); + rVirtPgNo = pFrame->GetVirtPageNum(); + return nPgNo; + } + } + return 0; +} + +bool sw_GetPostIts(const IDocumentFieldsAccess& rIDFA, SetGetExpFields* pSrtLst) +{ + SwFieldType* pFieldType = rIDFA.GetSysFieldType(SwFieldIds::Postit); + assert(pFieldType); + + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + if(pSrtLst) + for(auto pField: vFields) + { + auto pTextField = pField->GetTextField(); + std::unique_ptr<PostItField_> pNew(new PostItField_(pTextField->GetTextNode(), 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_Int32 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 + + sTmp + + OUString::number( nPageNo ) + + " "; + if( nLineNo ) + { + aStr += SwViewShell::GetShellRes()->aPostItLine + + sTmp + + OUString::number( nLineNo ) + + " "; + } + SvtSysLocale aSysLocale; + aStr += SwViewShell::GetShellRes()->aPostItAuthor + + sTmp + pField->GetPar1() + " " + + /*(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 = o3tl::toInt32(aNumber); + aNumber.setLength(0); + 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(); + o3tl::sorted_vector< 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; + + CurrShell aCurr( 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, nLastPageNum = 0, nPhyPageNum = 0; + sal_Int32 nLineNo = 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(); + o3tl::sorted_vector< 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() ) + { +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif + aVec.insert( aVec.begin() + 1, nullptr ); // insert a second empty page +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L +#pragma GCC diagnostic pop +#endif + } + 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( std::u16string_view 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; +} + +void SwDoc::DeleteFormatRefMark(const SwFormatRefMark* pFormatRefMark) +{ + const SwTextRefMark* pTextRefMark = pFormatRefMark->GetTextRefMark(); + SwTextNode& rTextNd = const_cast<SwTextNode&>(pTextRefMark->GetTextNode()); + std::unique_ptr<SwRegHistory> aRegHistory; + if (GetIDocumentUndoRedo().DoesUndo()) + { + SwUndoResetAttr* pUndo = new SwUndoResetAttr(SwPosition(rTextNd, pTextRefMark->GetStart()), + RES_TXTATR_REFMARK); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndo)); + aRegHistory.reset(new SwRegHistory(rTextNd, &pUndo->GetHistory())); + rTextNd.GetpSwpHints()->Register(aRegHistory.get()); + } + rTextNd.DeleteAttribute(const_cast<SwTextRefMark*>(pTextRefMark)); + if (GetIDocumentUndoRedo().DoesUndo()) + { + if (rTextNd.GetpSwpHints()) + rTextNd.GetpSwpHints()->DeRegister(); + } + getIDocumentState().SetModified(); +} + +static bool lcl_SpellAndGrammarAgain( SwNode* pNd, void* pArgs ) +{ + SwTextNode *pTextNode = pNd->GetTextNode(); + bool bOnlyWrong = *static_cast<sal_Bool*>(pArgs); + if( pTextNode ) + { + if( bOnlyWrong ) + { + if( pTextNode->GetWrong() && + pTextNode->GetWrong()->InvalidateWrong() ) + pTextNode->SetWrongDirty(sw::WrongState::TODO); + if( pTextNode->GetGrammarCheck() && + pTextNode->GetGrammarCheck()->InvalidateWrong() ) + pTextNode->SetGrammarCheckDirty( true ); + } + else + { + pTextNode->SetWrongDirty(sw::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( SwNode* pNd, void* ) +{ + SwTextNode *pTextNode = pNd->GetTextNode(); + if( pTextNode ) + { + pTextNode->SetSmartTagDirty( true ); + pTextNode->ClearSmartTags(); + } + 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 ) + return; + + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = GetAllLayouts(); + for( auto aLayout : aAllLayouts ) + aLayout->AllInvalidateAutoCompleteWords(); + for( SwNodeOffset 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( std::u16string_view 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& rExtDoc, sal_uInt8 nLevel, sal_uInt8 nPara, bool bImpress) +{ + const SwOutlineNodes& rOutNds = GetNodes().GetOutLineNds(); + if (rOutNds.empty()) + return; + + ::StartProgress( STR_STATSTR_SUMMARY, 0, rOutNds.size(), GetDocShell() ); + SwNodeIndex aEndOfDoc( rExtDoc.GetNodes().GetEndOfContent(), -1 ); + for( SwOutlineNodes::size_type i = 0; i < rOutNds.size(); ++i ) + { + ::SetProgressState( static_cast<tools::Long>(i), GetDocShell() ); + const SwNodeOffset nIndex = rOutNds[ i ]->GetIndex(); + + const int nLvl = GetNodes()[ nIndex ]->GetTextNode()->GetAttrOutlineLevel()-1; + if( nLvl > nLevel ) + continue; + SwNodeOffset nEndOfs(1); + sal_uInt8 nWish = nPara; + SwNodeOffset 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 ], SwNodeOffset(0), *rOutNds[ i ], nEndOfs ); + GetNodes().Copy_( aRange, aEndOfDoc.GetNode() ); + } + const SwTextFormatColls *pColl = rExtDoc.GetTextFormatColls(); + for( SwTextFormatColls::size_type i = 0; i < pColl->size(); ++i ) + (*pColl)[ i ]->ResetFormatAttr( RES_PAGEDESC, RES_BREAK ); + SwNodeIndex aIndx( rExtDoc.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 = o3tl::narrowing<sal_uInt16>( + !pMyColl->IsAssignedToListLevelOfOutlineStyle() + ? RES_POOLCOLL_HEADLINE2 + : RES_POOLCOLL_HEADLINE1 ); + pMyColl = rExtDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( nHeadLine ); + pNd->ChgFormatColl( pMyColl ); + } + if( !pNd->Len() && + pNd->StartOfSectionIndex()+SwNodeOffset(2) < pNd->EndOfSectionIndex() ) + { + bDelete = true; + rExtDoc.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 ((SwNodeOffset(2) == pTextNd->EndOfSectionIndex() - pTextNd->StartOfSectionIndex()) + || (SwNodeOffset(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( SwNodeOffset n = GetNodes().Count(); n; ) + { + SwTextNode* pTextNd = GetNodes()[ --n ]->GetTextNode(); + if ( pTextNd ) + { + bool bRemoved = false; + if ( pTextNd->HasHiddenCharAttribute( true ) ) + { + bRemoved = true; + bRet = true; + + if (SwNodeOffset(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() ); + aPam.SetMark(); + aPam.GetPoint()->Assign( *pSectNd->EndOfSectionNode() ); + pCNd = SwNodes::GoPrevious( aPam.GetPoint() ); + assert(pCNd); // keep coverity happy + aPam.GetPoint()->SetContent( pCNd->Len() ); + + getIDocumentContentOperations().DeleteRange( aPam ); + } + else + { + // delete the whole section + aPam.SetMark(); + aPam.GetPoint()->Assign( *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( SwNodeOffset n = GetNodes().Count()-SwNodeOffset(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; +} + +static bool IsMailMergeField(SwFieldIds fieldId) +{ + switch (fieldId) + { + case SwFieldIds::Database: // Mail merge fields + case SwFieldIds::DatabaseName: // Database name + case SwFieldIds::HiddenText: // Hidden text may use database fields in condition + case SwFieldIds::HiddenPara: // Hidden paragraph may use database fields in condition + case SwFieldIds::DbNextSet: // Moving to next mail merge record + case SwFieldIds::DbNumSet: // Moving to a specific mail merge record + case SwFieldIds::DbSetNumber: // Number of current mail merge record + return true; + default: + return false; + } +} + +bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) +{ + bool bRet = false; + getIDocumentFieldsAccess().LockExpFields(); + GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE, nullptr ); + + const bool bOnlyConvertDBFields + = officecfg::Office::Writer::FormLetter::ConvertToTextOnlyMMFields::get(); + + 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; + + if (bOnlyConvertDBFields && !IsMailMergeField(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) + continue; + + bool bInHeaderFooter = IsInHeaderFooter(*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.SetContent( 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()->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.GetPointNode() == &rPam.GetMarkNode()) + { + SwTextNode * pTextNode = rPam.GetPointNode().GetTextNode(); + + if (nullptr != pTextNode) + { + const sal_Int32 nStart = rPam.Start()->GetContentIndex(); + const sal_Int32 nEnd = rPam.End()->GetContentIndex(); + + 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( SwNodeOffset 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() +{ + return mxVbaEvents; +} + +void SwDoc::SetVbaEventProcessor() +{ +#if HAVE_FEATURE_SCRIPTING + if (mpDocShell && ooo::vba::isAlienWordDoc(*mpDocShell)) + { + try + { + uno::Reference< frame::XModel > xModel( mpDocShell->GetModel(), uno::UNO_SET_THROW ); + uno::Sequence< uno::Any > aArgs{ uno::Any(xModel) }; + mxVbaEvents.set( ooo::vba::createVBAUnoAPIServiceWithArgs( mpDocShell, "com.sun.star.script.vba.VBATextEventProcessor" , aArgs ), uno::UNO_QUERY_THROW ); + } + catch( uno::Exception& ) + { + } + } +#endif +} + +void SwDoc::SetMissingDictionaries( bool bIsMissing ) +{ + if (!bIsMissing) + meDictionaryMissing = MissingDictionary::False; + else if (meDictionaryMissing == MissingDictionary::Undefined) + meDictionaryMissing = MissingDictionary::True; +}; + +void SwDoc::SetLanguage(const LanguageType eLang, const sal_uInt16 nId) +{ + mpAttrPool->SetPoolDefaultItem(SvxLanguageItem(eLang, nId)); +} + +bool SwDoc::HasParagraphDirectFormatting(const SwPosition& rPos) +{ + rtl::Reference<SwXTextRange> xRange(SwXTextRange::CreateXTextRange(rPos.GetDoc(), rPos, + &rPos)); + uno::Reference<container::XEnumeration> xParaEnum = xRange->createEnumeration(); + uno::Reference<text::XTextRange> xThisParagraphRange(xParaEnum->nextElement(), uno::UNO_QUERY); + if (xThisParagraphRange.is()) + { + const std::vector<OUString> aHiddenProperties{ UNO_NAME_RSID, + UNO_NAME_PARA_IS_NUMBERING_RESTART, + UNO_NAME_PARA_STYLE_NAME, + UNO_NAME_PARA_CONDITIONAL_STYLE_NAME, + UNO_NAME_PAGE_STYLE_NAME, + UNO_NAME_NUMBERING_START_VALUE, + UNO_NAME_NUMBERING_IS_NUMBER, + UNO_NAME_PARA_CONTINUEING_PREVIOUS_SUB_TREE, + UNO_NAME_CHAR_STYLE_NAME, + UNO_NAME_NUMBERING_LEVEL, + UNO_NAME_SORTED_TEXT_ID, + UNO_NAME_PARRSID, + UNO_NAME_CHAR_COLOR_THEME, + UNO_NAME_CHAR_COLOR_TINT_OR_SHADE }; + + SfxItemPropertySet const& rPropSet(*aSwMapProvider.GetPropertySet( + PROPERTY_MAP_PARA_AUTO_STYLE)); + SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap()); + + uno::Reference<beans::XPropertySet> xPropertySet(xThisParagraphRange, + uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertyState> xPropertyState(xThisParagraphRange, + uno::UNO_QUERY_THROW); + const uno::Sequence<beans::Property> aProperties + = xPropertySet->getPropertySetInfo()->getProperties(); + for (const beans::Property& rProperty : aProperties) + { + const OUString& rPropName = rProperty.Name; + if (!rMap.hasPropertyByName(rPropName)) + continue; + if (std::find(aHiddenProperties.begin(), aHiddenProperties.end(), rPropName) + != aHiddenProperties.end()) + continue; + if (xPropertyState->getPropertyState(rPropName) == beans::PropertyState_DIRECT_VALUE) + return true; + } + } + return false; +} + +/* 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 0000000000..697559b0b4 --- /dev/null +++ b/sw/source/core/doc/docbasic.cxx @@ -0,0 +1,233 @@ +/* -*- 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 <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.Count(); + if( nCount > 1 ) + { + nCount--; + pRet = new Sequence<Any>( nCount ); + Any *pUnoArgs = pRet->getArray(); + for( sal_uInt32 i=0; i<nCount; i++ ) + { + SbxVariable* pVar = rArgs.Get(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 && SfxPoolItem::areSame(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 auto pSpz = static_cast<const sw::SpzFrameFormat*>(rCallEvent.PTR.pFormat); + if( bCheckPtr ) + { + if (GetSpzFrameFormats()->IsAlive(pSpz)) + bCheckPtr = false; // misuse as a flag + else + // this shouldn't be possible now that SwCallMouseEvent + // listens for dying format? + assert(false); + } + if( !bCheckPtr ) + pTable = &pSpz->GetMacro().GetMacroTable(); + } + break; + + case EVENT_OBJECT_IMAGEMAP: + { + const IMapObject* pIMapObj = rCallEvent.PTR.IMAP.pIMapObj; + if( bCheckPtr ) + { + const auto pSpz = static_cast<const sw::SpzFrameFormat*>(rCallEvent.PTR.IMAP.pFormat); + if (GetSpzFrameFormats()->IsAlive(pSpz)) + { + const ImageMap* pIMap = pSpz->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() ) + { + Sequence<Any> aUnoArgs; + + 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(), aUnoArgs, 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 0000000000..00681135b4 --- /dev/null +++ b/sw/source/core/doc/docbm.cxx @@ -0,0 +1,2124 @@ +/* -*- 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 <bookmark.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 <txtfrm.hxx> +#include <view.hxx> + +#include <libxml/xmlstring.h> +#include <libxml/xmlwriter.h> +#include <comphelper/lok.hxx> +#include <strings.hrc> + +constexpr OUString S_ANNOTATION_BOOKMARK = u"____"_ustr; + +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(rIter) +{ +} + +IDocumentMarkAccess::iterator::iterator(iterator const& rOther) + : m_pIter(rOther.m_pIter) +{ +} + +auto IDocumentMarkAccess::iterator::operator=(iterator const& rOther) -> iterator& +{ + m_pIter = 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; +} + +// 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(std::in_place) +{ +} + +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 SwNode& rNdIdx, std::optional<sal_Int32> oContentIdx ) + { + return oContentIdx.has_value() + ? ( rPos.GetNode() > rNdIdx + || ( rPos.GetNode() == rNdIdx + && rPos.GetContentIndex() >= *oContentIdx ) ) + : rPos.GetNode() >= rNdIdx; + } + + bool lcl_Lower( const SwPosition& rPos, const SwNode& rNdIdx, std::optional<sal_Int32> oContentIdx ) + { + if (rPos.GetNode() < rNdIdx) + return true; + + if (rPos.GetNode() != rNdIdx || !oContentIdx) + return false; + + if (rPos.GetContentIndex() < *oContentIdx) + return true; + + // paragraph end selected? + return rNdIdx.IsTextNode() && *oContentIdx == rNdIdx.GetTextNode()->Len(); + } + + bool lcl_MarkOrderingByStart(const ::sw::mark::MarkBase *const pFirst, + const ::sw::mark::MarkBase *const pSecond) + { + SwPosition const& rFirstStart(pFirst->GetMarkStart()); + SwPosition const& rSecondStart(pSecond->GetMarkStart()); + if (rFirstStart.GetNode() != rSecondStart.GetNode()) + { + return rFirstStart.GetNode() < rSecondStart.GetNode(); + } + const sal_Int32 nFirstContent = rFirstStart.GetContentIndex(); + const sal_Int32 nSecondContent = rSecondStart.GetContentIndex(); + if (nFirstContent != 0 || nSecondContent != 0) + { + return nFirstContent < nSecondContent; + } + SwContentNode const*const pFirstNode(rFirstStart.nContent.GetContentNode()); + SwContentNode const*const pSecondNode(rSecondStart.nContent.GetContentNode()); + if ((pFirstNode != nullptr) != (pSecondNode != nullptr)) + { // consistency with SwPosition::operator< + return pSecondNode != nullptr; + } + 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); + } + + void lcl_PositionFromContentNode( + std::optional<SwPosition>& rFoundPos, + const SwContentNode * const pContentNode, + const bool bAtEnd) + { + rFoundPos.emplace(*pContentNode, bAtEnd ? pContentNode->Len() : 0); + } + + // 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 + void lcl_FindExpelPosition( + std::optional<SwPosition>& rFoundPos, + const SwNode& rStt, + const SwNode& rEnd, + const SwPosition& rOtherPosition) + { + const SwContentNode * pNode = rEnd.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 ) + { + lcl_PositionFromContentNode( rFoundPos, pNode, bPosAtEndOfNode ); + return; + } + + rFoundPos = 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, + bool bLoop) + { + auto const pMarkAfter = upper_bound( + rMarks.begin(), + rMarks.end(), + rPos, + CompareIMarkStartsAfter()); + if(pMarkAfter == rMarks.end()) + { + if (bLoop && rMarks.begin() != rMarks.end()) + return *rMarks.begin(); + + return nullptr; + } + return *pMarkAfter; + }; + + IMark* lcl_getMarkBefore(const MarkManager::container_t& rMarks, const SwPosition& rPos, + bool bLoop) + { + // 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()) + { + if (bLoop && rMarks.begin() != rMarks.end()) + return *(rMarks.end() - 1); + + 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().GetNode().FindTableBoxStartNode() != + io_pMark->GetMarkPos().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", + sal_Int32(pStPos->GetNodeIndex()) << "," << + pStPos->GetContentIndex() << " " << + sal_Int32(pEndPos->GetNodeIndex()) << "," << + pEndPos->GetContentIndex() << " " << + 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()->GetNode().IsTextNode() && + rPaM.Start()->GetContentIndex() == 0 && + ( !rPaM.HasMark() || + ( rPaM.GetMark()->GetNode() == rPaM.GetPoint()->GetNode() && + rPaM.End()->GetContentIndex() == rPaM.End()->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()->AdjustContent(+1); // skip CH_TXT_ATR_FIELDSTART + pam.GetDoc().getIDocumentContentOperations().DeleteAndJoin(pam); +} + +namespace sw::mark +{ + MarkManager::MarkManager(SwDoc& rDoc) + : m_rDoc(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 << " " << + sal_Int32(pPos1->GetNodeIndex() )<< "," << + pPos1->GetContentIndex() << " " << + sal_Int32(pPos2->GetNodeIndex()) << "," << + pPos2->GetContentIndex()); + } +#endif + if ( (!rPaM.GetPoint()->GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + // SwXTextRange can be on table node or plain start node (FLY_AT_FLY) + || !rPaM.GetPoint()->GetNode().IsStartNode())) + || (!rPaM.GetMark()->GetNode().IsTextNode() + && (eType != MarkType::UNO_BOOKMARK + || !rPaM.GetMark()->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()->GetNode() != rPaM.GetMark()->GetNode() + || rPaM.Start()->GetContentIndex() + 1 != rPaM.End()->GetContentIndex()))) + { + SAL_WARN("sw.core", "MarkManager::makeMark(..)" + " - invalid range on point fieldmark"); + return nullptr; + } + + if ((eType == MarkType::TEXT_FIELDMARK || eType == MarkType::DATE_FIELDMARK) + && (rPaM.GetPoint()->GetNode().StartOfSectionNode() != rPaM.GetMark()->GetNode().StartOfSectionNode() + || (pSepPos && rPaM.GetPoint()->GetNode().StartOfSectionNode() != pSepPos->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, rName); + break; + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + pMark = std::make_unique<DropDownFieldmark>(rPaM, rName); + 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_rDoc, 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; + } + if (eMode == InsertMode::New + && (eType == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || eType == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + // due to sw::InsertText notifications everything is visible now - tell + // layout to hide as appropriate + // note: we don't know how many layouts there are and which + // parts they hide, so just notify the entire fieldmark, it + // should give the right result if not in the most efficient way + // note2: can't be done in InitDoc() because it requires the mark + // to be inserted in the vectors. + SwPaM const tmp(pMark->GetMarkPos(), pMark->GetOtherMarkPos()); + sw::UpdateFramesForAddDeleteRedline(m_rDoc, tmp); + } + + 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_rDoc.GetIDocumentUndoRedo().DoesUndo(); + m_rDoc.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_rDoc.GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_rDoc.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_rDoc.GetIDocumentUndoRedo().DoesUndo(); + m_rDoc.GetIDocumentUndoRedo().DoUndo(false); + + bool bEnableSetModified = m_rDoc.getIDocumentState().IsEnableSetModified(); + m_rDoc.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_rDoc.GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); + if (pFieldMark) + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoInsNoTextFieldmark>(*pFieldMark)); + } + + m_rDoc.getIDocumentState().SetEnableSetModified(bEnableSetModified); + m_rDoc.getIDocumentState().SetModified(); + + return pFieldMark; + } + + ::sw::mark::IMark* MarkManager::getMarkForTextNode( + const SwTextNode& rTextNode, + const IDocumentMarkAccess::MarkType eType ) + { + SwPosition aPos(rTextNode); + 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_rDoc && + "<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_rDoc && + "<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_rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + m_rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoRenameBookmark>(sOldName, rNewName, m_rDoc)); + } + m_rDoc.getIDocumentState().SetModified(); + } + } + return true; + } + + void MarkManager::correctMarksAbsolute( + const SwNode& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode; + SwPosition aNewPos(rNewPos); + aNewPos.AdjustContent(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().GetNode() == pOldNode) + { + pMark->SetMarkPos(aNewPos); + bChangedPos = true; + isSortingNeeded = true; + } + bool bChangedOPos = false; + if (pMark->IsExpanded() && + &pMark->GetOtherMarkPos().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 SwNode& rOldNode, const SwPosition& rNewPos, const sal_Int32 nOffset) + { + const SwNode* const pOldNode = &rOldNode; + SwPosition aNewPos(rNewPos); + aNewPos.AdjustContent(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().GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + if (dynamic_cast< ::sw::mark::CrossRefBookmark *>(pMark)) + { + // ensure that cross ref bookmark always starts at 0 + aNewPosRel.SetContent(0); // HACK for WW8 import + isSortingNeeded = true; // and sort them to be safe... + } + aNewPosRel.AdjustContent(pMark->GetMarkPos().GetContentIndex()); + pMark->SetMarkPos(aNewPosRel); + bChangedPos = true; + } + if(pMark->IsExpanded() && + &pMark->GetOtherMarkPos().GetNode() == pOldNode) + { + SwPosition aNewPosRel(aNewPos); + aNewPosRel.AdjustContent(pMark->GetOtherMarkPos().GetContentIndex()); + 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, + bool const isReplace, + SwNode const& rStt, + SwNode const& rEnd, + std::optional<sal_Int32> oStartContentIdx, + std::optional<sal_Int32> oEndContentIdx, + 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, oStartContentIdx) + && lcl_Lower(pMark->GetMarkPos(), rEnd, oEndContentIdx); + rbIsOtherPosInRange = pMark->IsExpanded() + && lcl_GreaterThan(pMark->GetOtherMarkPos(), rStt, oStartContentIdx) + && lcl_Lower(pMark->GetOtherMarkPos(), rEnd, oEndContentIdx); + // special case: completely in range, touching the end? + if ( oEndContentIdx.has_value() + && !(isReplace && IDocumentMarkAccess::GetType(*pMark) + == IDocumentMarkAccess::MarkType::BOOKMARK) + && ( ( rbIsOtherPosInRange + && pMark->GetMarkPos().GetNode() == rEnd + && pMark->GetMarkPos().GetContentIndex() == *oEndContentIdx ) + || ( rbIsPosInRange + && pMark->IsExpanded() + && pMark->GetOtherMarkPos().GetNode() == rEnd + && pMark->GetOtherMarkPos().GetContentIndex() == *oEndContentIdx ) ) ) + { + 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() + || !oStartContentIdx.has_value() + || pMark->GetMarkPos().GetNode() != rStt + || pMark->GetMarkPos().GetContentIndex() != *oStartContentIdx; + break; + default: + bDeleteMark = true; + break; + } + } + return bDeleteMark; + } + return false; + } + + bool MarkManager::isBookmarkDeleted(SwPaM const& rPaM, bool const isReplace) 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, isReplace, + rStart.GetNode(), rEnd.GetNode(), rStart.GetContentIndex(), rEnd.GetContentIndex(), + bIsPosInRange, bIsOtherPosInRange); + if (bDeleteMark + && IDocumentMarkAccess::GetType(**ppMark) == MarkType::BOOKMARK) + { + return true; + } + } + return false; + } + + void MarkManager::deleteMarks( + const SwNode& rStt, + const SwNode& rEnd, + std::vector<SaveBookmark>* pSaveBkmk, + std::optional<sal_Int32> oStartContentIdx, + std::optional<sal_Int32> oEndContentIdx, + bool const isReplace) + { + 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, isReplace, rStt, rEnd, + oStartContentIdx, oEndContentIdx, bIsPosInRange, bIsOtherPosInRange); + + if ( bIsPosInRange + && ( bIsOtherPosInRange + || !pMark->IsExpanded() ) ) + { + if ( bDeleteMark ) + { + if ( pSaveBkmk ) + { + pSaveBkmk->push_back( SaveBookmark( *pMark, rStt, oStartContentIdx ) ); + } + 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::optional< SwPosition > oNewPos; + if ( oEndContentIdx ) + { + oNewPos.emplace( *rEnd.GetContentNode(), *oEndContentIdx ); + } + else + { + lcl_FindExpelPosition( oNewPos, 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().GetNode() != oNewPos->GetNode(); + 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(*oNewPos); + else + pMark->SetOtherMarkPos(*oNewPos); + 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, pSaveBkmk != nullptr)); + } + } // 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_rDoc; + bool const m_isMoveNodes; + LazyFieldmarkDeleter(Fieldmark *const pMark, SwDoc& rDoc, bool const isMoveNodes) + : m_pFieldmark(pMark), m_rDoc(rDoc), m_isMoveNodes(isMoveNodes) + { + 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*! + if (!m_isMoveNodes) + { + m_pFieldmark->ReleaseDoc(m_rDoc); + } + } + }; + + // Call DeregisterFromDoc() lazily, because it can call selection change listeners, which + // may mutate the marks container + struct LazyDdeBookmarkDeleter : public IDocumentMarkAccess::ILazyDeleter + { + std::unique_ptr<DdeBookmark> m_pDdeBookmark; + SwDoc& m_rDoc; + LazyDdeBookmarkDeleter(DdeBookmark *const pDdeBookmark, SwDoc& rDoc) + : m_pDdeBookmark(pDdeBookmark), m_rDoc(rDoc) + { + assert(pDdeBookmark); + } + virtual ~LazyDdeBookmarkDeleter() override + { + m_pDdeBookmark->DeregisterFromDoc(m_rDoc); + } + }; + + } + + std::unique_ptr<IDocumentMarkAccess::ILazyDeleter> + MarkManager::deleteMark(const const_iterator_t& ppMark, bool const isMoveNodes) + { + std::unique_ptr<ILazyDeleter> ret; + if (ppMark.get() == m_vAllMarks.end()) + return ret; + IMark* pMark = *ppMark; + + switch(IDocumentMarkAccess::GetType(*pMark)) + { + case IDocumentMarkAccess::MarkType::BOOKMARK: + { + auto const ppBookmark = lcl_FindMark(m_vBookmarks, *ppMark.get()); + if ( ppBookmark != m_vBookmarks.end() ) + { + Bookmark* pBookmark = dynamic_cast<Bookmark*>(*ppBookmark); + + if(pBookmark) + pBookmark->sendLOKDeleteCallback(); + + m_vBookmarks.erase(ppBookmark); + } + else + { + assert(false && + "<MarkManager::deleteMark(..)> - Bookmark not found in Bookmark container."); + } + } + break; + 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_rDoc, isMoveNodes)); + } + 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; + } + //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())); + DdeBookmark* const pDdeBookmark = dynamic_cast<DdeBookmark*>(pMark); + if (pDdeBookmark) + { + ret.reset(new LazyDdeBookmarkDeleter(pDdeBookmark, m_rDoc)); + } + + 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_rDoc && + "<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), false); + 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(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getFieldmarksBegin() const + { return m_vFieldmarks.begin(); } + + IDocumentMarkAccess::const_iterator_t MarkManager::getFieldmarksEnd() const + { return m_vFieldmarks.end(); } + + sal_Int32 MarkManager::getFieldmarksCount() const { return m_vFieldmarks.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().GetContentIndex() == rPos.GetContentIndex() + 1 + && pMark->GetMarkEnd().GetNode() == rPos.GetNode()); + } ); + return (pFieldmark == m_vFieldmarks.end()) + ? nullptr + : dynamic_cast<IFieldmark*>(*pFieldmark); + } + + IFieldmark* MarkManager::getInnerFieldmarkFor(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); + + // See if any fieldmarks after the first hit are closer to rPos. + ++itFieldmark; + for ( ; itFieldmark != m_vFieldmarks.end() + && (**itFieldmark).GetMarkStart() <= rPos; ++itFieldmark) + { // find the innermost fieldmark + if (rPos < (**itFieldmark).GetMarkEnd() + && (pFieldmark->GetMarkStart() < (**itFieldmark).GetMarkStart() + || (**itFieldmark).GetMarkEnd() < pFieldmark->GetMarkEnd())) + { + pFieldmark = *itFieldmark; + } + } + return dynamic_cast<IFieldmark*>(pFieldmark); + } + + IMark* MarkManager::getOneInnermostBookmarkFor(const SwPosition& rPos) const + { + auto it = std::find_if(m_vBookmarks.begin(), m_vBookmarks.end(), + [&rPos](const sw::mark::MarkBase* pMark) + { return pMark->IsCoveringPosition(rPos); }); + if (it == m_vBookmarks.end()) + { + return nullptr; + } + sw::mark::IMark* pBookmark = *it; + + // See if any bookmarks after the first hit are closer to rPos. + ++it; + + for (; it != m_vBookmarks.end() && (*it)->GetMarkStart() <= rPos; ++it) + { + // Find the innermost bookmark. + if (rPos < (*it)->GetMarkEnd() + && (pBookmark->GetMarkStart() < (*it)->GetMarkStart() + || (*it)->GetMarkEnd() < pBookmark->GetMarkEnd())) + { + pBookmark = *it; + } + } + return pBookmark; + } + + void MarkManager::deleteFieldmarkAt(const SwPosition& rPos) + { + auto const pFieldmark = dynamic_cast<Fieldmark*>(getFieldmarkAt(rPos)); + assert(pFieldmark); // currently all callers require it to be there + + deleteMark(lcl_FindMark(m_vAllMarks, pFieldmark), false); + } + + ::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 const aPaM(pFieldmark->GetMarkStart()); + + // Remove the old fieldmark and create a new one with the new type + if (rNewType == ODF_FORMDROPDOWN || rNewType == ODF_FORMCHECKBOX) + { + SwPosition aNewPos (*aPaM.GetPoint()); + deleteFieldmarkAt(aNewPos); + return makeNoTextFieldBookmark(aPaM, sName, rNewType); + } + else if(rNewType == ODF_FORMDATE) + { + SwPosition aPos (*aPaM.GetPoint()); + 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 = getInnerFieldmarkFor(aPos); + FieldmarkWithDropDownButton* pNewActiveFieldmark = nullptr; + if ((!pFieldBM || (pFieldBM->GetFieldname() != ODF_FORMDROPDOWN && pFieldBM->GetFieldname() != ODF_FORMDATE)) + && aPos.GetContentIndex() > 0 ) + { + aPos.AdjustContent(-1); + pFieldBM = getInnerFieldmarkFor(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); + } + + LOKUpdateActiveField(pSwView); + } + + void MarkManager::ClearFieldActivation() + { + if(m_pLastActiveFieldmark) + m_pLastActiveFieldmark->RemoveButton(); + + m_pLastActiveFieldmark = nullptr; + } + + void MarkManager::LOKUpdateActiveField(const SfxViewShell* pViewShell) + { + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if (m_pLastActiveFieldmark) + { + if (auto pDrowDown = m_pLastActiveFieldmark->GetFieldname() == ODF_FORMDROPDOWN ? + dynamic_cast<::sw::mark::DropDownFieldmark*>(m_pLastActiveFieldmark) : + nullptr) + { + pDrowDown->SendLOKShowMessage(pViewShell); + } + } + else + { + // Check whether we have any drop down fieldmark at all. + bool bDropDownFieldExist = false; + for (auto aIter = m_vFieldmarks.begin(); aIter != m_vFieldmarks.end(); ++aIter) + { + IFieldmark *pMark = dynamic_cast<IFieldmark*>(*aIter); + if (pMark && pMark->GetFieldname() == ODF_FORMDROPDOWN) + { + bDropDownFieldExist = true; + break; + } + } + + if (bDropDownFieldExist) + ::sw::mark::DropDownFieldmark::SendLOKHideMessage(pViewShell); + } + } + + 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::getNoTextFieldmarksIn(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 + && pMark->GetFieldname() != ODF_FORMCHECKBOX)) + { + continue; + } + + aRet.push_back(pMark); + } + + return aRet; + } + + IFieldmark* MarkManager::getFieldmarkAfter(const SwPosition& rPos, bool bLoop) const + { return dynamic_cast<IFieldmark*>(lcl_getMarkAfter(m_vFieldmarks, rPos, bLoop)); } + + IFieldmark* MarkManager::getFieldmarkBefore(const SwPosition& rPos, bool bLoop) const + { return dynamic_cast<IFieldmark*>(lcl_getMarkBefore(m_vFieldmarks, rPos, bLoop)); } + + 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()); + } + + // create helper bookmark for annotations on tracked deletions + ::sw::mark::IMark* MarkManager::makeAnnotationBookmark(const SwPaM& rPaM, + const OUString& rName, + const IDocumentMarkAccess::MarkType eType, + sw::mark::InsertMode const eMode, + SwPosition const*const pSepPos) + { + OUString sAnnotationBookmarkName(rName + S_ANNOTATION_BOOKMARK); + return makeMark( rPaM, sAnnotationBookmarkName, eType, eMode, pSepPos); + } + + // find helper bookmark of annotations on tracked deletions + IDocumentMarkAccess::const_iterator_t MarkManager::findAnnotationBookmark(const OUString& rName) const + { + OUString sAnnotationBookmarkName(rName + S_ANNOTATION_BOOKMARK); + return findBookmark(sAnnotationBookmarkName); + } + + // restore text ranges of annotations on tracked deletions + // based on the helper bookmarks (which can survive I/O and hiding redlines) + void MarkManager::restoreAnnotationMarks(bool bDelete) + { + for (auto iter = getBookmarksBegin(); + iter != getBookmarksEnd(); ) + { + const OUString & rBookmarkName = (**iter).GetName(); + sal_Int32 nPos; + if ( rBookmarkName.startsWith("__Annotation__") && + (nPos = rBookmarkName.indexOf(S_ANNOTATION_BOOKMARK)) > -1 ) + { + ::sw::UndoGuard const undoGuard(m_rDoc.GetIDocumentUndoRedo()); + IDocumentMarkAccess::const_iterator_t pMark = findAnnotationMark(rBookmarkName.copy(0, nPos)); + if ( pMark != getAnnotationMarksEnd() ) + { + const SwPaM aPam((**iter).GetMarkStart(), (**pMark).GetMarkEnd()); + repositionMark(*pMark, aPam); + } + if (bDelete) + { + deleteMark(&**iter); + // this invalidates iter, have to start over... + iter = getBookmarksBegin(); + } + else + ++iter; + } + else + ++iter; + } + } + + OUString MarkManager::getUniqueMarkName(const OUString& rName) const + { + OSL_ENSURE(rName.getLength(), + "<MarkManager::getUniqueMarkName(..)> - a name should be proposed"); + if( m_rDoc.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; + OUString aPrefix = SwResId(STR_MARK_COPY).replaceFirst("%1", rName); + while(nCnt < SAL_MAX_INT32) + { + sTmp = aPrefix + 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() + { + stable_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} + }; + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkManager")); + for (const auto & rContainer : aContainers) + { + if (!rContainer.pContainer->empty()) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST(rContainer.pName)); + for (auto it = rContainer.pContainer->begin(); it != rContainer.pContainer->end(); ++it) + (*it)->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + } + (void)xmlTextWriterEndElement(pWriter); +} + +} // namespace ::sw::mark + +namespace +{ + bool lcl_Greater( const SwPosition& rPos, const SwNode& rNdIdx, std::optional<sal_Int32> oContentIdx ) + { + return rPos.GetNode() > rNdIdx || + ( oContentIdx && rPos.GetNode() == rNdIdx && rPos.GetContentIndex() > *oContentIdx ); + } +} + +// 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 SwNode& rMvPos, + std::optional<sal_Int32> oContentIdx) + : m_aName(rBkmk.GetName()) + , m_bHidden(false) + , m_eOrigBkmType(IDocumentMarkAccess::GetType(rBkmk)) +{ + const IBookmark* const pBookmark = dynamic_cast< const IBookmark* >(&rBkmk); + if(pBookmark) + { + m_aShortName = pBookmark->GetShortName(); + m_aCode = pBookmark->GetKeyCode(); + m_bHidden = pBookmark->IsHidden(); + m_aHideCondition = pBookmark->GetHideCondition(); + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pBookmark)); + if (pMetadatable) + { + m_pMetadataUndo = pMetadatable->CreateUndo(); + } + } + m_nNode1 = rBkmk.GetMarkPos().GetNodeIndex(); + m_nContent1 = rBkmk.GetMarkPos().GetContentIndex(); + + m_nNode1 -= rMvPos.GetIndex(); + if(oContentIdx && !m_nNode1) + m_nContent1 -= *oContentIdx; + + if(rBkmk.IsExpanded()) + { + m_nNode2 = rBkmk.GetOtherMarkPos().GetNodeIndex(); + m_nContent2 = rBkmk.GetOtherMarkPos().GetContentIndex(); + + m_nNode2 -= rMvPos.GetIndex(); + if(oContentIdx && !m_nNode2) + m_nContent2 -= *oContentIdx; + } + else + { + m_nNode2 = NODE_OFFSET_MAX; + m_nContent2 = -1; + } +} + +void SaveBookmark::SetInDoc( + SwDoc* pDoc, + const SwNode& rNewPos, + std::optional<sal_Int32> oContentIdx) +{ + SwPaM aPam(rNewPos); + if(oContentIdx) + { + if (aPam.GetPoint()->GetNode().IsContentNode()) + aPam.GetPoint()->SetContent( *oContentIdx ); + else + SAL_WARN("sw", "trying to sent content index, but point node is not a content node"); + } + + if(NODE_OFFSET_MAX != m_nNode2) + { + aPam.SetMark(); + + aPam.GetMark()->Adjust(m_nNode2); + if (aPam.GetMark()->GetNode().IsContentNode()) + { + if(oContentIdx && !m_nNode2) + aPam.GetMark()->SetContent(*oContentIdx + m_nContent2); + else + aPam.GetMark()->SetContent(m_nContent2); + } + else + SAL_WARN("sw", "trying to sent content index, but mark node is not a content node"); + } + + aPam.GetPoint()->Adjust(m_nNode1); + + if (aPam.GetPoint()->GetNode().IsContentNode()) + { + if(oContentIdx && !m_nNode1) + aPam.GetPoint()->SetContent(*oContentIdx + m_nContent1); + else + aPam.GetPoint()->SetContent(m_nContent1); + } + + if(aPam.HasMark() + && !CheckNodesRange(aPam.GetPoint()->GetNode(), aPam.GetMark()->GetNode(), true)) + return; + + ::sw::mark::IBookmark* const pBookmark = dynamic_cast<::sw::mark::IBookmark*>( + pDoc->getIDocumentMarkAccess()->makeMark(aPam, m_aName, + m_eOrigBkmType, sw::mark::InsertMode::CopyText)); + if(!pBookmark) + return; + + pBookmark->SetKeyCode(m_aCode); + pBookmark->SetShortName(m_aShortName); + pBookmark->Hide(m_bHidden); + pBookmark->SetHideCondition(m_aHideCondition); + + 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( + SwNode& rStt, + const SwNode& rEnd, + std::vector<SaveBookmark> * pSaveBkmk, + std::optional<sal_Int32> oStartContentIdx, + std::optional<sal_Int32> oEndContentIdx, + bool const isReplace) +{ + // illegal range ?? + if(rStt.GetIndex() > rEnd.GetIndex() + || (&rStt == &rEnd && (!oStartContentIdx || !oEndContentIdx || *oStartContentIdx >= *oEndContentIdx))) + return; + SwDoc& rDoc = rStt.GetDoc(); + + rDoc.getIDocumentMarkAccess()->deleteMarks(rStt, rEnd, pSaveBkmk, + oStartContentIdx, + oEndContentIdx, + isReplace); + + // 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 = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + for(SwRangeRedline* pRedl : rTable) + { + // Is at position? + auto [pRStt, pREnd] = pRedl->StartEnd(); + + if( lcl_Greater( *pRStt, rStt, oStartContentIdx ) && lcl_Lower( *pRStt, rEnd, oEndContentIdx )) + { + pRStt->Assign( rEnd ); + if( oEndContentIdx ) + pRStt->SetContent( *oEndContentIdx ); + else + { + bool bStt = true; + SwContentNode* pCNd = pRStt->GetNode().GetContentNode(); + if( !pCNd ) + pCNd = rDoc.GetNodes().GoNext( pRStt ); + if (!pCNd) + { + bStt = false; + pRStt->Assign(rStt); + pCNd = SwNodes::GoPrevious( pRStt ); + if( !pCNd ) + { + *pRStt = *pREnd; + pCNd = pRStt->GetNode().GetContentNode(); + } + } + if (pCNd && !bStt) + pRStt->AssignEndIndex( *pCNd ); + } + } + if( lcl_Greater( *pREnd, rStt, oStartContentIdx ) && lcl_Lower( *pREnd, rEnd, oEndContentIdx )) + { + pREnd->Assign( rStt ); + if (oStartContentIdx && rStt.IsContentNode()) + pREnd->SetContent( *oStartContentIdx ); + else + { + bool bStt = false; + SwContentNode* pCNd = pREnd->GetNode().GetContentNode(); + if( !pCNd ) + pCNd = SwNodes::GoPrevious( pREnd ); + if( !pCNd ) + { + bStt = true; + pREnd->Assign(rEnd); + pCNd = rDoc.GetNodes().GoNext( pREnd ); + if( !pCNd ) + { + *pREnd = *pRStt; + pCNd = pREnd->GetNode().GetContentNode(); + } + } + if (pCNd && !bStt) + pREnd->AssignEndIndex( *pCNd ); + } + } + } +} + +namespace sw { + +InsertText MakeInsertText(SwTextNode& rNode, const sal_Int32 nPos, const sal_Int32 nLen) +{ + SwCursor cursor(SwPosition(rNode, nPos), nullptr); + bool isInsideFieldmarkCommand(false); + bool isInsideFieldmarkResult(false); + while (auto const*const pMark = rNode.GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(*cursor.GetPoint())) + { + if (sw::mark::FindFieldSep(*pMark) < *cursor.GetPoint()) + { + isInsideFieldmarkResult = true; + } + else + { + isInsideFieldmarkCommand = true; + } + *cursor.GetPoint() = pMark->GetMarkStart(); + if (!cursor.Left(1)) + { + break; + } + } + return InsertText(nPos, nLen, isInsideFieldmarkCommand, isInsideFieldmarkResult); +} + +} // namespace sw + +/* 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 0000000000..e421ac9ad0 --- /dev/null +++ b/sw/source/core/doc/docchart.cxx @@ -0,0 +1,196 @@ +/* -*- 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 <ndindex.hxx> +#include <swtable.hxx> +#include <viewsh.hxx> +#include <ndole.hxx> +#include <swtblfmt.hxx> +#include <tblsel.hxx> +#include <frameformats.hxx> +#include <unochart.hxx> +#include <osl/diagnose.h> + +void SwTable::UpdateCharts() const +{ + GetFrameFormat()->GetDoc()->UpdateCharts( GetFrameFormat()->GetName() ); +} + +bool SwTable::IsTableComplexForChart( std::u16string_view aSelection ) const +{ + const SwTableBox* pSttBox, *pEndBox; + if( 2 < aSelection.size() ) + { + const size_t nSeparator = aSelection.find( u':' ); + OSL_ENSURE( std::u16string_view::npos != nSeparator, "no valid selection" ); + + // Remove brackets at the beginning and from the end + const sal_Int32 nOffset = '<' == aSelection[0] ? 1 : 0; + const sal_Int32 nLength = '>' == aSelection[ aSelection.size()-1 ] + ? aSelection.size()-1 : aSelection.size(); + + pSttBox = GetTableBox(OUString(aSelection.substr( nOffset, nSeparator - nOffset ))); + pEndBox = GetTableBox(OUString(aSelection.substr( 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 ) + return; + + for(const SwTableFormat* pFormat: *GetTableFrameFormats()) + { + 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() ) ) + { + // tdf#122995 for OLE/Charts in SW we do not (yet) have a refresh + // mechanism or embedding of the primitive representation, so this + // needs to be done locally here (simplest solution). + bool bImmediateMode(false); + + if(pONd->IsChart()) + { + // refresh to trigger repaint + const SwRect aChartRect(pONd->FindLayoutRect()); + if(!aChartRect.IsEmpty()) + const_cast<SwViewShell &>(rVSh).InvalidateWindows(aChartRect); + + // forced refresh of the chart's primitive representation + pONd->GetOLEObj().resetBufferedData(); + + // InvalidateTable using the Immediate-Mode, else the chart will + // not yet know that it is invalidated at the next repaint and create + // the same graphical representation again + bImmediateMode = true; + } + + SwChartDataProvider *pPCD = getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + pPCD->InvalidateTable( &rTable, bImmediateMode ); + // 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 ) + { + for(const SwTableFormat* pFormat: *GetTableFrameFormats()) + { + if( !pFormat->IsDefault() && + pFormat->GetName() == rNewName && IsUsed( *pFormat ) ) + { + bNameFound = true; + break; + } + } + } + + if( !bNameFound ) + rTableFormat.SetFormatName( rNewName, true ); + else + rTableFormat.SetFormatName( 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 0000000000..d813e08965 --- /dev/null +++ b/sw/source/core/doc/doccomp.cxx @@ -0,0 +1,2691 @@ +/* -*- 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 <type_traits> +#include <vector> + +using namespace ::com::sun::star; + +using std::vector; + +namespace { + +class SwCompareLine final +{ + const SwNode* m_pNode; +public: + explicit SwCompareLine( const SwNode& rNd ) : m_pNode( &rNd ) {} + SwCompareLine() : m_pNode( nullptr ) {} + + 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_pNode; } + + 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 SwNodeOffset PrevIdx( const SwNode* pNd ); + static SwNodeOffset 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 aLine ) + { m_aLines.push_back( aLine ); } + + 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; + SwCompareLine aLine; + + HashData() + : nNext( 0 ), nHash( 0 ) {} + }; + + 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<tools::Long[]> m_pMemory; + tools::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( size_t 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_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 ) + return; + + for( size_t n = 0; n < rData.GetLineCount(); ++n ) + { + const SwCompareLine aLine = rData.GetLine( n ); + sal_uLong nH = aLine.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].aLine = aLine; + *pFound = i; + break; + } + else if( m_pDataArr[i].nHash == nH && + m_pDataArr[i].aLine.Compare( aLine )) + 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( size_t nLen, char* pDiscard ) +{ + for( size_t n = 0; n < nLen; ++n ) + { + if( 2 == pDiscard[ n ] ) + pDiscard[n] = 0; + else if( pDiscard[ n ] ) + { + size_t j; + size_t length; + size_t 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 + { + size_t consec; + size_t minimum = 1; + size_t 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 tools::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[ std::make_signed_t<decltype(d)>(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 tools::Long dmin = nStt1 - nEnd2; /* Minimum valid diagonal. */ + const tools::Long dmax = nEnd1 - nStt2; /* Maximum valid diagonal. */ + const tools::Long fmid = nStt1 - nStt2; /* Center diagonal of top-down search. */ + const tools::Long bmid = nEnd1 - nEnd2; /* Center diagonal of bottom-up search. */ + + tools::Long fmin = fmid, fmax = fmid; /* Limits of top-down search. */ + tools::Long bmin = bmid, bmax = bmid; /* Limits of bottom-up search. */ + + tools::Long c; /* Cost. */ + tools::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) + { + tools::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) + { + tools::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) + { + tools::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_pNode->GetNodeType() ) + { + case SwNodeType::Text: + nRet = GetTextNodeHashValue( *m_pNode->GetTextNode(), nRet ); + break; + + case SwNodeType::Table: + { + const SwNode* pEndNd = m_pNode->EndOfSectionNode(); + SwNodeIndex aIdx( *m_pNode ); + 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_pNode; + switch( m_pNode->GetNodeType() ) + { + case SwNodeType::Table: + pNd = m_pNode->EndOfSectionNode(); + break; + + case SwNodeType::Section: + { + const SwSectionNode& rSNd = static_cast<const SwSectionNode&>(*m_pNode); + const SwSection& rSect = rSNd.GetSection(); + if( SectionType::Content != rSect.GetType() || rSect.IsProtect() ) + pNd = m_pNode->EndOfSectionNode(); + } + break; + default: break; + } + return *pNd; +} + +bool SwCompareLine::Compare( const SwCompareLine& rLine ) const +{ + return CompareNode( *m_pNode, *rLine.m_pNode ); +} + +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_pNode->GetNodeType() ) + { + case SwNodeType::Text: + sRet = m_pNode->GetTextNode()->GetExpandText(nullptr); + break; + + case SwNodeType::Table: + { + sRet = "Tabelle: " + SimpleTableToText(*m_pNode); + } + break; + + case SwNodeType::Section: + { + sRet = "Section - Node:"; + + const SwSectionNode& rSNd = static_cast<const SwSectionNode&>(*m_pNode); + const SwSection& rSect = rSNd.GetSection(); + switch( rSect.GetType() ) + { + case SectionType::Content: + if( rSect.IsProtect() ) + sRet += OUString::number( + sal_Int32(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_pNode->GetNodeType() && + SwNodeType::Text == rLine.GetNode().GetNodeType() ) + { + SwTextNode& rDstNd = *const_cast<SwTextNode*>(m_pNode->GetTextNode()); + const SwTextNode& rSrcNd = *rLine.GetNode().GetTextNode(); + SwDoc& rDstDoc = 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()->SetContent(nDstFrom + nSkip); + } + + if ( nSrcFrom < nSrcTo ) + { + bool bUndo = rDstDoc.GetIDocumentUndoRedo().DoesUndo(); + rDstDoc.GetIDocumentUndoRedo().DoUndo( false ); + SwPaM aCpyPam( rSrcNd, nSrcFrom ); + aCpyPam.SetMark(); + aCpyPam.GetPoint()->SetContent(nSrcTo); + aCpyPam.GetDoc().getIDocumentContentOperations().CopyRange( aCpyPam, *aPam.GetPoint(), + SwCopyFlags::CheckPosInFly); + rDstDoc.GetIDocumentUndoRedo().DoUndo( bUndo ); + + SwPaM* pTmp = new SwPaM( *aPam.GetPoint(), rpDelRing.get() ); + if( !rpDelRing ) + rpDelRing.reset(pTmp); + + pTmp->SetMark(); + pTmp->GetMark()->SetContent(nDstTo + nSkip); + nSkip += nSrcTo - nSrcFrom; + + if( rpInsRing ) + { + SwPaM* pCorr = rpInsRing->GetPrev(); + if( *pCorr->GetPoint() == *pTmp->GetPoint() ) + *pCorr->GetPoint() = *pTmp->GetMark(); + } + } + } + + bRet = true; + } + + return bRet; +} + +SwNodeOffset 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; +} + +SwNodeOffset 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(); + + SwNodeOffset nSrcSttIdx = NextIdx( rSrcEndNd.StartOfSectionNode() ); + SwNodeOffset nSrcEndIdx = rSrcEndNd.GetIndex(); + + SwNodeOffset nDstSttIdx = NextIdx( rDstEndNd.StartOfSectionNode() ); + SwNodeOffset 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( SwCompareLine( *pNd ) ); + nSrcSttIdx = NextIdx( pNd ); + } + + while( nDstSttIdx <= nDstEndIdx ) + { + const SwNode* pNd = rDstNds[ nDstSttIdx ]; + InsertLine( 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(), SwNodeOffset(0), + rData.GetLine( nEnd-1 ).GetEndNode(), SwNodeOffset(1) ); + + SwNodeOffset nOffset(0); + std::optional<SwCompareLine> xLine; + if( nInsPos >= 1 ) + { + if( GetLineCount() == nInsPos ) + { + xLine = GetLine( nInsPos-1 ); + nOffset = SwNodeOffset(1); + } + else + xLine = GetLine( nInsPos ); + } + + const SwNode* pLineNd; + if( xLine ) + { + if( nOffset ) + pLineNd = &xLine->GetEndNode(); + else + pLineNd = &xLine->GetNode(); + } + else + { + pLineNd = &GetEndOfContent(); + nOffset = SwNodeOffset(0); + } + + SwNodeIndex aInsPos( *pLineNd, nOffset ); + SwNodeIndex aSavePos( aInsPos, -1 ); + + rData.m_rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aInsPos.GetNode()); + 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(), SwNodeOffset(0), SwNodeOffset(-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()->GetNode(), -1 ); + pCorr->GetPoint()->Assign( 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 aDstLn = GetLine( nThisStt + nDstFrom - 1 ); + const SwCompareLine aSrcLn = rData.GetLine( nStt + nSrcFrom - 1 ); + + // Show differences in detail for lines that + // were matched as only slightly different + if( !aDstLn.ChangesInLine( aSrcLn, 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, 0, + OUString(), nullptr ); + do { + // #i65201#: Expand again, see comment above. + if( pTmp->GetPoint()->GetContentIndex() == 0 ) + { + pTmp->GetPoint()->Adjust(SwNodeOffset(1)); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->GetNode()) + { + pTmp->GetPoint()->Adjust(SwNodeOffset(-1)); + SwContentNode *const pContentNode( pTmp->GetPointContentNode() ); + if( pContentNode ) + pTmp->GetPoint()->SetContent( pContentNode->Len() ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->GetContentIndex() == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->GetNode(), -1); + if (prev.GetNode().IsTextNode()) + { + pTmp->GetMark()->Assign( + *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 ) + return; + + do { + if( pTmp->GetPoint()->GetContentIndex() == 0 ) + { + pTmp->GetPoint()->Adjust(SwNodeOffset(1)); + } + // #i101009# + // prevent redlines that end on structural end node + if (& GetEndOfContent() == + & pTmp->GetPoint()->GetNode()) + { + pTmp->GetPoint()->Adjust(SwNodeOffset(-1)); + SwContentNode *const pContentNode( pTmp->GetPointContentNode() ); + if( pContentNode ) + pTmp->GetPoint()->SetContent( pContentNode->Len() ); + // tdf#106218 try to avoid losing a paragraph break here: + if (pTmp->GetMark()->GetContentIndex() == 0) + { + SwNodeIndex const prev(pTmp->GetMark()->GetNode(), -1); + if (prev.GetNode().IsTextNode()) + { + pTmp->GetMark()->Assign( + *prev.GetNode().GetTextNode(), + prev.GetNode().GetTextNode()->Len()); + } + } + } + } while( m_pInsertRing.get() != ( pTmp = pTmp->GetNext()) ); + SwRedlineData aRedlnData( RedlineType::Insert, nAuthor, aTimeStamp, 0, + OUString(), nullptr ); + + // combine consecutive + if( pTmp->GetNext() != m_pInsertRing.get() ) + { + do { + // coverity[deref_arg] - the SwPaM delete moves a new entry into GetNext() + SwPosition& rSttEnd = *pTmp->End(), + & rEndStt = *pTmp->GetNext()->Start(); + const SwContentNode* pCNd; + if( rSttEnd == rEndStt || + (!rEndStt.GetContentIndex() && + rEndStt.GetNodeIndex() - 1 == rSttEnd.GetNodeIndex() && + nullptr != ( pCNd = rSttEnd.GetNode().GetContentNode() ) && + rSttEnd.GetContentIndex() == 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 { + // coverity[deref_arg] - pTmp is valid here + 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 sw::SpzFrameFormats* pSrcFrameFormats = rSrcDoc.GetSpzFrameFormats(); + const sw::SpzFrameFormats* pDestFrameFormats = rDestDoc.GetSpzFrameFormats(); + if (pSrcFrameFormats->size() == pDestFrameFormats->size()) + { + for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type i = 0; i < pSrcFrameFormats->size(); ++i) + { + const sw::SpzFrameFormat& rSrcFormat = *(*pSrcFrameFormats)[i]; + const sw::SpzFrameFormat& 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 +tools::Long SwDoc::CompareDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + tools::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.SetContent( pStt->GetContentIndex() ); + pDestRedl = new SwRangeRedline( rSrcRedl.GetRedlineData(), aPos ); + + if( RedlineType::Delete != pDestRedl->GetType() ) + return; + + // mark the area as deleted + const SwPosition* pEnd = rSrcRedl.End(); + + pDestRedl->SetMark(); + pDestRedl->GetPoint()->Adjust( pEnd->GetNodeIndex() - + pStt->GetNodeIndex() ); + if( pDestRedl->GetPointContentNode() ) + pDestRedl->GetPoint()->SetContent( pEnd->GetContentIndex() ); +} + +sal_uInt16 SaveMergeRedline::InsertRedline(SwPaM* pLastDestRedline) +{ + sal_uInt16 nIns = 0; + SwDoc& rDoc = pDestRedl->GetDoc(); + + if( RedlineType::Insert == pDestRedl->GetType() ) + { + // the part was inserted so copy it from the SourceDoc + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwNodeIndex aSaveNd( pDestRedl->GetPoint()->GetNode(), -1 ); + const sal_Int32 nSaveCnt = pDestRedl->GetPoint()->GetContentIndex(); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + + pSrcRedl->GetDoc().getIDocumentContentOperations().CopyRange( + *const_cast<SwPaM*>(static_cast<const SwPaM*>(pSrcRedl)), + *pDestRedl->GetPoint(), SwCopyFlags::CheckPosInFly); + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + pDestRedl->SetMark(); + ++aSaveNd; + pDestRedl->GetMark()->Assign( aSaveNd, 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( !rDoc.getIDocumentRedlineAccess().GetRedline( *pDStt, &n ) && n ) + --n; + + const SwRedlineTable& rRedlineTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + for( ; n < rRedlineTable.size(); ++n ) + { + SwRangeRedline* pRedl = rRedlineTable[ n ]; + SwPosition* pRStt = pRedl->Start(), + * pREnd = pRedl->End(); + 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 (rDoc.GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pCpyRedl )); + + // now modify doc: append redline, undo (and count) + rDoc.getIDocumentRedlineAccess().AppendRedline( pCpyRedl, true ); + if( pUndo ) + { + rDoc.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 (rDoc.GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoCompDoc( *pDestRedl )); + + // now modify doc: append redline, undo (and count) + IDocumentRedlineAccess::AppendResult const result( + rDoc.getIDocumentRedlineAccess().AppendRedline(pDestRedl, true)); + if( pUndo ) + { + rDoc.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 +tools::Long SwDoc::MergeDoc( const SwDoc& rDoc ) +{ + if( &rDoc == this ) + return 0; + + tools::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(); + SwNodeOffset nEndOfExtra = rSrcDoc.GetNodes().GetEndOfExtras().GetIndex(); + SwNodeOffset nMyEndOfExtra = GetNodes().GetEndOfExtras().GetIndex(); + for(const SwRangeRedline* pRedl : rSrcRedlTable) + { + SwNodeOffset nNd = pRedl->GetPoint()->GetNodeIndex(); + 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] ); + } + std::swap( currL, prevL ); + } + 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 0000000000..783e1aa234 --- /dev/null +++ b/sw/source/core/doc/doccorr.cxx @@ -0,0 +1,367 @@ +/* -*- 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 <IDocumentRedlineAccess.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.IsStartNode() ? rNode.GetStartNode() : rNode.StartOfSectionNode(); + while( ( pStartNode != nullptr ) && + ( pStartNode->StartOfSectionNode() != pStartNode ) && + // section node is only start node allowing overlapped delete + pStartNode->IsSectionNode() ) + { + 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) + { + SwPosition & rPos = pPam->GetBound(bool(nb)); + if(&rPos.GetNode() == pOldNode) + { + rPos.Assign(rNewPos.GetNode(), SwNodeOffset(0), + nCntIdx + rPos.GetContentIndex()); + } + } + } +} + +void PaMCorrAbs( const SwPaM& rRange, + const SwPosition& rNewPos ) +{ + SwPosition const aStart( *rRange.Start() ); + SwPosition const aEnd( *rRange.End() ); + SwPosition const aNewPos( rNewPos ); + SwDoc& rDoc = aStart.GetNode().GetDoc(); + + if (SwCursorShell *const pShell = rDoc.GetEditShell()) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + const SwCursorShell* pCursorShell = dynamic_cast<const SwCursorShell*>(&rShell); + if(!pCursorShell) + continue; + 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 ); + } + } + + rDoc.cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : rDoc.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.GetNode() ) != + lcl_FindUnoCursorSection( + pUnoCursor->GetPoint()->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 SwNode& rOldNode, + const SwPosition& rNewPos, + const sal_Int32 nOffset, + bool bMoveCursor) +{ + const SwContentNode *const pContentNode( rOldNode.GetContentNode() ); + SwPaM const aPam(rOldNode, 0, + rOldNode, pContentNode ? pContentNode->Len() : 0); + SwPosition aNewPos(rNewPos); + aNewPos.AdjustContent(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 ) +{ + const SwPosition& aStart(*rRange.Start()); + const SwPosition& aEnd(*rRange.End()); + + DelBookmarks( aStart.GetNode(), aEnd.GetNode(), nullptr, aStart.GetContentIndex(), aEnd.GetContentIndex() ); + + if(bMoveCursor) + ::PaMCorrAbs(rRange, rNewPos); +} + +void SwDoc::CorrAbs( + const SwNodeIndex& rStartNode, + const SwNodeIndex& rEndNode, + const SwPosition& rNewPos, + bool bMoveCursor ) +{ + DelBookmarks( rStartNode.GetNode(), rEndNode.GetNode() ); + + if(bMoveCursor) + { + SwContentNode *const pContentNode( rEndNode.GetNode().GetContentNode() ); + SwPaM const aPam(rStartNode, 0, + rEndNode, pContentNode ? pContentNode->Len() : 0); + ::PaMCorrAbs(aPam, rNewPos); + } +} + +void PaMCorrRel( const SwNode &rOldNode, + const SwPosition &rNewPos, + const sal_Int32 nOffset ) +{ + const SwNode* pOldNode = &rOldNode; + SwPosition aNewPos( rNewPos ); + const SwDoc& rDoc = pOldNode->GetDoc(); + + const sal_Int32 nCntIdx = rNewPos.GetContentIndex() + nOffset; + + if (SwCursorShell const* pShell = rDoc.GetEditShell()) + { + for(const SwViewShell& rShell : pShell->GetRingContainer()) + { + SwCursorShell* pCursorShell = const_cast<SwCursorShell*>(dynamic_cast<const SwCursorShell*>(&rShell)); + if(!pCursorShell) + continue; + 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 ); + } + } + + rDoc.cleanupUnoCursorTable(); + for(const auto& pWeakUnoCursor : rDoc.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 SwNode& 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, aNewPos, aNewPos.GetContentIndex() + 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( auto pEditShell = dynamic_cast<const SwEditShell *>(&rCurrentSh) ) + { + return pEditShell; + } + } + } + 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 0000000000..57e561e1f4 --- /dev/null +++ b/sw/source/core/doc/docdesc.cxx @@ -0,0 +1,1044 @@ +/* -*- 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 <osl/diagnose.h> +#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 <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 <o3tl/unit_conversion.hxx> + +#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 = o3tl::toTwips(1, o3tl::Length::cm); + nMinLeft = o3tl::toTwips(2, o3tl::Length::cm); + } + else if (!utl::ConfigManager::IsFuzzing() && MeasurementSystem::Metric == SvtSysLocale().GetLocaleData().getMeasurementSystemEnum() ) + { + nMinTop = nMinBottom = nMinLeft = nMinRight = o3tl::toTwips(2, o3tl::Length::cm); + } + else + { + nMinTop = nMinBottom = o3tl::toTwips(1, o3tl::Length::in); // as in MS Word + nMinLeft = nMinRight = o3tl::toTwips(1.25, o3tl::Length::in); + } + + // set margins + SvxLRSpaceItem aLR( RES_LR_SPACE ); + SvxULSpaceItem aUL( RES_UL_SPACE ); + + aUL.SetUpper( o3tl::narrowing<sal_uInt16>(nMinTop) ); + aUL.SetLower( o3tl::narrowing<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_BACKGROUND_FULL_SIZE, RES_BACKGROUND_FULL_SIZE, // [131 + RES_RTL_GUTTER, RES_RTL_GUTTER, // [132 + 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 these: + case RES_COL: + case RES_PAPER_BIN: + case RES_BACKGROUND_FULL_SIZE: + case RES_RTL_GUTTER: + 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 SwFormatContent &aCnt = rFormatHead.GetHeaderFormat()->GetContent(); + + if (!aCnt.GetContentIdx()) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetHeader() ); + } + else + { + const SwFrameFormat *pRight = rHead.GetHeaderFormat(); + if (!pRight) + return; + const SwFormatContent &aRCnt = pRight->GetContent(); + + 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. + SwStartNode* pSttNd = SwNodes::MakeEmptySection( GetNodes().GetEndOfAutotext(), SwHeaderStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), SwNodeOffset(0), + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + GetNodes().Copy_( aRange, *pSttNd->EndOfSectionNode(), false ); + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, *pSttNd); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(*pSttNd); + 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 SwFormatContent &aLCnt = rFormatFoot.GetFooterFormat()->GetContent(); + if( !aLCnt.GetContentIdx() ) + { + const SwFrameFormat& rChgedFrameFormat = getConstFrameFormat(rChged, bLeft, bFirst); + rDescFrameFormat.SetFormatAttr( rChgedFrameFormat.GetFooter() ); + } + else + { + const SwFrameFormat *pRight = rFoot.GetFooterFormat(); + if (!pRight) + return; + const SwFormatContent &aRCnt = pRight->GetContent(); + + 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. + SwStartNode* pSttNd = SwNodes::MakeEmptySection( GetNodes().GetEndOfAutotext(), SwFooterStartNode ); + SwNodeRange aRange( aRCnt.GetContentIdx()->GetNode(), SwNodeOffset(0), + *aRCnt.GetContentIdx()->GetNode().EndOfSectionNode() ); + GetNodes().Copy_( aRange, *pSttNd->EndOfSectionNode(), false ); + GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRange, nullptr, *pSttNd); + SwPaM const source(aRange.aStart, aRange.aEnd); + SwPosition dest(*pSttNd); + 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()) + { + // Stash header formats as needed. + const SwFormatHeader& rLeftHead = rChged.GetLeft().GetHeader(); + const SwFormatHeader& rFirstMasterHead = rChged.GetFirstMaster().GetHeader(); + const SwFormatHeader& rFirstLeftHead = rChged.GetFirstLeft().GetHeader(); + const bool bStashLeftHead = !rDesc.IsHeaderShared() && rChged.IsHeaderShared(); + const bool bStashFirstMasterHead = !rDesc.IsFirstShared() && rChged.IsFirstShared(); + const bool bStashFirstLeftHead = (!rDesc.IsHeaderShared() && rChged.IsHeaderShared()) || (!rDesc.IsFirstShared() && rChged.IsFirstShared()); + if (bStashLeftHead && rLeftHead.GetRegisteredIn() && !rDesc.HasStashedFormat(true, true, false)) + rDesc.StashFrameFormat(rChged.GetLeft(), true, true, false); + if (bStashFirstMasterHead && rFirstMasterHead.GetRegisteredIn() && !rDesc.HasStashedFormat(true, false, true)) + rDesc.StashFrameFormat(rChged.GetFirstMaster(), true, false, true); + if (bStashFirstLeftHead && rFirstLeftHead.GetRegisteredIn() && !rDesc.HasStashedFormat(true, true, true)) + rDesc.StashFrameFormat(rChged.GetFirstLeft(), true, true, true); + + // Stash footer formats as needed. + const SwFormatFooter& rLeftFoot = rChged.GetLeft().GetFooter(); + const SwFormatFooter& rFirstMasterFoot = rChged.GetFirstMaster().GetFooter(); + const SwFormatFooter& rFirstLeftFoot = rChged.GetFirstLeft().GetFooter(); + const bool bStashLeftFoot = !rDesc.IsFooterShared() && rChged.IsFooterShared(); + const bool bStashFirstMasterFoot = !rDesc.IsFirstShared() && rChged.IsFirstShared(); + const bool bStashFirstLeftFoot = (!rDesc.IsFooterShared() && rChged.IsFooterShared()) || (!rDesc.IsFirstShared() && rChged.IsFirstShared()); + if (bStashLeftFoot && rLeftFoot.GetRegisteredIn() && !rDesc.HasStashedFormat(false, true, false)) + rDesc.StashFrameFormat(rChged.GetLeft(), false, true, false); + if (bStashFirstMasterFoot && rFirstMasterFoot.GetRegisteredIn() && !rDesc.HasStashedFormat(false, false, true)) + rDesc.StashFrameFormat(rChged.GetFirstMaster(), false, false, true); + if (bStashFirstLeftFoot && rFirstLeftFoot.GetRegisteredIn() && !rDesc.HasStashedFormat(false, true, true)) + rDesc.StashFrameFormat(rChged.GetFirstLeft(), false, true, true); + + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoPageDesc>(rDesc, rChged, this)); + } + else + { + SwUndoId nBeingUndone(SwUndoId::EMPTY); + GetIDocumentUndoRedo().GetFirstRedoInfo(nullptr, &nBeingUndone); + if (SwUndoId::HEADER_FOOTER == nBeingUndone) + { + // The last format change is currently being undone. Remove header/footer and corresponding nodes. + auto rDescMasterHeaderFormat = rDesc.GetMaster().GetFormatAttr(RES_HEADER); + auto rDescLeftHeaderFormat = rDesc.GetLeft().GetFormatAttr(RES_HEADER); + auto rDescFirstLeftHeaderFormat = rDesc.GetFirstLeft().GetFormatAttr(RES_HEADER); + auto rDescMasterFooterFormat = rDesc.GetMaster().GetFormatAttr(RES_FOOTER); + auto rDescLeftFooterFormat = rDesc.GetLeft().GetFormatAttr(RES_FOOTER); + auto rDescFirstLeftFooterFormat = rDesc.GetFirstLeft().GetFormatAttr(RES_FOOTER); + + auto rChgedMasterHeaderFormat = rChged.GetMaster().GetFormatAttr(RES_HEADER); + auto rChgedLeftHeaderFormat = rChged.GetLeft().GetFormatAttr(RES_HEADER); + auto rChgedFirstLeftHeaderFormat = rChged.GetFirstLeft().GetFormatAttr(RES_HEADER); + auto rChgedMasterFooterFormat = rChged.GetMaster().GetFormatAttr(RES_FOOTER); + auto rChgedLeftFooterFormat = rChged.GetLeft().GetFormatAttr(RES_FOOTER); + auto rChgedFirstLeftFooterFormat = rChged.GetFirstLeft().GetFormatAttr(RES_FOOTER); + + rDesc.GetMaster().ResetFormatAttr(RES_HEADER); + rDesc.GetLeft().ResetFormatAttr(RES_HEADER); + rDesc.GetFirstLeft().ResetFormatAttr(RES_HEADER); + rDesc.GetMaster().ResetFormatAttr(RES_FOOTER); + rDesc.GetLeft().ResetFormatAttr(RES_FOOTER); + rDesc.GetFirstLeft().ResetFormatAttr(RES_FOOTER); + + auto lDelHFFormat = [this](SwClient* pToRemove, SwFrameFormat* pFormat) + { + // Code taken from lcl_DelHFFormat + pFormat->Remove(pToRemove); + SwFormatContent& rCnt = const_cast<SwFormatContent&>(pFormat->GetContent()); + if (rCnt.GetContentIdx()) + { + SwNode* pNode = nullptr; + { + SwNodeIndex aIdx(*rCnt.GetContentIdx(), 0); + pNode = &aIdx.GetNode(); + SwNodeOffset nEnd = pNode->EndOfSectionIndex(); + while (aIdx < nEnd) + { + if (pNode->IsContentNode() && + static_cast<SwContentNode*>(pNode)->HasWriterListeners()) + { + SwCursorShell* pShell = SwIterator<SwCursorShell, SwContentNode>(*static_cast<SwContentNode*>(pNode)).First(); + if (pShell) + { + pShell->ParkCursor(aIdx.GetNode()); + aIdx = nEnd - 1; + } + } + ++aIdx; + pNode = &aIdx.GetNode(); + } + } + rCnt.SetNewContentIdx(nullptr); + + ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo()); + + assert(pNode); + getIDocumentContentOperations().DeleteSection(pNode); + } + delete pFormat; + }; + + if (rDescMasterHeaderFormat.GetHeaderFormat() && rDescMasterHeaderFormat != rChgedMasterHeaderFormat) + lDelHFFormat(&rDescMasterHeaderFormat, rDescMasterHeaderFormat.GetHeaderFormat()); + else if (rDescLeftHeaderFormat.GetHeaderFormat() && rDescLeftHeaderFormat != rChgedLeftHeaderFormat) + lDelHFFormat(&rDescLeftHeaderFormat, rDescLeftHeaderFormat.GetHeaderFormat()); + else if (rDescFirstLeftHeaderFormat.GetHeaderFormat() && rDescFirstLeftHeaderFormat != rChgedFirstLeftHeaderFormat) + lDelHFFormat(&rDescFirstLeftHeaderFormat, rDescFirstLeftHeaderFormat.GetHeaderFormat()); + + else if (rDescMasterFooterFormat.GetFooterFormat() && rDescMasterFooterFormat != rChgedMasterFooterFormat) + lDelHFFormat(&rDescMasterFooterFormat, rDescMasterFooterFormat.GetFooterFormat()); + else if (rDescLeftFooterFormat.GetFooterFormat() && rDescLeftFooterFormat != rChgedLeftFooterFormat) + lDelHFFormat(&rDescLeftFooterFormat, rDescLeftFooterFormat.GetFooterFormat()); + else if (rDescFirstLeftFooterFormat.GetFooterFormat() && rDescFirstLeftFooterFormat != rChgedFirstLeftFooterFormat) + lDelHFFormat(&rDescFirstLeftFooterFormat, rDescFirstLeftFooterFormat.GetFooterFormat()); + } + } + ::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() ); + + // Synch header. + const SwFormatHeader& rMasterHead = rChged.GetMaster().GetHeader(); + rDesc.GetMaster().SetFormatAttr( rMasterHead ); + const bool bRestoreStashedLeftHead = rDesc.IsHeaderShared() && !rChged.IsHeaderShared(); + const bool bRestoreStashedFirstMasterHead = rDesc.IsFirstShared() && !rChged.IsFirstShared(); + const bool bRestoreStashedFirstLeftHead = (rDesc.IsHeaderShared() && !rChged.IsHeaderShared()) || (rDesc.IsFirstShared() && !rChged.IsFirstShared()); + const SwFrameFormat* pStashedLeftFormat = bRestoreStashedLeftHead ? rChged.GetStashedFrameFormat(true, true, false) : nullptr; + const SwFrameFormat* pStashedFirstMasterFormat = bRestoreStashedFirstMasterHead ? rChged.GetStashedFrameFormat(true, false, true) : nullptr; + const SwFrameFormat* pStashedFirstLeftFormat = bRestoreStashedFirstLeftHead ? rChged.GetStashedFrameFormat(true, true, true) : nullptr; + CopyMasterHeader(rChged, pStashedLeftFormat ? pStashedLeftFormat->GetHeader() : rMasterHead, rDesc, true, false); // Copy left header + CopyMasterHeader(rChged, pStashedFirstMasterFormat ? pStashedFirstMasterFormat->GetHeader() : rMasterHead, rDesc, false, true); // Copy first master + CopyMasterHeader(rChged, pStashedFirstLeftFormat ? pStashedFirstLeftFormat->GetHeader() : rMasterHead, rDesc, true, true); // Copy first left + + if (pStashedLeftFormat) + rDesc.RemoveStashedFormat(true, true, false); + + if (pStashedFirstMasterFormat) + rDesc.RemoveStashedFormat(true, false, true); + + if (pStashedFirstLeftFormat) + rDesc.RemoveStashedFormat(true, true, true); + + rDesc.ChgHeaderShare( rChged.IsHeaderShared() ); + + // Synch Footer. + const SwFormatFooter& rMasterFoot = rChged.GetMaster().GetFooter(); + rDesc.GetMaster().SetFormatAttr( rMasterFoot ); + const bool bRestoreStashedLeftFoot = rDesc.IsFooterShared() && !rChged.IsFooterShared(); + const bool bRestoreStashedFirstMasterFoot = rDesc.IsFirstShared() && !rChged.IsFirstShared(); + const bool bRestoreStashedFirstLeftFoot = (rDesc.IsFooterShared() && !rChged.IsFooterShared()) || (rDesc.IsFirstShared() && !rChged.IsFirstShared()); + const SwFrameFormat* pStashedLeftFoot = bRestoreStashedLeftFoot ? rChged.GetStashedFrameFormat(false, true, false) : nullptr; + const SwFrameFormat* pStashedFirstMasterFoot = bRestoreStashedFirstMasterFoot ? rChged.GetStashedFrameFormat(false, false, true) : nullptr; + const SwFrameFormat* pStashedFirstLeftFoot = bRestoreStashedFirstLeftFoot ? rChged.GetStashedFrameFormat(false, true, true) : nullptr; + CopyMasterFooter(rChged, pStashedLeftFoot ? pStashedLeftFoot->GetFooter() : rMasterFoot, rDesc, true, false); // Copy left footer + CopyMasterFooter(rChged, pStashedFirstMasterFoot ? pStashedFirstMasterFoot->GetFooter() : rMasterFoot, rDesc, false, true); // Copy first master + CopyMasterFooter(rChged, pStashedFirstLeftFoot ? pStashedFirstLeftFoot->GetFooter() : rMasterFoot, rDesc, true, true); // Copy first left + + if (pStashedLeftFormat) + rDesc.RemoveStashedFormat(false, true, false); + + if (pStashedFirstMasterFoot) + rDesc.RemoveStashedFormat(false, false, true); + + if (pStashedFirstLeftFoot) + rDesc.RemoveStashedFormat(false, true, true); + + 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(); + + 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(auto pFEShell = dynamic_cast<SwFEShell*>( &rShell)) + { + pShell = pFEShell; + 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) + return; + + mbOLEPrtNotifyPending = mbAllOLENotify = false; + + std::unique_ptr<SwOLENodes> pNodes = SwContentNode::CreateOLENodesArray( *GetDfltGrfFormatColl(), true ); + if( !pNodes ) + return; + + ::StartProgress( STR_STATSTR_SWGPRTOLENOTIFY, + 0, pNodes->size(), GetDocShell()); + getIDocumentLayoutAccess().GetCurrentLayout()->StartAllAction(); + SwUpdateAttr aHint(0,0,0); + 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->UpdateAttr(aHint); + } + } + 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 0000000000..aecbe2ac82 --- /dev/null +++ b/sw/source/core/doc/docdraw.cxx @@ -0,0 +1,699 @@ +/* -*- 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 <osl/diagnose.h> +#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 tools::Rectangle 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 tools::Rectangle aObjRect = _rSdrObj.GetSnapRect(); + const_cast<SwAnchoredDrawObject*>(pAnchoredDrawObj) + ->SetLastObjRect( aObjRect ); + } + } +} + +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( o3tl::narrowing<sal_uInt16>(rMrkList.GetMarkCount()), *this)); + + // #i53320# + bool bGroupMembersNotPositioned( false ); + { + SwAnchoredDrawObject* pAnchoredDrawObj = + static_cast<SwAnchoredDrawObject*>(pMyContact->GetAnchoredObj( pObj )); + bGroupMembersNotPositioned = pAnchoredDrawObj->NotYetPositioned(); + } + + std::map<const SdrObject*, SwFrameFormat*> vSavedTextBoxes; + // 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 + // Before the format will be killed, save its textbox for later use. + if (auto pShapeFormat = pContact->GetFormat()) + if (auto pTextBoxNode = pShapeFormat->GetOtherTextBoxFormats()) + for (const auto& rTextBoxElement : pTextBoxNode->GetAllTextBoxes()) + vSavedTextBoxes.emplace(rTextBoxElement); + + 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 ); + + // Add the saved textboxes to the new format. + auto pTextBoxNode = std::make_shared<SwTextBoxNode>( + SwTextBoxNode(static_cast<SwFrameFormat*>(pFormat))); + for (const auto& pTextBoxEntry : vSavedTextBoxes) + { + pTextBoxNode->AddTextBox(const_cast<SdrObject*>(pTextBoxEntry.first), + pTextBoxEntry.second); + pTextBoxEntry.second->SetOtherTextBoxFormats(pTextBoxNode); + } + pFormat->SetOtherTextBoxFormats(pTextBoxNode); + vSavedTextBoxes.clear(); + + 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; +} + +static void lcl_CollectTextBoxesForSubGroupObj(SwFrameFormat* pTargetFormat, std::shared_ptr<SwTextBoxNode> pTextBoxNode, + SdrObject* pSourceObjs) +{ + if (auto pChildrenObjs = pSourceObjs->getChildrenOfSdrObject()) + for (const rtl::Reference<SdrObject>& pSubObj : *pChildrenObjs) + lcl_CollectTextBoxesForSubGroupObj(pTargetFormat, pTextBoxNode, pSubObj.get()); + else + { + if (auto pTextBox = pTextBoxNode->GetTextBox(pSourceObjs)) + { + if (!pTargetFormat->GetOtherTextBoxFormats()) + { + pTargetFormat->SetOtherTextBoxFormats(std::make_shared<SwTextBoxNode>(SwTextBoxNode(pTargetFormat))); + } + pTargetFormat->GetOtherTextBoxFormats()->AddTextBox(pSourceObjs, pTextBox); + pTextBox->SetOtherTextBoxFormats(pTargetFormat->GetOtherTextBoxFormats()); + } + } +} + + +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 ( auto pObjGroup = dynamic_cast<SdrObjGroup*>(pObj) ) + { + SwDrawContact *pContact = static_cast<SwDrawContact*>(GetUserCall(pObj)); + + std::shared_ptr<SwTextBoxNode> pTextBoxNode; + if (auto pGroupFormat = pContact->GetFormat()) + pTextBoxNode = pGroupFormat->GetOtherTextBoxFormats(); + + SwFormatAnchor aAnch( pContact->GetFormat()->GetAnchor() ); + SdrObjList *pLst = pObjGroup->GetSubList(); + + SwUndoDrawUnGroup* pUndo = nullptr; + if( bUndo ) + { + pUndo = new SwUndoDrawUnGroup( pObjGroup, *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 ); + + if (pTextBoxNode) + { + if (!pObj->getChildrenOfSdrObject()) + { + if (auto pTextBoxFormat = pTextBoxNode->GetTextBox(pSubObj)) + { + auto pNewTextBoxNode =std::make_shared<SwTextBoxNode>(SwTextBoxNode(pFormat)); + pNewTextBoxNode->AddTextBox(pSubObj, pTextBoxFormat); + pFormat->SetOtherTextBoxFormats(pNewTextBoxNode); + pTextBoxFormat->SetOtherTextBoxFormats(pNewTextBoxNode); + } + } + else + { + lcl_CollectTextBoxesForSubGroupObj(pFormat, pTextBoxNode, pSubObj); + } + } + // #i36010# - set layout direction of the position + pFormat->SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + if (pSubObj->GetName().isEmpty()) + pSubObj->SetName(pFormat->GetName()); + pFormatsAndObjs[i].emplace_back( pFormat, pSubObj ); + + if( bUndo ) + pUndo->AddObj( o3tl::narrowing<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( auto pDrawObj = dynamic_cast<SwVirtFlyDrawObj*>( pObj) ) + { + SwFlyFrameFormat* pFrameFormat = pDrawObj->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( o3tl::narrowing<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 0000000000..c78d8e18b6 --- /dev/null +++ b/sw/source/core/doc/docedt.cxx @@ -0,0 +1,866 @@ +/* -*- 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 <rolbck.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 <osl/diagnose.h> + +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 SwNode* pInsertPos, bool const isForceToStartPos) +{ + SwPosition aPos(rStartPos); + for(const SaveFly & rSave : rArr) + { + // create new anchor + SwFrameFormat* pFormat = rSave.pFrameFormat; + SwFormatAnchor aAnchor( pFormat->GetAnchor() ); + + if (rSave.isAtInsertNode || isForceToStartPos) + { + if( pInsertPos != nullptr ) + { + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + assert(pInsertPos->GetContentNode()); + aPos.Assign( *pInsertPos->GetContentNode(), + rSave.nContentIndex); + } + else + { + assert(aAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR); + aPos = rStartPos; + } + } + else + { + aPos.Assign(rStartPos.GetNode()); + assert(aPos.GetNode().GetContentNode()); + } + } + else + { + aPos.Assign(rStartPos.GetNodeIndex() + rSave.nNdDiff); + assert(aPos.GetNode().GetContentNode()); + aPos.SetContent( + rSave.nNdDiff == SwNodeOffset(0) + ? rStartPos.GetContentIndex() + rSave.nContentIndex + : rSave.nContentIndex); + } + + aAnchor.SetAnchor( &aPos ); + pFormat->GetDoc()->GetSpzFrameFormats()->push_back(static_cast<sw::SpzFrameFormat*>(pFormat)); + // SetFormatAttr should call Modify() and add it to the node + pFormat->SetFormatAttr( aAnchor ); + SwContentNode* pCNd = aPos.GetNode().GetContentNode(); + if (pCNd && pCNd->getLayoutFrame(pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) + pFormat->MakeFrames(); + } + sw::CheckAnchoredFlyConsistency(rStartPos.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) +{ + sw::SpzFrameFormats& rSpzs = *rRg.aStart.GetNode().GetDoc().GetSpzFrameFormats(); + for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0; n < rSpzs.size(); ++n ) + { + auto pSpz = rSpzs[n]; + SwFormatAnchor const*const pAnchor = &pSpz->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + rRg.aStart <= *pAnchorNode && *pAnchorNode < rRg.aEnd.GetNode() ) + { + SaveFly aSave( pAnchorNode->GetIndex() - rRg.aStart.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? pAnchor->GetAnchorContentOffset() + : 0, + pSpz, false ); + rArr.push_back( aSave ); + pSpz->DelFrames(); + // set a dummy anchor position to maintain anchoring invariants + SwFormatAnchor aAnchor( pSpz->GetAnchor() ); + aAnchor.SetAnchor(nullptr); + pSpz->SetFormatAttr(aAnchor); + rSpzs.erase( rSpzs.begin() + n-- ); + } + } + sw::CheckAnchoredFlyConsistency(rRg.aStart.GetNode().GetDoc()); +} + +void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, + SaveFlyArr& rArr, bool bMoveAllFlys, SwHistory *const pHistory) +{ + sw::SpzFrameFormats& rFormats = *rPam.GetPoint()->GetNode().GetDoc().GetSpzFrameFormats(); + sw::SpzFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + + const SwPosition* pPos = rPam.Start(); + const SwNode& rSttNd = pPos->GetNode(); + + SwPosition atParaEnd(*rPam.End()); + if (bMoveAllFlys) + { + assert(!rPam.End()->GetNode().IsTextNode() // can be table end-node + || rPam.End()->GetContentIndex() == rPam.End()->GetNode().GetTextNode()->Len()); + atParaEnd.Adjust(SwNodeOffset(1)); + } + + for(sw::FrameFormats<sw::SpzFrameFormat*>::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.GetNode() || + rInsPos.GetNode() >= *pContentIdx->GetNode().EndOfSectionNode()))) + { + 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.GetNode() == pAPos->GetNode()))) + || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() + && (bInsPos = (rInsPos == *pAPos)))) + { + if (pHistory) + { + pHistory->AddChangeFlyAnchor(*pFormat); + } + SaveFly aSave( pAPos->GetNodeIndex() - rSttNd.GetIndex(), + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) + ? (pAPos->GetNode() == rSttNd) + ? pAPos->GetContentIndex() - rPam.Start()->GetContentIndex() + : pAPos->GetContentIndex() + : 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()->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( SwNode& rMkNd, + SwNode& rPtNd, + std::optional<sal_Int32> oMkContentIdx, std::optional<sal_Int32> oPtContentIdx) +{ + assert(oMkContentIdx.has_value() == oPtContentIdx.has_value()); + SwPosition const point(oPtContentIdx + ? SwPosition(rPtNd, rPtNd.GetContentNode(), *oPtContentIdx) + : SwPosition(rPtNd)); + SwPosition const mark(oPtContentIdx + ? SwPosition(rMkNd, rMkNd.GetContentNode(), *oMkContentIdx) + : SwPosition(rMkNd)); + SwPosition const& rStart = mark <= point ? mark : point; + SwPosition const& rEnd = mark <= point ? point : mark; + + SwDoc& rDoc = rMkNd.GetDoc(); + sw::SpzFrameFormats& rTable = *rDoc.GetSpzFrameFormats(); + for ( auto i = rTable.size(); i; ) + { + sw::SpzFrameFormat* 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, oPtContentIdx + ? DelContentType::AllMask|DelContentType::WriterfilterHack + : DelContentType::AllMask|DelContentType::WriterfilterHack|DelContentType::CheckNoCntnt)) + || ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && IsDestroyFrameAnchoredAtChar(*pAPos, rStart, rEnd, oPtContentIdx + ? 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()->GetNode(), + *rContent.GetContentIdx()-> + GetNode().EndOfSectionNode() ); + // Position could have been moved! + if (i > rTable.size()) + i = rTable.size(); + else if (i == rTable.size() || pFormat != rTable[i]) + i = std::distance(rTable.begin(), rTable.find(pFormat)); + } + + rDoc.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 SwNode& rInsIdx, sal_Int32 nCnt ) + : mnSaveContent( nCnt ) +{ + const SwDoc& rDest = rInsIdx.GetDoc(); + if( rDest.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + return; + + SwRedlineTable::size_type nFndPos; + const SwPosition* pEnd; + SwPosition aSrcPos( rInsIdx, rInsIdx.GetContentNode(), nCnt ); + rDest.getIDocumentRedlineAccess().GetRedline( aSrcPos, &nFndPos ); + const SwRangeRedline* pRedl; + while( nFndPos-- + && *( pEnd = ( pRedl = rDest.getIDocumentRedlineAccess().GetRedlineTable()[ nFndPos ] )->End() ) == aSrcPos + && *pRedl->Start() < aSrcPos ) + { + if( !moSaveIndex ) + { + moSaveIndex.emplace( rInsIdx, -1 ); + } + mvSavArr.push_back( const_cast<SwPosition*>(pEnd) ); + } +} + +SaveRedlEndPosForRestore::~SaveRedlEndPosForRestore() +{ + moSaveIndex.reset(); +} + +void SaveRedlEndPosForRestore::Restore() +{ + if (mvSavArr.empty()) + return; + ++(*moSaveIndex); + SwContentNode* pNode = moSaveIndex->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( *moSaveIndex, 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 WhichRangesContainer& pRanges) +{ + std::vector<sal_uInt16> aResult; + + for(const WhichPair& rPair : pRanges) + { + for (sal_uInt16 j = rPair.first; j <= rPair.second; j++) + aResult.push_back(j); + } + + return aResult; +} + +void sw_GetJoinFlags( SwPaM& rPam, bool& rJoinText, bool& rJoinPrev ) +{ + rJoinText = false; + rJoinPrev = false; + if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() ) + return; + + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + SwTextNode *pSttNd = pStt->GetNode().GetTextNode(); + if( !pSttNd ) + return; + + SwTextNode *pEndNd = pEnd->GetNode().GetTextNode(); + rJoinText = nullptr != pEndNd; + if( !rJoinText ) + return; + + bool bExchange = pStt == rPam.GetPoint(); + if( !pStt->GetContentIndex() && + pEndNd->GetText().getLength() != pEnd->GetContentIndex()) + bExchange = !bExchange; + if( bExchange ) + rPam.Exchange(); + rJoinPrev = rPam.GetPoint() == pStt; + OSL_ENSURE( !pStt->GetContentIndex() && + pEndNd->GetText().getLength() != pEnd->GetContentIndex() + ? (rPam.GetPoint()->GetNode() < rPam.GetMark()->GetNode()) + : (rPam.GetPoint()->GetNode() > rPam.GetMark()->GetNode()), + "sw_GetJoinFlags"); +} + +bool sw_JoinText( SwPaM& rPam, bool bJoinPrev ) +{ + SwNodeIndex aIdx( rPam.GetPoint()->GetNode() ); + SwTextNode *pTextNd = aIdx.GetNode().GetTextNode(); + SwNodeIndex aOldIdx( aIdx ); + SwTextNode *pOldTextNd = pTextNd; + + if( pTextNd && pTextNd->CanJoinNext( &aIdx ) ) + { + SwDoc& rDoc = 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(rDoc.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()) + { + if( SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_BREAK, false) ) + pTextNd->ResetAttr( RES_BREAK ); + if( pTextNd->HasSwAttrSet() && + SfxItemState::SET == pTextNd->GetpSwAttrSet()->GetItemState( RES_PAGEDESC, false ) ) + pTextNd->ResetAttr( RES_PAGEDESC ); + } + + /* The PointNode */ + if( pOldTextNd->HasSwAttrSet() ) + { + const SfxPoolItem* pItem; + SfxItemSet aSet( rDoc.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(rDoc, aOldIdx.GetIndex(), SAL_MAX_INT32); + + SwContentIndex aAlphaIdx(pTextNd); + pOldTextNd->CutText( pTextNd, aAlphaIdx, SwContentIndex(pOldTextNd), + pOldTextNd->Len() ); + SwPosition aAlphaPos( aIdx, aAlphaIdx ); + rDoc.CorrRel( rPam.GetPoint()->GetNode(), aAlphaPos, 0, true ); + + // move all Bookmarks/TOXMarks + if( !pContentStore->Empty() ) + pContentStore->Restore( rDoc, 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().GetContentNode() ) + rPam.GetBound() = aAlphaPos; + if( pOldTextNd == rPam.GetBound( false ).GetContentNode() ) + 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); + } + rDoc.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( rDoc.GetAttrPool(), aCharFormatSetRange ); + aTmpSet.Put( *pDelNd->GetpSwAttrSet() ); + pTextNd->SetAttr( aTmpSet ); + } + } + + rDoc.CorrRel( aIdx.GetNode(), *rPam.GetPoint(), 0, true ); + // #i100466# adjust given <rPam>, if it does not belong to the cursors + if ( pDelNd == rPam.GetBound().GetContentNode() ) + { + rPam.GetBound().Assign( *pTextNd ); + } + if( pDelNd == rPam.GetBound( false ).GetContentNode() ) + { + rPam.GetBound( false ).Assign( *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* const pSttPos = rPaM.Start(); + SwPosition* const pEndPos = rPaM.End(); + + std::unique_ptr<SwSpellArgs> pSpellArgs; + if (pConvArgs) + { + pConvArgs->SetStart(*pSttPos); + pConvArgs->SetEnd(*pEndPos); + } + else + pSpellArgs.reset(new SwSpellArgs( xSpeller, *pSttPos, *pEndPos, bGrammarCheck )); + + SwNodeOffset nCurrNd = pSttPos->GetNodeIndex(); + SwNodeOffset nEndNd = pEndPos->GetNodeIndex(); + + 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; + } + tools::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->pStartPos->GetNode() == pNd ? pSpellArgs->pStartPos->GetContentIndex() : 0; + // if grammar checking starts inside of a sentence the start position has to be adjusted + if( nBeginGrammarCheck ) + { + SwContentIndex aStartIndex( pNd->GetTextNode(), 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()->GetContentIndex(); + } + } + nEndGrammarCheck = (&pSpellArgs->pEndPos->GetNode() == pNd) + ? pSpellArgs->pEndPos->GetContentIndex() + : 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 + if( pSpellArgs ) + nSpellErrorPosition = pSpellArgs->pStartPos->GetContentIndex() > pSpellArgs->pEndPos->GetContentIndex() ? + pSpellArgs->pEndPos->GetContentIndex() : + pSpellArgs->pStartPos->GetContentIndex(); + if( nCurrNd != nEndNd ) + { + pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex()); + pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex()); + nCurrNd = nEndNd; + } + } + + 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]; + pSttPos->Assign(nCurrNd, pSttPos->GetContentIndex()); + pEndPos->Assign(nCurrNd, pEndPos->GetContentIndex()); + pSpellArgs->pStartPos->Assign(*pNd->GetTextNode(), aConversionMap.ConvertToModelPosition( rError.nErrorStart ).mnPos ); + pSpellArgs->pEndPos->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 +{ + SwNodeIndex m_aNodeIdx; + const SwNode *m_pStart; + const SwNode *m_pEnd; + sal_uInt16 *m_pPageCnt; + sal_uInt16 *m_pPageSt; + + 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& rNew ) { m_aNodeIdx.Assign(rNew); } + inline void SetRange( const SwNode *pNew ); + void NextNode() { ++m_aNodeIdx; } + 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_aNodeIdx(pPam->GetPoint()->GetNode()), + 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(); + + // Set start + m_pStart = pPoint->GetNode().GetTextNode(); + m_nPamStart = pPoint->GetContentIndex(); + + // Set End and Length + const SwPosition *pMark = pPam->GetMark(); + m_pEnd = pMark->GetNode().GetTextNode(); + m_nPamLen = pMark->GetContentIndex(); + if( pPoint->GetNode() == pMark->GetNode() ) + m_nPamLen = m_nPamLen - pPoint->GetContentIndex(); +} + +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 +{ + pPam->GetPoint()->Assign( m_aNodeIdx, m_nWordStart ); + pPam->GetMark()->Assign( m_aNodeIdx, m_nWordStart + m_nWordLen ); +} + +// Returns true if we can proceed. +static bool lcl_HyphenateNode( SwNode* pNd, void* pArgs ) +{ + // Hyphenate returns true if there is a hyphenation point and sets pPam + SwTextNode *pNode = pNd->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; + } + tools::Long nStat = nPageNr >= *pPageSt ? nPageNr - *pPageSt + 1 + : nPageNr + *pPageCnt - *pPageSt + 1; + ::SetProgressState( nStat, pNode->GetDoc().GetDocShell() ); + } + pHyphArgs->SetRange( pNd ); + if( pNode->Hyphenate( *pHyphArgs ) ) + { + pHyphArgs->SetNode( *pNd ); + 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()->GetNode(), 1 ); + GetNodes().ForEach( pPam->GetPoint()->GetNode(), aTmpIdx.GetNode(), + 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 + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + + const SwNodeOffset nSttNd = pStt->GetNodeIndex(); + const SwNodeOffset nEndNd = pEnd->GetNodeIndex(); + + const sal_Int32 nSttCnt = pStt->GetContentIndex(); + const sal_Int32 nEndCnt = pEnd->GetContentIndex(); + + const SwTextNode* pTNd = pStt->GetNode().GetTextNode(); + if( pStt == pEnd && pTNd ) // no region ? + { + // do nothing + return; + } + + if( nSttNd != nEndNd ) + { + SwNodeIndex aIdx( pStt->GetNode() ); + 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->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 0000000000..c723ee162e --- /dev/null +++ b/sw/source/core/doc/docfld.cxx @@ -0,0 +1,1230 @@ +/* -*- 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 <config_fuzzers.h> + +#include <hintids.hxx> + +#include <comphelper/string.hxx> +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> +#ifndef UNX +#include <unotools/transliterationwrapper.hxx> +#endif +#include <o3tl/string_view.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <node2lay.hxx> +#include <cntfrm.hxx> +#include <pagefrm.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 <utility> + +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 SwNode& rNdIdx, + const SwTextField* pField, + std::optional<sal_Int32> oContentIdx, + sal_uInt16 const nPageNumber) + : m_nPageNumber(nPageNumber) +{ + m_eSetGetExpFieldType = TEXTFIELD; + m_CNTNT.pTextField = pField; + m_nNode = rNdIdx.GetIndex(); + if( oContentIdx ) + m_nContent = *oContentIdx; + else if( pField ) + m_nContent = pField->GetStart(); + else + m_nContent = 0; +} + +SetGetExpField::SetGetExpField( const SwNode& 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, + sal_uInt16 const nPageNumber) + : m_nPageNumber(nPageNumber) +{ + m_eSetGetExpFieldType = SECTIONNODE; + m_CNTNT.pSection = &rSectNd.GetSection(); + + if( pPos ) + { + m_nNode = pPos->GetNodeIndex(); + m_nContent = pPos->GetContentIndex(); + } + else + { + m_nNode = rSectNd.GetIndex(); + m_nContent = 0; + } +} + +SetGetExpField::SetGetExpField(::sw::mark::IBookmark const& rBookmark, + SwPosition const*const pPos, + sal_uInt16 const nPageNumber) + : m_nPageNumber(nPageNumber) +{ + m_eSetGetExpFieldType = BOOKMARK; + m_CNTNT.pBookmark = &rBookmark; + + if (pPos) + { + m_nNode = pPos->GetNodeIndex(); + m_nContent = pPos->GetContentIndex(); + } + else + { + m_nNode = rBookmark.GetMarkStart().GetNodeIndex(); + m_nContent = rBookmark.GetMarkStart().GetContentIndex();; + } +} + +SetGetExpField::SetGetExpField( const SwTableBox& rTBox ) +{ + m_eSetGetExpFieldType = TABLEBOX; + m_CNTNT.pTBox = &rTBox; + + m_nNode = SwNodeOffset(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 SwNode& 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.GetNodeIndex(); + m_nContent = rPos.GetContentIndex(); +} + +SetGetExpField::SetGetExpField( const SwFlyFrameFormat& rFlyFormat, + const SwPosition* pPos ) +{ + m_eSetGetExpFieldType = FLYFRAME; + m_CNTNT.pFlyFormat = &rFlyFormat; + if( pPos ) + { + m_nNode = pPos->GetNodeIndex(); + m_nContent = pPos->GetContentIndex(); + } + 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.Assign( *static_cast<const SwContentNode*>(pNd), GetCntPosFromContent() ); + } + else + { + rPos.Assign( m_nNode, 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.GetNodeIndex(); + // tdf#106663 - use the starting position of the frame + m_nContent = 0; + } +} + +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_nPageNumber != rField.m_nPageNumber) + { + // sort "invalid" page nums of 0 after valid page nums of non 0 + if (m_nPageNumber == 0 || rField.m_nPageNumber == 0) + return m_nPageNumber != 0; + return m_nPageNumber < rField.m_nPageNumber; + } + 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(); + + pTableNd = pNext->FindTableNode(); + if( pTableNd ) + 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 BOOKMARK: + pRet = &m_CNTNT.pBookmark->GetMarkStart().GetNode(); + break; + + case CRSRPOS: + pRet = &m_CNTNT.pPos->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 BOOKMARK: + nRet = m_CNTNT.pBookmark->GetMarkStart().GetContentIndex(); + break; + case CRSRPOS: + nRet = m_CNTNT.pPos->GetContentIndex(); + break; + default: + break; + } + return nRet; +} + +/// Look up the Name, if it is present, return its String, otherwise return an empty String +OUString LookString( std::unordered_map<OUString, OUString> const & rTable, const OUString& rName ) +{ + auto it = rTable.find( comphelper::string::strip(rName, ' ') ); + if( it != rTable.end() ) + return it->second; + + return OUString(); +} + +SwDBData const & SwDoc::GetDBData() +{ +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + if(maDBData.sDataSource.isEmpty()) + { + // Similar to: SwEditShell::IsAnyDatabaseFieldInDoc + for (const auto& pFieldType : *getIDocumentFieldsAccess().GetFieldTypes()) + { + if (IsUsed(*pFieldType)) + { + SwFieldIds nWhich = pFieldType->Which(); + switch(nWhich) + { + case SwFieldIds::Database: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + { + std::vector<SwFormatField*> vFields; + pFieldType->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 || ENABLE_FUZZERS + (void) b; +#else + GetDBManager()->SetInitDBFields( b ); +#endif +} + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + +/// 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 || ENABLE_FUZZERS + (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 || ENABLE_FUZZERS + (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.subView( 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 || ENABLE_FUZZERS + (void) rDBNameList; + (void) rDBName; +#else + if( rDBName.isEmpty() ) + return; + +#ifdef UNX + for( const auto &sName : rDBNameList ) + if( rDBName == o3tl::getToken(sName, 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 || ENABLE_FUZZERS + (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 = o3tl::toInt32(o3tl::getToken(rNewName, 0, DB_DELIM, nIdx)); + + 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 && !ENABLE_FUZZERS + 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_at(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); + + // remember sections that were unhidden and need to be hidden again + std::vector<std::reference_wrapper<SwSection>> aUnhiddenSections; + + // 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<SwNodeOffset> aTmpArr; + std::vector<SwNodeOffset>::size_type nArrStt = 0; + SwSectionFormats& rArr = rDoc.GetSections(); + SwSectionNode* pSectNd = nullptr; + SwNodeOffset 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 ) + { + SwNodeOffset 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" ); + + auto& rSection = pSectNd->GetSection(); + // unhide and remember the conditionally hidden sections + if (rSection.IsHidden() && !rSection.GetCondition().isEmpty() && rSection.IsCondHidden()) + { + aUnhiddenSections.push_back(std::ref(rSection)); // remember to later hide again + rSection.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" ); + + auto& rSection = pSectNd->GetSection(); + // unhide and remember the conditionally hidden sections + if (rSection.IsHidden() && !rSection.GetCondition().isEmpty() && rSection.IsCondHidden()) + { + aUnhiddenSections.push_back(std::ref(rSection)); // remember to later hide again + rSection.SetCondHidden(false); + } + } + + // add all to the list so that they are sorted + for (const auto &nId : aTmpArr) + { + SwSectionNode const& rSectionNode(*rDoc.GetNodes()[ nId ]->GetSectionNode()); + GetBodyNodeGeneric(rSectionNode, rSectionNode); + } + + // bookmarks with hide conditions, handle similar to sections + auto const& rIDMA(*rDoc.getIDocumentMarkAccess()); + for (auto it = rIDMA.getBookmarksBegin(); it != rIDMA.getBookmarksEnd(); ++it) + { + auto const pBookmark(dynamic_cast<::sw::mark::IBookmark const*>(*it)); + assert(pBookmark); + if (!pBookmark->GetHideCondition().isEmpty()) + { + GetBodyNodeGeneric((*it)->GetMarkStart().GetNode(), *pBookmark); + } + } + } + + static constexpr OUString sTrue(u"TRUE"_ustr); + static constexpr OUString sFalse(u"FALSE"_ustr); + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + 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)->ForceUpdateTextNode(); + } + 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)->ForceUpdateTextNode(); + } + break; + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + 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(); + + // return the conditional hidden value back to the previous value + for (auto& rSectionWrapper : aUnhiddenSections) + { + auto& rSection = rSectionWrapper.get(); + rSection.SetCondHidden(true); + } +} + +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); + // need pos to get the frame on the correct page + SwPosition const pos(rTextNd, rTField.GetStart()); + const SwFrame* pFrame = rTextNd.getLayoutFrame( + rDoc.getIDocumentLayoutAccess().GetCurrentLayout(), &pos, &tmp); + + std::unique_ptr<SetGetExpField> pNew; + bool bIsInBody = false; + + if( !pFrame || pFrame->IsInDocBody() ) + { + bIsInBody = rDoc.GetNodes().GetEndOfExtras().GetIndex() < rTextNd.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) + { // try harder to get a frame for the page number + pFrame = ::sw::FindNeighbourFrameForNode(rTextNd); + // possibly there is no layout at all, happens in mail merge + } + if( (pFrame != nullptr) || bIsInBody ) + { + pNew.reset(new SetGetExpField(rTextNd, &rTField, std::nullopt, + pFrame ? pFrame->GetPhyPageNum() : 0)); + } + } + 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.GetNode(), &rTField, aPos.GetContentIndex(), + pFrame->GetPhyPageNum())); + } + + // 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 && !ENABLE_FUZZERS + 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) ); +} + +template<typename T> +void SwDocUpdateField::GetBodyNodeGeneric(SwNode const& rNode, T const& rCond) +{ + const SwDoc& rDoc = rNode.GetDoc(); + std::unique_ptr<SetGetExpField> pNew; + + if (rNode.GetIndex() < rDoc.GetNodes().GetEndOfExtras().GetIndex()) + { + do { // middle check loop + + // we need to get the anchor first + // create index to determine the TextNode + SwPosition aPos(rNode); + SwContentNode const*const pCNd = rNode.IsSectionNode() + ? rDoc.GetNodes().GoNext(&aPos.nNode) // to the next ContentNode + : rNode.GetContentNode(); + + 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(rCond, &aPos, pFrame->GetPhyPageNum())); + + } while( false ); + } + + if( !pNew ) + { + // try harder to get a frame for the page number + SwFrame const*const pFrame = ::sw::FindNeighbourFrameForNode(rNode); + pNew.reset(new SetGetExpField(rCond, nullptr, pFrame ? pFrame->GetPhyPageNum() : 0)); + } + + 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() ) + return; + + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + + auto it = m_FieldTypeTable.find( sFieldName ); + if( it == m_FieldTypeTable.end() ) + m_FieldTypeTable.insert( { sFieldName, &rType } ); +} + +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() ) + return; + + SetFieldsDirty( true ); + // look up and remove from the hash table + sFieldName = GetAppCharClass().lowercase( sFieldName ); + + GetFieldTypeTable().erase( sFieldName ); +} + +SwDocUpdateField::SwDocUpdateField(SwDoc& rDoc) + : 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 0000000000..c492212487 --- /dev/null +++ b/sw/source/core/doc/docfly.cxx @@ -0,0 +1,1165 @@ +/* -*- 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 <osl/diagnose.h> +#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 <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> +#include <formatflysplit.hxx> + +using namespace ::com::sun::star; + +size_t SwDoc::GetFlyCount( FlyCntType eType, bool bIgnoreTextBoxes ) const +{ + size_t nCount = 0; + const SwNodeIndex* pIdx; + + for(sw::SpzFrameFormat* pFlyFormat: *GetSpzFrameFormats()) + { + 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 ) +{ + SwFrameFormat* pRetFormat = nullptr; + const SwNodeIndex* pIdx; + size_t nCount = 0; + + for(sw::SpzFrameFormat* pFlyFormat: *GetSpzFrameFormats()) + { + 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) +{ + std::vector<SwFrameFormat const*> ret; + ret.reserve(GetSpzFrameFormats()->size()); + + for(sw::SpzFrameFormat* pFlyFormat: *GetSpzFrameFormats()) + { + 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.GetAnchorNode() ) + { + 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.GetAnchorNode() ) + { + const SwContentNode* pNd = rAnch.GetAnchorNode()->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.GetAnchorNode() ) + { + const SwFlyFrameFormat* pFormat = static_cast<SwFlyFrameFormat*>(rAnch.GetAnchorNode()-> + 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.GetAnchorNode() && (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 TextAttribute needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + SwNode *pAnchorNode = rOldAnch.GetAnchorNode(); + SwTextNode *pTextNode = pAnchorNode->GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = rOldAnch.GetAnchorContentOffset(); + 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 + 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. + { + SwNode *pAnchorNode = aNewAnch.GetAnchorNode(); + SwTextNode *pNd = pAnchorNode->GetTextNode(); + OSL_ENSURE( pNd, "Cursor does not point to TextNode." ); + + SwFormatFlyCnt aFormat( static_cast<SwFlyFrameFormat*>(&rFormat) ); + pNd->InsertItem( aFormat, aNewAnch.GetAnchorContentOffset(), 0 ); + } + + if( SfxItemState::SET != rSet.GetItemState( RES_VERT_ORIENT, false )) + { + 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 only the anchor type has changed (char -> para -> page) and the absolute position + // is unchanged even though there is a new relative orientation + // (likely because the old orientation was not valid for the new anchor type), + // then adjust the position to account for the moved anchor position. + const SwFormatHoriOrient* pHoriOrientItem = rSet.GetItemIfSet( RES_HORI_ORIENT, false ); + + SwFormatHoriOrient aOldH( rFormat.GetHoriOrient() ); + bool bPutOldH(false); + + if (text::HoriOrientation::NONE == aOldH.GetHoriOrient() && pHoriOrientItem + && text::HoriOrientation::NONE == pHoriOrientItem->GetHoriOrient() + && aOldH.GetPos() == pHoriOrientItem->GetPos()) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldH.GetPos(); + nPos += aOldAnchorPos.getX() - aNewAnchorPos.getX(); + + assert(aOldH.GetRelationOrient() != pHoriOrientItem->GetRelationOrient()); + aOldH.SetRelationOrient(pHoriOrientItem->GetRelationOrient()); + + aOldH.SetPos( nPos ); + bPutOldH = true; + } + if (nNew == RndStdIds::FLY_AT_PAGE) + { + sal_Int16 nRelOrient(pHoriOrientItem + ? pHoriOrientItem->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 ); + } + + const SwFormatVertOrient* pVertOrientItem = rSet.GetItemIfSet( RES_VERT_ORIENT, false ); + SwFormatVertOrient aOldV( rFormat.GetVertOrient() ); + + if (text::VertOrientation::NONE == aOldV.GetVertOrient() && pVertOrientItem + && text::VertOrientation::NONE == pVertOrientItem->GetVertOrient() + && aOldV.GetPos() == pVertOrientItem->GetPos()) + { + SwTwips nPos = (RndStdIds::FLY_AS_CHAR == nOld) ? 0 : aOldV.GetPos(); + nPos += aOldAnchorPos.getY() - aNewAnchorPos.getY(); + + assert(aOldV.GetRelationOrient() != pVertOrientItem->GetRelationOrient()); + aOldV.SetRelationOrient(pVertOrientItem->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 = pItem->StaticWhichCast(XATTR_FILLBITMAP).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEDASH: + { + pResult = pItem->StaticWhichCast(XATTR_LINEDASH).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINESTART: + { + pResult = pItem->StaticWhichCast(XATTR_LINESTART).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_LINEEND: + { + pResult = pItem->StaticWhichCast(XATTR_LINEEND).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLGRADIENT: + { + pResult = pItem->StaticWhichCast(XATTR_FILLGRADIENT).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLFLOATTRANSPARENCE: + { + pResult = pItem->StaticWhichCast(XATTR_FILLFLOATTRANSPARENCE).checkForUniqueItem(pDrawModel); + break; + } + case XATTR_FILLHATCH: + { + pResult = pItem->StaticWhichCast(XATTR_FILLHATCH).checkForUniqueItem(pDrawModel); + break; + } + } + + if(pResult) + { + rSet.Put(std::move(pResult)); + } + } +} + +bool SwDoc::SetFlyFrameAttr( SwFrameFormat& rFlyFormat, SfxItemSet& rSet ) +{ + if( !rSet.Count() ) + return false; + + SwDocModifyAndUndoGuard guard(rFlyFormat); + + bool const bRet = lcl_SetFlyFrameAttr(*this, &SwDoc::SetFlyFrameAnchor, rFlyFormat, rSet); + + //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(); +} + +void SwDoc::SetFlyFrameDecorative(SwFlyFrameFormat& rFlyFrameFormat, + bool const isDecorative) +{ + if (rFlyFrameFormat.GetAttrSet().Get(RES_DECORATIVE).GetValue() == isDecorative) + { + return; + } + + ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo()); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoFlyDecorative>(rFlyFrameFormat, isDecorative)); + } + + rFlyFrameFormat.SetObjDecorative(isDecorative); + + 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 + 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(); + const SwFormatAnchor* pFormatAnchor = pAsk->GetItemIfSet( RES_ANCHOR, false ); + if( pFormatAnchor + && pFormatAnchor->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( *pFormatAnchor ); + 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_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 + auto pSwDrawVirtObj = dynamic_cast<SwDrawVirtObj*>( pObj); + bool bNoUserCallExcepted = pSwDrawVirtObj && !pSwDrawVirtObj->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::optional<SwPosition> oOldAsCharAnchorPos; + const RndStdIds eOldAnchorType = pContact->GetAnchorId(); + if ( !_bSameOnly && eOldAnchorType == RndStdIds::FLY_AS_CHAR ) + { + oOldAsCharAnchorPos.emplace(*pContact->GetAnchorFormat().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.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().Contains( 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().Contains( 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.GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Cursor not positioned at TextNode." ); + + SwFormatFlyCnt aFormat( pContact->GetFormat() ); + pNd->InsertItem( aFormat, aPos.GetContentIndex(), 0 ); + + // Has a textbox attached to the format? Sync it as well! + if (pContact->GetFormat() && pContact->GetFormat()->GetOtherTextBoxFormats()) + { + SwTextBoxHelper::synchronizeGroupTextBoxProperty( + SwTextBoxHelper::changeAnchor, pContact->GetFormat(), pObj); + } + } + 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 + auto pSwDrawVirtObj = dynamic_cast<SwDrawVirtObj*>( pObj); + if ( pSwDrawVirtObj && !pSwDrawVirtObj->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 (oOldAsCharAnchorPos) + { + if ( pNewAnchorFrame) + { + // We need to handle InContents in a special way: + // The TextAttribute needs to be destroyed which, unfortunately, also + // destroys the format. To avoid that, we disconnect the format from + // the attribute. + const sal_Int32 nIndx( oOldAsCharAnchorPos->GetContentIndex() ); + SwTextNode* pTextNode( oOldAsCharAnchorPos->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; + + // Split flys are incompatible with chaining. + const SwFormatFlySplit& rOldSplit = rSource.GetFlySplit(); + if (rOldSplit.GetValue()) + { + return SwChainRet::SOURCE_CHAINED; + } + const SwFormatFlySplit& rNewSplit = rDest.GetFlySplit(); + if (rNewSplit.GetValue()) + { + 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 SwNodeOffset nFlySttNd = pCntIdx->GetIndex(); + if( SwNodeOffset(2) != ( pCntIdx->GetNode().EndOfSectionIndex() - nFlySttNd ) || + pTextNd->GetText().getLength() ) + { + return SwChainRet::NOT_EMPTY; + } + + for(sw::SpzFrameFormat* 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.GetAnchorNode() ) + continue; + SwNodeOffset nTstSttNd = rAnchor.GetAnchorNode()->GetIndex(); + if( nFlySttNd <= nTstSttNd && nTstSttNd < nFlySttNd + SwNodeOffset(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(); + SwNodeOffset nEndOfExtras = GetNodes().GetEndOfExtras().GetIndex(); + bool bAllowed = false; + if ( RndStdIds::FLY_AT_PAGE == rSrcAnchor.GetAnchorId() ) + { + if ( (RndStdIds::FLY_AT_PAGE == rDstAnchor.GetAnchorId()) || + ( rDstAnchor.GetAnchorNode() && + rDstAnchor.GetAnchorNode()->GetIndex() > nEndOfExtras )) + bAllowed = true; + } + else if( rSrcAnchor.GetAnchorNode() && rDstAnchor.GetAnchorNode() ) + { + const SwNode &rSrcNd = *rSrcAnchor.GetAnchorNode(), + &rDstNd = *rDstAnchor.GetAnchorNode(); + const SwStartNode* pSttNd = nullptr; + if( rSrcNd == rDstNd || + ( !pSttNd && + nullptr != ( pSttNd = rSrcNd.FindFlyStartNode() ) && + pSttNd == rDstNd.FindFlyStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcNd.FindFooterStartNode() ) && + pSttNd == rDstNd.FindFooterStartNode() ) || + ( !pSttNd && + nullptr != ( pSttNd = rSrcNd.FindHeaderStartNode() ) && + pSttNd == rDstNd.FindHeaderStartNode() ) || + ( !pSttNd && rDstNd.GetIndex() > nEndOfExtras && + rSrcNd.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 ); + + SfxItemSetFixed<RES_FRM_SIZE, RES_FRM_SIZE, + RES_CHAIN, RES_CHAIN> aSet( GetAttrPool() ); + + // 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 0000000000..57c42c529e --- /dev/null +++ b/sw/source/core/doc/docfmt.cxx @@ -0,0 +1,2097 @@ +/* -*- 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 <svl/numformat.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/rsiditem.hxx> +#include <editeng/colritem.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/diagnose.h> +#include <svl/zforlist.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.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 <IDocumentRedlineAccess.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 <textboxhelper.hxx> +#include <textcontentcontrol.hxx> +#include <memory> +#include <algorithm> +#include <functional> + +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( SwNode* pNd, void* pArgs ) +{ + const sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast<sw::DocumentContentOperationsManager::ParaRstFormat*>(pArgs); + SwContentNode* pNode = pNd->GetContentNode(); + if (pPara && pPara->pLayout && pPara->pLayout->HasMergedParas() + && pNode && pNode->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if( pNode && pNode->HasSwAttrSet() ) + { + const bool bLocked = pNode->IsModifyLocked(); + pNode->LockModify(); + + SwDoc& rDoc = pNode->GetDoc(); + + // remove unused attribute RES_LR_SPACE + // add list attributes, except RES_PARATR_LIST_AUTOFMT + SfxItemSetFixed< + RES_PARATR_NUMRULE, RES_PARATR_NUMRULE, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_AUTOFMT - 1, + RES_PAGEDESC, RES_BREAK, + RES_FRMATR_STYLE_NAME, RES_FRMATR_CONDITIONAL_STYLE_NAME> aSavedAttrsSet(rDoc.GetAttrPool()); + const SfxItemSet* pAttrSetOfNode = pNode->GetpSwAttrSet(); + + std::vector<sal_uInt16> aClearWhichIds; + // restoring all paragraph list attributes + { + SfxItemSetFixed<RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_AUTOFMT - 1> aListAttrSet( rDoc.GetAttrPool() ); + 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(); + } + } + } + + if (auto pItem = pAttrSetOfNode->GetItemIfSet(RES_PARATR_NUMRULE, false); + pItem && !pItem->GetValue().isEmpty()) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(RES_PARATR_NUMRULE); + } + if (auto pItem = pAttrSetOfNode->GetItemIfSet(RES_PAGEDESC, false); + pItem && pItem->GetPageDesc()) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(RES_PAGEDESC); + } + if (auto pItem = pAttrSetOfNode->GetItemIfSet(RES_BREAK, false); + pItem && pItem->GetBreak() != SvxBreak::NONE) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(RES_BREAK); + } + if (auto pItem = pAttrSetOfNode->GetItemIfSet(RES_FRMATR_STYLE_NAME, false); + pItem && !pItem->GetValue().isEmpty()) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(RES_FRMATR_STYLE_NAME); + } + if (auto pItem = pAttrSetOfNode->GetItemIfSet(RES_FRMATR_CONDITIONAL_STYLE_NAME, false); + pItem && !pItem->GetValue().isEmpty()) + { + aSavedAttrsSet.Put(*pItem); + aClearWhichIds.push_back(RES_FRMATR_CONDITIONAL_STYLE_NAME); + } + + // 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 (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if ( ( pItem->Which() != RES_PAGEDESC && + pItem->Which() != RES_BREAK && + pItem->Which() != RES_FRMATR_STYLE_NAME && + pItem->Which() != RES_FRMATR_CONDITIONAL_STYLE_NAME && + 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)); + } + auto [pStt, pEnd] = rRg.StartEnd(); // SwPosition* + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout ); + aPara.bInclRefToxMark = bInclRefToxMark; + aPara.bExactRange = bExactRange; + GetNodes().ForEach( pStt->GetNodeIndex(), pEnd->GetNodeIndex()+1, + sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrs( const SwPaM &rRg, + bool bTextAttr, + const o3tl::sorted_vector<sal_uInt16> &rAttrs, + const bool bSendDataChangedEvents, + SwRootFrame const*const pLayout) +{ + SwPaM* pPam = const_cast<SwPaM*>(&rRg); + std::optional<SwPaM> oExtraPaM; + if( !bTextAttr && !rAttrs.empty() && RES_TXTATR_END > *(rAttrs.begin()) ) + bTextAttr = true; + + if( !rRg.HasMark() ) + { + SwTextNode* pTextNd = rRg.GetPoint()->GetNode().GetTextNode(); + if( !pTextNd ) + return ; + + oExtraPaM.emplace( *rRg.GetPoint() ); + pPam = &*oExtraPaM; + + SwPosition& rSt = *pPam->GetPoint(); + sal_Int32 nMkPos, nPtPos = rSt.GetContentIndex(); + + // Special case: if the Cursor is located within a URL attribute, we take over it's area + SwTextAttr const*const pURLAttr( + pTextNd->GetTextAttrAt(rSt.GetContentIndex(), 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.GetContentIndex(); + if( bTextAttr ) + pTextNd->DontExpandFormat( nPtPos ); + } + } + + rSt.SetContent(nMkPos); + pPam->SetMark(); + pPam->GetPoint()->SetContent(nPtPos); + } + + // #i96644# + std::optional< SwDataChanged > oDataChanged; + if ( bSendDataChangedEvents ) + { + oDataChanged.emplace( *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( o3tl::sorted_vector(rAttrs) ); + } + pHst = &pUndo->GetHistory(); + GetIDocumentUndoRedo().AppendUndo(std::move(pUndo)); + } + + auto [pStt, pEnd] = pPam->StartEnd(); // SwPosition* + sw::DocumentContentOperationsManager::ParaRstFormat aPara( + pStt, pEnd, pHst, nullptr, pLayout); + + // mst: not including META here; it seems attrs with CH_TXTATR are omitted + SfxItemSetFixed<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> + aDelSet(GetAttrPool()); + for( auto 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->GetNode() ); + SwNodeIndex aTmpEnd( pEnd->GetNode() ); + if( pStt->GetContentIndex() ) // 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->GetContentIndex() == pEnd->GetNode().GetContentNode()->Len() ) + { + // set up a later, and all CharFormatAttr -> TextFormatAttr + ++aTmpEnd; + bAdd = false; + } + else if( pStt->GetNode() != pEnd->GetNode() || !pStt->GetContentIndex() ) + { + 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->GetNode(), aTmpEnd.GetNode(), lcl_RstAttr, &aPara ); + else if( !rRg.HasMark() ) + { + aPara.bResetAll = false ; + ::lcl_RstAttr( &pStt->GetNode(), &aPara ); + aPara.bResetAll = true ; + } + + if( bTextAttr ) + { + if( bAdd ) + ++aTmpEnd; + GetNodes().ForEach( pStt->GetNode(), aTmpEnd.GetNode(), sw::DocumentContentOperationsManager::lcl_RstTextAttr, &aPara ); + } + + getIDocumentState().SetModified(); + + oDataChanged.reset(); //before 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()->GetNode().GetTextNode(); + if (!pTextNode) + { + return; + } + const sal_Int32 nStart(rRg.GetPoint()->GetContentIndex() - nLen); + SvxRsidItem aRsid( mnRsid, RES_CHRATR_RSID ); + + SfxItemSetFixed<RES_CHRATR_RSID, RES_CHRATR_RSID> aSet(GetAttrPool()); + aSet.Put(aRsid); + bool const bRet(pTextNode->SetAttr(aSet, nStart, + rRg.GetPoint()->GetContentIndex())); + + 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 ); + } + + // If the format is a shape, and it has a textbox, sync. + auto pShapeFormat = dynamic_cast<SwFrameFormat*>(&rFormat); + if (pShapeFormat && SwTextBoxHelper::isTextBox(pShapeFormat, RES_DRAWFRMFMT)) + { + if (auto pObj = pShapeFormat->FindRealSdrObject()) + { + SwTextBoxHelper::syncFlyFrameAttr(*pShapeFormat, rSet, pObj); + SwTextBoxHelper::changeAnchor(pShapeFormat, pObj); + } + } + + getIDocumentState().SetModified(); +} + +void SwDoc::ResetAttrAtFormat( const std::vector<sal_uInt16>& rIds, + SwFormat& rChangedFormat ) +{ + std::unique_ptr<SwUndo> pUndo; + if (GetIDocumentUndoRedo().DoesUndo()) + pUndo.reset(new SwUndoFormatResetAttr( rChangedFormat, rIds )); + + bool bAttrReset = false; + for (const auto& nWhichId : rIds) + bAttrReset = rChangedFormat.ResetFormatAttr(nWhichId) || bAttrReset; + + 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; + + sw::BroadcastingModify 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 SvxTabStopItem* pTmpItem = aNew.GetItemIfSet( RES_PARATR_TABSTOP, false ); + if( pTmpItem && 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 = (*pTmpItem)[ 0 ].GetTabPos(), + nOldWidth = aOld.Get(RES_PARATR_TABSTOP)[ 0 ].GetTabPos(); + + bool bChg = false; + for (const SfxPoolItem* pItem2 : GetAttrPool().GetItemSurrogates(RES_PARATR_TABSTOP)) + { + if(auto pTabStopItem = pItem2->DynamicWhichCast(RES_PARATR_TABSTOP)) + 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.CallSwClientNotify(sw::LegacyModifyHint( &aChgFormat, &aChgFormat )); + } + } + } + + if( aNew.Count() && aCallMod.HasWriterListeners() ) + { + SwAttrSetChg aChgOld( aOld, aOld ); + SwAttrSetChg aChgNew( aNew, aNew ); + aCallMod.CallSwClientNotify(sw::LegacyModifyHint( &aChgOld, &aChgNew )); // all changed are sent + } + + // remove the default formats from the object again + SwIterator<SwClient, sw::BroadcastingModify> 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 + { + auto pSpz = static_cast<sw::SpzFrameFormat*>(pFormat); + if(GetSpzFrameFormats()->ContainsFormat(pSpz)) + { + GetSpzFrameFormats()->erase(pSpz); + delete pSpz; + } + else + SAL_WARN("sw", "FrameFormat not found."); + } + } +} + +void SwDoc::DelTableFrameFormat( SwTableFormat *pFormat ) +{ + auto 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*>(mpFrameFormatTable->FindFormatByName(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(); + return std::count_if(mpTableFrameFormatTable->begin(), mpTableFrameFormatTable->end(), + std::mem_fn(&SwFormat::IsUsed)); +} + +SwTableFormat& SwDoc::GetTableFrameFormat(size_t nFormat, bool bUsed) const +{ + if (!bUsed) + return *const_cast<SwTableFormat*>((*mpTableFrameFormatTable)[nFormat]); + for(SwTableFormat* pFormat: *mpTableFrameFormatTable) + { + if(!pFormat->IsUsed()) + continue; + if(nFormat) + --nFormat; + else + return *pFormat; + } + 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 pFrameFormat; +} + +SwCharFormat *SwDoc::MakeCharFormat( const OUString &rFormatName, + SwCharFormat *pDerivedFrom, + bool bBroadcast ) +{ + SwCharFormat *pFormat = new SwCharFormat( GetAttrPool(), rFormatName, pDerivedFrom ); + mpCharFormatTable->insert( 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 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 pTextFormatColl; +} + +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; +} + +// 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( SwNode* pNode, void* pArgs ) +{ + SwContentNode* pCNd = pNode->GetTextNode(); + + if( pCNd == nullptr) + return true; + + sw::DocumentContentOperationsManager::ParaRstFormat* pPara = static_cast<sw::DocumentContentOperationsManager::ParaRstFormat*>(pArgs); + + if (pPara->pLayout && pPara->pLayout->HasMergedParas()) + { + if (pCNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + return true; + } + if (pCNd->IsTextNode()) + { + pCNd = sw::GetParaPropsNode(*pPara->pLayout, *pCNd); + } + } + + SwTextFormatColl* pFormat = 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(*pCNd->GetTextNode()); + { + 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 ); + } + else + { + // The List Level must be applied as direct formatting. The spec says: + // 19.495 The style:list-level attribute specifies the list level value + // of a list style that may be applied to any paragraph style. + // It does not directly specify the paragraph's list level value, + // but consumers can change the paragraph's list level value to the specified value + // when the paragraph style is applied. + pCNd->SetAttr(pFormat->GetFormatAttr(RES_PARATR_LIST_LEVEL)); + } + } + } + + // add to History so that old data is saved, if necessary + if( pPara->pHistory ) + pPara->pHistory->AddColl(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 ); + auto [pStt, pEnd] = rRg.StartEnd(); // SwPosition* + 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->GetNodeIndex(), pEnd->GetNodeIndex()+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()) ); + + 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 + 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 SwNumRuleItem* pItem = pNewColl->GetItemIfSet( RES_PARATR_NUMRULE, + false ); + if( pItem ) + { + const OUString& rName = 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 = mpGrfFormatCollTable->FindFormatByName( 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 == rDestArr.FindFormatByName( 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 = rDestArr.FindFormatByName( pSrc->GetName() ); + pDest->SetAuto(false); + pDest->DelDiffs( *pSrc ); + + // #i94285#: existing <SwFormatPageDesc> instance, before copying attributes + const SwFormatPageDesc* pItem; + if( &GetAttrPool() != pSrc->GetAttrSet().GetPool() + && (pItem = pSrc->GetAttrSet().GetItemIfSet( RES_PAGEDESC, false )) + && pItem->GetPageDesc() ) + { + SwFormatPageDesc aPageDesc( *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( rDestArr.FindFormatByName( + 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*>(rDestArr.FindFormatByName( pSrcColl->GetNextTextFormatColl().GetName() )) ); + + if(pSrcColl->IsAssignedToListLevelOfOutlineStyle()) + pDstColl->AssignToListLevelOfOutlineStyle(pSrcColl->GetAssignedOutlineStyleLevel()); + + if( RES_CONDTXTFMTCOLL == pSrc->Which() ) + { + if (pDstColl->Which() != RES_CONDTXTFMTCOLL) + { + // Target already had a style with a matching name, but it's not a conditional + // style, then don't copy the conditions. + continue; + } + + // Copy the conditions, but delete the old ones first! + static_cast<SwConditionTextFormatColl*>(pDstColl)->SetConditions( + static_cast<SwConditionTextFormatColl*>(pSrc)->GetCondColls() ); + } + } + } +} + +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 = pNewItem->StaticWhichCast(RES_HEADER).GetHeaderFormat(); + else + pOldFormat = pNewItem->StaticWhichCast(RES_FOOTER).GetFooterFormat(); + + if( !pOldFormat ) + return; + + SwFrameFormat* pNewFormat = new SwFrameFormat( GetAttrPool(), "CpyDesc", + GetDfltFrameFormat() ); + pNewFormat->CopyAttrs( *pOldFormat ); + + if( const SwFormatContent* pContent = pNewFormat->GetAttrSet().GetItemIfSet( + RES_CNTNT, false ) ) + { + if( pContent->GetContentIdx() ) + { + const SwNodes& rSrcNds = rSrcFormat.GetDoc()->GetNodes(); + SwStartNode* pSttNd = SwNodes::MakeEmptySection( GetNodes().GetEndOfAutotext(), + bCpyHeader + ? SwHeaderStartNode + : SwFooterStartNode ); + const SwNode& rCSttNd = pContent->GetContentIdx()->GetNode(); + SwNodeRange aRg( rCSttNd, SwNodeOffset(0), *rCSttNd.EndOfSectionNode() ); + rSrcNds.Copy_( aRg, *pSttNd->EndOfSectionNode() ); + rSrcFormat.GetDoc()->GetDocumentContentOperationsManager().CopyFlyInFlyImpl(aRg, nullptr, *pSttNd); + // TODO: investigate calling CopyWithFlyInFly? + SwPaM const source(aRg.aStart, aRg.aEnd); + SwPosition dest(*pSttNd); + sw::CopyBookmarks(source, dest); + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + } + else + pNewFormat->ResetFormatAttr( RES_CNTNT ); + } + if( bCpyHeader ) + pNewItem->StaticWhichCast(RES_HEADER).RegisterToFormat(*pNewFormat); + else + pNewItem->StaticWhichCast(RES_FOOTER).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); + } + + // Copy the stashed formats as well between the page descriptors... + for (bool bFirst : { true, false }) + { + for (bool bLeft : { true, false }) + { + for (bool bHeader : { true, false }) + { + if (!bLeft && !bFirst) + continue; + + // Copy format only if it exists + if (auto pStashedFormatSrc = rSrcDesc.GetStashedFrameFormat(bHeader, bLeft, bFirst)) + { + if (pStashedFormatSrc->GetDoc() != this) + { + SwFrameFormat* pNewFormat = new SwFrameFormat(GetAttrPool(), "CopyDesc", GetDfltFrameFormat()); + + SfxItemSet aAttrSet(pStashedFormatSrc->GetAttrSet()); + aAttrSet.ClearItem(RES_HEADER); + aAttrSet.ClearItem(RES_FOOTER); + + pNewFormat->DelDiffs( aAttrSet ); + pNewFormat->SetFormatAttr( aAttrSet ); + + if (bHeader) + CopyHeader(*pStashedFormatSrc, *pNewFormat); + else + CopyFooter(*pStashedFormatSrc, *pNewFormat); + + rDstDesc.StashFrameFormat(*pNewFormat, bHeader, bLeft, bFirst); + } + else + { + rDstDesc.StashFrameFormat(*pStashedFormatSrc, bHeader, bLeft, bFirst); + } + } + } + } + } +} + +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 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 + // b) 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(); +} + +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.GetNode() ); + while( aIdx <= rEnd.GetNode() ) + { + SwTextNode* pTNd = aIdx.GetNode().GetTextNode(); + if( pTNd ) + { + pTNd = sw::GetParaPropsNode(*pLayout, aIdx.GetNode()); + SvxFirstLineIndentItem firstLine(pTNd->SwContentNode::GetAttr(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem leftMargin(pTNd->SwContentNode::GetAttr(RES_MARGIN_TEXTLEFT)); + + // #i93873# See also lcl_MergeListLevelIndentAsLRSpaceItem in thints.cxx + ::sw::ListLevelIndents const indents(pTNd->AreListLevelIndentsApplicable()); + if (indents != ::sw::ListLevelIndents::No) + { + const SwNumRule* pRule = pTNd->GetNumRule(); + if ( pRule ) + { + const int nListLevel = pTNd->GetActualListLevel(); + if ( nListLevel >= 0 ) + { + const SwNumFormat& rFormat = pRule->Get(o3tl::narrowing<sal_uInt16>(nListLevel)); + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) + { + if (indents & ::sw::ListLevelIndents::LeftMargin) + { + leftMargin.SetTextLeft(rFormat.GetIndentAt()); + } + if (indents & ::sw::ListLevelIndents::FirstLine) + { + firstLine.SetTextFirstLineOffset(static_cast<short>(rFormat.GetFirstLineIndent())); + } + } + } + } + } + + tools::Long nNext = leftMargin.GetTextLeft(); + if( bModulus ) + nNext = ( nNext / nDefDist ) * nDefDist; + + if( bRight ) + nNext += nDefDist; + else + if(nNext >0) // fdo#75936 set limit for decreasing indent + nNext -= nDefDist; + + leftMargin.SetTextLeft( nNext ); + + SwRegHistory aRegH( pTNd, *pTNd, pHistory ); + pTNd->SetAttr(firstLine); + pTNd->SetAttr(leftMargin); + aIdx = *sw::GetFirstAndLastNode(*pLayout, aIdx.GetNode()).second; + } + ++aIdx; + } + getIDocumentState().SetModified(); +} + +bool SwDoc::DontExpandFormat( const SwPosition& rPos, bool bFlag ) +{ + bool bRet = false; + SwTextNode* pTextNd = rPos.GetNode().GetTextNode(); + if( pTextNd ) + { + bRet = pTextNd->DontExpandFormat( rPos.GetContentIndex(), 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->SetFormatName("TableBox" + OUString::number(reinterpret_cast<sal_IntPtr>(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +SwTableLineFormat* SwDoc::MakeTableLineFormat() +{ + SwTableLineFormat* pFormat = new SwTableLineFormat( GetAttrPool(), mpDfltFrameFormat.get() ); + pFormat->SetFormatName("TableLine" + OUString::number(reinterpret_cast<sal_IntPtr>(pFormat))); + getIDocumentState().SetModified(); + return pFormat; +} + +void SwDoc::EnsureNumberFormatter() +{ + if (mpNumberFormatter == nullptr) + { + LanguageType eLang = LANGUAGE_SYSTEM; + mpNumberFormatter = new SvNumberFormatter(comphelper::getProcessComponentContext(), eLang); + mpNumberFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL ); + if (!utl::ConfigManager::IsFuzzing()) + mpNumberFormatter->SetYear2000( + officecfg::Office::Common::DateFormat::TwoDigitYear::get()); + }; +} + +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.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()->SetContent(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()->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()->GetContentIndex()); + std::vector<WhichPair> whichIds; + SfxItemIter iter(rSet); + for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem()) + { + whichIds.push_back({pItem->Which(), pItem->Which()}); + } + SfxItemSet currentSet(GetAttrPool(), WhichRangesContainer(whichIds.data(), whichIds.size())); + pTNd->GetParaAttr(currentSet, nEnd, nEnd); + for (const WhichPair& rPair : whichIds) + { // yuk - want to explicitly set the pool defaults too :-/ + currentSet.Put(currentSet.Get(rPair.first)); + } + + 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>(std::move(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)); + } + } + + // name change means the o3tl::sorted_array is not property sorted + if (rFormat.Which() == RES_CHRFMT) + mpCharFormatTable->SetFormatNameAndReindex(static_cast<SwCharFormat*>(&rFormat), sNewName); + else + rFormat.SetFormatName(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); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwDoc")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + m_pNodes->dumpAsXml(pWriter); + m_PageDescs.dumpAsXml(pWriter); + maDBData.dumpAsXml(pWriter); + mpMarkManager->dumpAsXml(pWriter); + m_pContentControlManager->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); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbModified")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(getIDocumentState().IsModified()).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +void SwDBData::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwDBData")); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sDataSource"), BAD_CAST(sDataSource.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("sCommand"), BAD_CAST(sCommand.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nCommandType"), BAD_CAST(OString::number(nCommandType).getStr())); + + (void)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; + } +} +/* 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 0000000000..16f6694c94 --- /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 <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( o3tl::narrowing<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( o3tl::narrowing<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( + o3tl::narrowing<sal_uInt16>(m_bEndNote + ? RES_POOLCHR_ENDNOTE + : RES_POOLCHR_FOOTNOTE), + pFormat, + *this); +} + +SwCharFormat* SwEndNoteInfo::GetAnchorCharFormat(SwDoc& rDoc) const +{ + auto pAnchorFormatFromDoc = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( o3tl::narrowing<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( + o3tl::narrowing<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::UpdateFormatOrAttr() +{ + auto pFormat = GetCurrentCharFormat(m_pCharFormat == nullptr); + if (!pFormat || !m_aDepends.IsListeningTo(pFormat) || pFormat->IsFormatInDTOR()) + return; + SwDoc* pDoc = pFormat->GetDoc(); + SwFootnoteIdxs& rFootnoteIdxs = pDoc->GetFootnoteIdxs(); + for(auto pTextFootnote : rFootnoteIdxs) + { + const SwFormatFootnote &rFootnote = pTextFootnote->GetFootnote(); + if(rFootnote.IsEndNote() == m_bEndNote) + pTextFootnote->SetNumber(rFootnote.GetNumber(), rFootnote.GetNumberRLHidden(), rFootnote.GetNumStr()); + } +} + + +void SwEndNoteInfo::SwClientNotify( const SwModify& rModify, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + switch(pLegacyHint->GetWhich()) + { + case RES_ATTRSET_CHG: + case RES_FMT_CHG: + UpdateFormatOrAttr(); + break; + default: + CheckRegistration( pLegacyHint->m_pOld ); + } + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + auto pNew = const_cast<sw::BroadcastingModify*>(static_cast<const sw::BroadcastingModify*>(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() : + 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 ) + { + mpFootnoteInfo->UpdateFormatOrAttr(); + } + + // #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 ) + { + mpEndNoteInfo->UpdateFormatOrAttr(); + } + + // #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(); + + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + const SwNodeOffset nSttNd = pStt->GetNodeIndex(); + const sal_Int32 nSttCnt = pStt->GetContentIndex(); + const SwNodeOffset nEndNd = pEnd->GetNodeIndex(); + const sal_Int32 nEndCnt = pEnd->GetContentIndex(); + + size_t nPos = 0; + rFootnoteArr.SeekEntry( pStt->GetNode(), &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++ ]; + SwNodeOffset 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().AddFootnote(*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 ]; + SwNodeOffset 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().AddFootnote(*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 0000000000..56f78b66cb --- /dev/null +++ b/sw/source/core/doc/docglbl.cxx @@ -0,0 +1,515 @@ +/* -*- 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 SwNode* GetStartNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNode* pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel && !pNd->FindTableNode() ) + { + return pNd; + } + } + + return nullptr; +} + +static SwNode* GetEndNode( SwOutlineNodes const * pOutlNds, int nOutlineLevel, SwOutlineNodes::size_type* nOutl ) +{ + SwNode* 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 SwNode* GetStartNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + for( ; *nOutl < pOutlNds->size(); ++(*nOutl) ) + { + SwNode* pNd = (*pOutlNds)[ *nOutl ]; + if( pNd->GetTextNode()->GetTextColl() == pSplitColl && + !pNd->FindTableNode() ) + { + return pNd; + } + } + return nullptr; +} + +static SwNode* GetEndNode( const SwOutlineNodes* pOutlNds, const SwTextFormatColl* pSplitColl, SwOutlineNodes::size_type* nOutl ) +{ + SwNode* 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; + SwNode* 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(u"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::TempFileNamed 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 ) + { + SwNode* 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, SwNodeOffset(0), aEndIdx.GetNode() ); + GetDocumentContentOperationsManager().CopyWithFlyInFly( + aRg, pDoc->GetNodes().GetEndOfContent(), nullptr, false, false); + + // Delete the initial TextNode + SwNodeIndex aIdx( pDoc->GetNodes().GetEndOfExtras(), 2 ); + if( aIdx.GetIndex() + 1 != + pDoc->GetNodes().GetEndOfContent().GetIndex() ) + pDoc->GetNodes().Delete( aIdx ); + + sFileName = utl::CreateTempURL(sLeading, true, sExt, &sPath); + 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->GetErrorIgnoreWarning() ) + 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. + SwNodeOffset nNodeDiff = aEndIdx.GetIndex() - + pStartNd->GetIndex() - 1; + if( nNodeDiff ) + { + SwPaM aTmp( *pStartNd, aEndIdx.GetNode(), SwNodeOffset(1), SwNodeOffset(-1) ); + SwNodeIndex aSIdx( aTmp.GetMark()->GetNode() ); + SwNodeIndex aEIdx( aTmp.GetPoint()->GetNode() ); + + // 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 + auto& rSpzs = *GetSpzFrameFormats(); + for(sw::FrameFormats<sw::SpzFrameFormat*>::size_type n = 0; n < GetSpzFrameFormats()->size(); ++n) + { + auto pFly = rSpzs[n]; + const SwFormatAnchor* pAnchor = &pFly->GetAnchor(); + SwNode const*const pAnchorNode = + pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aSIdx <= *pAnchorNode && + *pAnchorNode < aEIdx.GetNode() ) + { + 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.GetNode() ); + } + 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, SwNodeOffset(1), aEndIdx.GetNode(), SwNodeOffset(1) ); + GetNodes().MoveNodes( aRg, GetNodes(), *pSectNd ); + } + + pSectNd = pStartNd->FindSectionNode(); + } + + // -> #i26762# + // Ensure order of start and end of section is sane. + SwNodeIndex aStartIdx(*pStartNd); + + if (aEndIdx >= aStartIdx) + { + pSectNd = GetNodes().InsertTextSection(aStartIdx.GetNode(), + *pFormat, aSectData, nullptr, &aEndIdx.GetNode(), false); + } + else + { + pSectNd = GetNodes().InsertTextSection(aEndIdx.GetNode(), + *pFormat, aSectData, nullptr, &aStartIdx.GetNode(), 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 SfxPoolItemHolder& rResult(mpDocShell->ExecuteSlot(aReq)); + const SfxBoolItem *pRet(static_cast<const SfxBoolItem*>(rResult.getItem())); + + 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 0000000000..2993a774ad --- /dev/null +++ b/sw/source/core/doc/docglos.cxx @@ -0,0 +1,219 @@ +/* -*- 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(); + + // tdf#53023 - remove the last empty paragraph (check SwXMLTextBlockParContext dtor) + if (mbInsOnlyTextGlssry) + { + SwPaM aPaM(*pGDoc->GetNodes()[pGDoc->GetNodes().GetEndOfContent().GetIndex() - 1]); + pGDoc->getIDocumentContentOperations().DelFullPara(aPaM); + } + + // 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()->Assign( pGDoc->GetNodes().GetEndOfContent().GetIndex()-SwNodeOffset(1) ); + pContentNd = aCpyPam.GetPointContentNode(); + if (pContentNd) + aCpyPam.GetPoint()->SetContent( pContentNd->Len() ); + + GetIDocumentUndoRedo().StartUndo( SwUndoId::INSGLOSSARY, nullptr ); + SwPaM *_pStartCursor = &rPaM, *_pStartCursor2 = _pStartCursor; + do { + + SwPosition& rInsPos = *_pStartCursor->GetPoint(); + SwStartNode* pBoxSttNd = const_cast<SwStartNode*>(rInsPos.GetNode(). + FindTableBoxStartNode()); + + if( pBoxSttNd && SwNodeOffset(2) == pBoxSttNd->EndOfSectionIndex() - + pBoxSttNd->GetIndex() && + aCpyPam.GetPoint()->GetNode() != aCpyPam.GetMark()->GetNode() ) + { + // We copy more than one Node to the current Box. + // However, we have to remove the BoxAttributes then. + ClearBoxNumAttrs( rInsPos.GetNode() ); + } + + 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 0000000000..2ecea30dc3 --- /dev/null +++ b/sw/source/core/doc/doclay.cxx @@ -0,0 +1,1703 @@ +/* -*- 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 <osl/diagnose.h> +#include <svx/svdouno.hxx> +#include <editeng/frmdiritem.hxx> +#include <istype.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 <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 <comphelper/string.hxx> +#include <o3tl/string_view.hxx> + +#include <sortedobjs.hxx> + +#include <string_view> +#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; +} + +rtl::Reference<SdrObject> SwDoc::CloneSdrObj( const SdrObject& rObj, bool bMoveWithinDoc, + bool bInsInPage ) +{ + // #i52858# - method name changed + SdrPage *pPg = getIDocumentDrawModelAccess().GetOrCreateDrawModel()->GetPage( 0 ); + if( !pPg ) + { + auto pNewPage = getIDocumentDrawModelAccess().GetDrawModel()->AllocPage( false ); + getIDocumentDrawModelAccess().GetDrawModel()->InsertPage( pNewPage.get() ); + pPg = pNewPage.get(); + } + + // TTTT Clone directly to target SdrModel + rtl::Reference<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.get())->GetUnoControlModel(); + uno::Any aVal; + uno::Reference< beans::XPropertySet > xSet(xModel, uno::UNO_QUERY); + static constexpr OUString sName(u"Name"_ustr); + if( xSet.is() ) + aVal = xSet->getPropertyValue( sName ); + if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj.get() ); + if( xSet.is() ) + xSet->setPropertyValue( sName, aVal ); + } + else if( bInsInPage ) + pPg->InsertObjectThenMakeNameUnique( pObj.get() ); + + // For drawing objects: set layer of cloned object to invisible layer + SdrLayerID nLayerIdForClone = rObj.GetLayer(); + if ( dynamic_cast<const SwFlyDrawObj*>( pObj.get() ) == nullptr && + dynamic_cast<const SwVirtFlyDrawObj*>( pObj.get() ) == nullptr && + pObj->GetObjIdentifier() != SdrObjKind::NewFrame ) + { + 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; + 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(), SwNodeOffset(-1), + GetNodes().GetEndOfAutotext() ); + GetNodes().SectionDown( &aRange, SwFlyStartNode ); + + pFormat->SetFormatAttr( SwFormatContent( rNode.StartOfSectionNode() )); + + const SwFormatAnchor* pAnchor = nullptr; + if( pFlySet ) + { + pAnchor = pFlySet->GetItemIfSet( RES_ANCHOR, false ); + 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->GetAnchorNode() ) || + ( RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId() && + !pAnchor->GetAnchorNode() && + pAnchor->GetPageNum() == 0 ) ) + { + // set it again, needed for Undo + SwFormatAnchor aAnch( pFormat->GetAnchor() ); + if (pAnchor && (RndStdIds::FLY_AT_FLY == pAnchor->GetAnchorId())) + { + SwPosition aPos( *rAnchPos.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.GetContentIndex(); + SwTextNode * pTextNode = rAnchPos.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()) + { + SwNodeOffset nNodeIdx = rAnchPos.GetNodeIndex(); + const sal_Int32 nCntIdx = rAnchPos.GetContentIndex(); + 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 && (pAnch = pFlySet->GetItemIfSet( RES_ANCHOR, false ))) || + ( pFrameFormat && (pAnch = pFrameFormat->GetItemIfSet(RES_ANCHOR)) ) ) + { + if ( RndStdIds::FLY_AT_PAGE != pAnch->GetAnchorId() ) + { + pAnchorPos = pAnch->GetContentAnchor(); + } + } + } + + if (pAnchorPos) + { + if( !pFrameFormat ) + pFrameFormat = getIDocumentStylePoolAccess().GetFrameFormatFromPool( RES_POOLFRM_FRAME ); + + sal_uInt16 nCollId = o3tl::narrowing<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 + ( GetNodes().GetEndOfAutotext(), + getIDocumentStylePoolAccess().GetTextCollFromPool( nCollId )); + SwContentNode * pAnchorNode = pAnchorPos->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 ); + + // Attention: Do not create an index on the stack, or we + // cannot delete ContentNode in the end! + std::optional<SwPosition> oPos( std::in_place, aIndex ); + + 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, SwNodeOffset(0), *pTableNd->EndOfSectionNode(), SwNodeOffset(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.GetNode(), + GetDfltTextFormatColl() ); + + // Create undo actions if undo is enabled. + getIDocumentContentOperations().MoveNodeRange( aRg, oPos->GetNode(), SwMoveFlags::CREATEUNDOOBJ ); + } + else + { + rTable.MakeCopy(*this, *oPos, *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" ); + oPos.reset(); // Deregister index! + // Delete the empty paragraph after the table, in a way that undo is aware of this. + SwPaM aPaM(aIndex); + getIDocumentContentOperations().DelFullPara(aPaM); + } + 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), *oPos, 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 SwFormatAnchor& rFlyFormatAnchor ) +{ + bool bOk = false; + const SwPaM* pTmp = pPam; + do { + const SwNodeOffset nFlyIndex = rFlyFormatAnchor.GetAnchorNode()->GetIndex(); + auto [pPaMStart, pPaMEnd] = pTmp->StartEnd(); // SwPosition* + const SwNodeOffset nPamStartIndex = pPaMStart->GetNodeIndex(); + const SwNodeOffset nPamEndIndex = pPaMEnd->GetNodeIndex(); + if (RndStdIds::FLY_AT_PARA == rFlyFormatAnchor.GetAnchorId()) + bOk = (nPamStartIndex < nFlyIndex && nPamEndIndex > nFlyIndex) || + (((nPamStartIndex == nFlyIndex) && (pPaMStart->GetContentIndex() == 0)) && + (nPamEndIndex > nFlyIndex)); + else + { + const sal_Int32 nFlyContentIndex = rFlyFormatAnchor.GetAnchorContentOffset(); + const sal_Int32 nPamEndContentIndex = pPaMEnd->GetContentIndex(); + bOk = (nPamStartIndex < nFlyIndex && + (( nPamEndIndex > nFlyIndex )|| + ((nPamEndIndex == nFlyIndex) && + (nPamEndContentIndex > nFlyContentIndex))) ) + || + (((nPamStartIndex == nFlyIndex) && + (pPaMStart->GetContentIndex() <= 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; + const SwStartNode* pDirectFly = nullptr; + if (pCmpRange && *pCmpRange->GetPoint() == *pCmpRange->GetMark() + && (pCmpRange->GetPoint()->GetNode().IsOLENode() + || pCmpRange->GetPoint()->GetNode().IsGrfNode())) + { + pDirectFly = pCmpRange->GetPoint()->GetNode().FindFlyStartNode(); + } + + // collect all anchored somehow to paragraphs + for(sw::SpzFrameFormat* pFly: *GetSpzFrameFormats()) + { + bool bDrawFormat = bDrawAlso && RES_DRAWFRMFMT == pFly->Which(); + bool bFlyFormat = RES_FLYFRMFMT == pFly->Which(); + if( bFlyFormat || bDrawFormat ) + { + const SwFormatAnchor& rAnchor = pFly->GetAnchor(); + SwNode const*const pAnchorNode = rAnchor.GetAnchorNode(); + if (!pAnchorNode) + continue; + if (pDirectFly) + { + const SwFormatContent& rContent = pFly->GetContent(); + const SwNodeIndex* pContentNodeIndex = rContent.GetContentIdx(); + if (pContentNodeIndex && pContentNodeIndex->GetIndex() == pDirectFly->GetIndex()) + { + aRetval.insert(SwPosFlyFrame(*pAnchorNode, pFly, aRetval.size())); + break; + } + continue; + } + if ( (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, rAnchor )) + continue; // not a valid FlyFrame + aRetval.insert(SwPosFlyFrame(*pAnchorNode, 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 ( pAnchoredObj->DynCastFlyFrame() != 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 ) + { + const SwNode* pNd( pContentFrame->IsTextFrame() + ? static_cast<SwTextFrame const*>(pContentFrame)->GetTextNodeFirst() + : static_cast<SwNoTextFrame const*>(pContentFrame)->GetNode() ); + aRetval.insert(SwPosFlyFrame(*pNd, 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, std::u16string_view rText, std::u16string_view rSeparator, + const OUString& rNumberingSeparator, + const bool bBefore, const sal_uInt16 nId, const SwNodeOffset 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." ); + SwNodeOffset 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.GetNode(), 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()> + auto pOldFlyFrameFormat = dynamic_cast<SwFlyFrameFormat*>(pOldFormat); + const OUString sTitle( pOldFlyFrameFormat + ? pOldFlyFrameFormat->GetObjTitle() + : OUString() ); + const OUString sDescription( pOldFlyFrameFormat + ? pOldFlyFrameFormat->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. */ + SwAttrSet aNewSet = pNewFormat->GetAttrSet().CloneAsValue(); + + // Copy only the set attributes. + // The others should apply from the Templates. + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_PRINT ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_OPAQUE ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_PROTECT ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_SURROUND ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_VERT_ORIENT ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_HORI_ORIENT ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_LR_SPACE ); + lcl_CpyAttr( aNewSet, pOldFormat->GetAttrSet(), RES_UL_SPACE ); + lcl_CpyAttr( aNewSet, 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 )) + aNewSet.Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_BOX )) + aNewSet.Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pOldFormat->GetAttrSet(). + GetItemState( RES_SHADOW, true, &pItem )) + aNewSet.Put( *pItem ); + else if( SfxItemState::SET == pNewFormat->GetAttrSet(). + GetItemState( RES_SHADOW )) + aNewSet.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. + aNewSet.Put( SvxBoxItem(RES_BOX) ); + aNewSet.Put( SvxShadowItem(RES_SHADOW) ); + } + + // Always transfer the anchor, which is a hard attribute anyways. + aNewSet.Put( pOldFormat->GetAnchor() ); + + // The new one should be changeable in its height. + std::unique_ptr<SwFormatFrameSize> aFrameSize(pOldFormat->GetFrameSize().Clone()); + aFrameSize->SetHeightSizeType( SwFrameSize::Minimum ); + aNewSet.Put( std::move(aFrameSize) ); + + SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( + rDoc.GetNodes().GetEndOfAutotext(), + SwFlyStartNode, pColl ); + aNewSet.Put( SwFormatContent( pSttNd )); + + pNewFormat->SetFormatAttr( aNewSet ); + + // 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() ) + { + SwTextNode *pTextNode = rAnchor.GetAnchorNode()->GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = rAnchor.GetAnchorContentOffset(); + 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. + aNewSet.ClearItem(); + + aNewSet.Put( SwFormatSurround( css::text::WrapTextMode_NONE ) ); + aNewSet.Put( SvxOpaqueItem( RES_OPAQUE, true ) ); + + sal_Int16 eVert = bBefore ? text::VertOrientation::BOTTOM : text::VertOrientation::TOP; + aNewSet.Put( SwFormatVertOrient( 0, eVert ) ); + aNewSet.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); + aNewSet.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 ) + { + aNewSet.Put( SvxBoxItem(RES_BOX) ); + aNewSet.Put( SvxShadowItem(RES_SHADOW) ); + } + aNewSet.Put( SvxLRSpaceItem(RES_LR_SPACE) ); + aNewSet.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 ); + aNewSet.Put( aAnch ); + + if( pUndo ) + pUndo->SetFlys( *pOldFormat, aNewSet, *pNewFormat ); + else + pOldFormat->SetFormatAttr( aNewSet ); + + // Have only the FlyFrames created. + // We leave this to established methods (especially for InCntFlys). + pNewFormat->MakeFrames(); + // #i115719# + if ( pOldFlyFrameFormat ) + { + pOldFlyFrameFormat->SetObjTitle( sTitle ); + pOldFlyFrameFormat->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.empty() ) + { + aText += rSeparator; + } + const sal_Int32 nSepIdx = aText.getLength(); + aText += rText; + + // Insert string + SwContentIndex 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, SwNodeOffset 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. + SwAttrSet aNewSet = pOldFormat->GetAttrSet().CloneAsValue( 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() ); + aNewSet.Put( aProtect ); + } + + // Take over the text wrap + lcl_CpyAttr( aNewSet, 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 ); + aNewSet.Put( aOpaque ); + } + + // Take over position + // #i26791# - use directly drawing object's positioning attributes + aNewSet.Put( pOldFormat->GetHoriOrient() ); + aNewSet.Put( pOldFormat->GetVertOrient() ); + + aNewSet.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() ); + aNewSet.Put( aFrameSize ); + + // Apply the margin to the new Frame. + // Don't set a border, use the one from the Template. + aNewSet.Put( pOldFormat->GetLRSpace() ); + aNewSet.Put( pOldFormat->GetULSpace() ); + + SwStartNode* pSttNd = + rDoc.GetNodes().MakeTextSection( + 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 )) + aNewSet.Put( *GetDfltAttr( RES_BOX ) ); + + if( SfxItemState::SET == pNewFormat->GetAttrSet().GetItemState(RES_SHADOW)) + aNewSet.Put( *GetDfltAttr( RES_SHADOW ) ); + + pNewFormat->SetFormatAttr( SwFormatContent( pSttNd )); + pNewFormat->SetFormatAttr( aNewSet ); + + const SwFormatAnchor& rAnchor = pNewFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId() ) + { + SwTextNode *pTextNode = rAnchor.GetAnchorNode()->GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = rAnchor.GetAnchorContentOffset(); + 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. + aNewSet.ClearItem(); + + aNewSet.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() ); + } + aNewSet.Put( SvxLRSpaceItem( RES_LR_SPACE ) ); + aNewSet.Put( SvxULSpaceItem( RES_UL_SPACE ) ); + + // #i26791# - set position of the drawing object, which is labeled. + aNewSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ) ); + aNewSet.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 ); + aNewSet.Put( aAnch ); + + if( pUndo ) + { + pUndo->SetFlys( *pOldFormat, aNewSet, *pNewFormat ); + // #i26791# - position no longer needed + pUndo->SetDrawObj( nLayerId ); + } + else + pOldFormat->SetFormatAttr( aNewSet ); + + // 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 + SwContentIndex 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, std::u16string_view rName, std::u16string_view rCmpName) +{ + if (o3tl::starts_with(rName, rCmpName)) + { + // Only get and set the Flag + const sal_Int32 nNum = o3tl::toInt32(rName.substr(nNmLen)) - 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()) + return; + + const SdrObjList* pSub(rObj.GetSubList()); + assert(pSub && "IsGroupObject is implemented as GetSubList != nullptr"); + for (const rtl::Reference<SdrObject>& pObj : *pSub) + { + 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& rDoc, TranslateId pDefStrId, sal_uInt16 eType, std::u16string_view rPrefix = std::u16string_view(), SwNodeType nNdTyp = SwNodeType::NONE) +{ + assert(eType >= RES_FMT_BEGIN && eType < RES_FMT_END); + if (rDoc.IsInMailMerge()) + { + OUString newName = "MailMergeFly" + + OStringToOUString( DateTimeToOString( DateTime( DateTime::SYSTEM )), RTL_TEXTENCODING_ASCII_US ) + + OUString::number( rDoc.GetSpzFrameFormats()->size() + 1 ); + return newName; + } + + if (!rPrefix.empty()) + { + // Generate a name that makes it possible to know this is a copy of which original name, + // e.g. 'Picture 1 Copy 1'. + assert(nNdTyp != SwNodeType::NONE); + sal_Int32 nCnt = 1; + OUString aPrefix = SwResId(STR_MARK_COPY).replaceFirst("%1", rPrefix); + OUString aTmp; + while(nCnt < SAL_MAX_INT32) + { + aTmp = aPrefix + OUString::number(nCnt); + ++nCnt; + if (!rDoc.FindFlyByName(aTmp, nNdTyp)) + { + break; + } + } + return aTmp; + } + + OUString aName(SwResId(pDefStrId)); + sal_Int32 nNmLen = aName.getLength(); + + std::vector<unsigned int> aUsedNums; + aUsedNums.reserve(rDoc.GetSpzFrameFormats()->size()); + + for(sw::SpzFrameFormat* pFlyFormat: *rDoc.GetSpzFrameFormats()) + { + 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 + auto nNum = first_available_number(aUsedNums) + 1; + return aName + OUString::number(nNum); +} + +OUString SwDoc::GetUniqueGrfName(std::u16string_view rPrefix) const +{ + return lcl_GetUniqueFlyName(*this, STR_GRAPHIC_DEFNAME, RES_FLYFRMFMT, rPrefix, SwNodeType::Grf); +} + +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, TranslateId(nullptr, "DrawObject"), RES_DRAWFRMFMT); +} + +const SwFlyFrameFormat* SwDoc::FindFlyByName( const OUString& rName, SwNodeType nNdTyp ) const +{ + auto it = GetSpzFrameFormats()->findByTypeAndName( RES_FLYFRMFMT, rName ); + if( it == GetSpzFrameFormats()->typeAndNameEnd() ) + return nullptr; + + const SwFrameFormat* pFlyFormat = *it; + assert( RES_FLYFRMFMT == pFlyFormat->Which() && pFlyFormat->GetName() == rName ); + 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 ) +{ + if (rFormat.GetName() == rName) + { + return; + } + OUString sName( rName ); + if( sName.isEmpty() || FindFlyByName( sName ) ) + { + TranslateId 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.SetFormatName( 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)); + + n = GetSpzFrameFormats()->size(); + if( 255 < n ) + 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 = o3tl::toInt32(aNm.subView( nLen )); + 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.GetAnchorNode() ) + { + 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->SetFormatName( sGrfNm + OUString::number( ++nGrfNum )); + break; + case SwNodeType::Ole: + pFlyFormat->SetFormatName( sOLENm + OUString::number( ++nOLENum )); + break; + default: + pFlyFormat->SetFormatName( sFlyNm + OUString::number( ++nFlyNum )); + break; + } + } + } + aArr.clear(); + + if( GetFootnoteIdxs().empty() ) + return; + + 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.GetNode() ); + } +} + +bool SwDoc::IsInHeaderFooter( const SwNode& 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. + const SwNode* pNd = &rIdx; + const SwNode* pFlyNd = pNd->FindFlyStartNode(); + while( pFlyNd ) + { + // get up by using the Anchor +#if OSL_DEBUG_LEVEL > 0 + std::vector<const SwFrameFormat*> checkFormats; + for(sw::SpzFrameFormat* pFormat: *GetSpzFrameFormats()) + { + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + checkFormats.push_back( pFormat ); + } +#endif + std::vector<SwFrameFormat*> const & rFlys(pFlyNd->GetAnchoredFlys()); + bool bFound(false); + for (size_t i = 0; i < rFlys.size(); ++i) + { + const SwFrameFormat *const pFormat = rFlys[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.GetAnchorNode() ) + { + return false; + } + + pNd = rAnchor.GetAnchorNode(); + 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.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->GetAnchorNode()) + { + pFlyFormat = pAnchor->GetAnchorNode()->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 0000000000..d6e885e821 --- /dev/null +++ b/sw/source/core/doc/docnew.cxx @@ -0,0 +1,1320 @@ +/* -*- 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 <string_view> + +#include <config_features.h> +#include <config_fuzzers.h> + +#include <o3tl/sorted_vector.hxx> + +#include <doc.hxx> +#include <proofreadingiterator.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/XFlatParagraphIteratorProvider.hpp> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/random.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/XmlIdRegistry.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <sfx2/linkmgr.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <svl/numformat.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 <GrammarContact.hxx> +#include <OnlineAccessibilityCheck.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 <textcontentcontrol.hxx> + +#include <svx/xfillit0.hxx> +#include <unotools/configmgr.hxx> +#include <i18nlangtag/mslangid.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::document; + +constexpr OUStringLiteral DEFAULT_CHAR_FORMAT_NAME = u"Character style"; + +/* + * 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.GetAnchorNode() ) + rFormatAnchor.SetAnchor( nullptr ); +} + +/* + * exported methods + */ +SwDoc::SwDoc() + : m_pNodes(new SwNodes(*this)), + mpAttrPool(new SwAttrPool(this)), + maOLEModifiedIdle( "sw::SwDoc maOLEModifiedIdle" ), + mpMarkManager(new ::sw::mark::MarkManager(*this)), + m_pMetaFieldManager(new ::sw::MetaFieldManager()), + m_pContentControlManager(new ::SwContentControlManager()), + 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(), DEFAULT_CHAR_FORMAT_NAME, nullptr ) ), + mpDfltTextFormatColl( new SwTextFormatColl( GetAttrPool(), "Paragraph style" ) ), + mpDfltGrfFormatColl( new SwGrfFormatColl( GetAttrPool(), "Graphikformatvorlage" ) ), + mpFrameFormatTable( new sw::FrameFormats<SwFrameFormat*>() ), + mpCharFormatTable( new SwCharFormats ), + mpSpzFrameFormatTable( new sw::FrameFormats<sw::SpzFrameFormat*>() ), + mpSectionFormatTable( new SwSectionFormats ), + mpTableFrameFormatTable( new sw::TableFrameFormats() ), + 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(new sw::GrammarContact), + mpOnlineAccessibilityCheck(new sw::OnlineAccessibilityCheck(*this)), + mpCellStyles(new SwCellStyleTable), + 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->insert(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( + GetUndoManager().GetUndoNodes().GetEndOfContent(), + mpDfltTextFormatColl.get() ); + new SwTextNode( GetNodes().GetEndOfContent(), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + + maOLEModifiedIdle.SetPriority( TaskPriority::LOWEST ); + maOLEModifiedIdle.SetInvokeHandler( LINK( this, SwDoc, DoUpdateModifiedOLE )); + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + // 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. + { + SfxItemSetFixed<RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1> aIgnorableParagraphItems( GetAttrPool() ); + 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; + + if (!utl::ConfigManager::IsFuzzing()) + { + // Make sure that in case the document language is not set, then we don't return + // LANGUAGE_DONTKNOW, but the UI locale. + const SvtLinguConfig aLinguConfig; + SvtLinguOptions aOptions; + aLinguConfig.GetOptions(aOptions); + LanguageType eLang = MsLangId::resolveSystemLanguageByScriptType(aOptions.nDefaultLanguage, + i18n::ScriptType::LATIN); + SetLanguage(eLang, RES_CHRATR_LANGUAGE); + eLang = MsLangId::resolveSystemLanguageByScriptType(aOptions.nDefaultLanguage_CJK, + i18n::ScriptType::ASIAN); + SetLanguage(eLang, RES_CHRATR_CJK_LANGUAGE); + eLang = MsLangId::resolveSystemLanguageByScriptType(aOptions.nDefaultLanguage_CTL, + i18n::ScriptType::COMPLEX); + SetLanguage(eLang, RES_CHRATR_CTL_LANGUAGE); + } + + getIDocumentState().ResetModified(); + + s_pLast = this; +} + +/** + * 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() +{ + s_pLast = nullptr; + + mxVbaFind.clear(); + + // nothing here should create Undo actions! + GetIDocumentUndoRedo().DoUndo(false); + + if (mpDocShell) + { + mpDocShell->SetUndoManager(nullptr); + } + + mpGrammarContact.reset(); + mpOnlineAccessibilityCheck.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_aOutlineNodes.clear(); + SwNodes & rUndoNodes( GetUndoManager().GetUndoNodes() ); + rUndoNodes.m_aOutlineNodes.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 ) + { + // coverity[deref_arg] - the SwPaM delete moves a new entry into GetNext() + delete pTmp->GetNext(); + } + delete pTmp; + } + + // 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() ); + + // clear TOX after nodes - TOXMarks are gone now so SwTOXType has no clients + for (const auto& pType : *mpTOXTypes) + { + pType->CallSwClientNotify(sw::DocumentDyingHint()); + } + mpTOXTypes->clear(); + mpDefTOXBases.reset(); + + // 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() ); + +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + // 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 + + { + std::scoped_lock lock(mNumberFormatterMutex); + delete mpNumberFormatter; mpNumberFormatter= nullptr; + } + mpFootnoteInfo.reset(); + mpEndNoteInfo.reset(); + mpLineNumberInfo.reset(); + mpFootnoteIdxs.reset(); + mpTOXTypes.reset(); + mpEmptyPageFormat.reset(); + mpColumnContFormat.reset(); + mpDfltCharFormat.reset(); + mpDfltFrameFormat.reset(); + mpLayoutCache.reset(); + mpAttrPool.clear(); +} + +void SwDoc::SetDocShell( SwDocShell* pDSh ) +{ + if( mpDocShell == pDSh ) + return; + + 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.GetNode(), mpDfltTextFormatColl.get() ); + + if( getIDocumentLayoutAccess().GetCurrentViewShell() ) + { + // set the layout to the dummy pagedesc + pFirstNd->SetAttr( SwFormatPageDesc( pDummyPgDsc )); + + SwPosition aPos( *pFirstNd ); + SwPaM const tmpPaM(aSttIdx.GetNode(), 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->DeleteAndDestroyAll(/*keepDefault*/true); + + 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(); + + { + std::scoped_lock lock(mNumberFormatterMutex); + delete mpNumberFormatter; 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 ); +} + +::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 + static const WhichRangesContainer aRangeOfDefaults(svl::Items< + RES_CHRATR_BEGIN, RES_CHRATR_END-1, + RES_PARATR_BEGIN, RES_PARATR_END-1, + RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END-1, + RES_FRMATR_BEGIN, RES_FRMATR_END-1, + RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END-1, + XATTR_START, XATTR_END-1 + >); + + SfxItemSet aNewDefaults(GetAttrPool(), aRangeOfDefaults); + + for (const WhichPair& rPair : aRangeOfDefaults) + { + for (sal_uInt16 nWhich = rPair.first; + nWhich <= rPair.second; ++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() - SwNodeOffset(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, 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); + + uno::Reference<beans::XPropertySet> const xThisSet( + GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> const xRetSet( + pRetShell->GetBaseModel(), uno::UNO_QUERY_THROW); + uno::Sequence<beans::PropertyValue> aInteropGrabBag; + xThisSet->getPropertyValue("InteropGrabBag") >>= aInteropGrabBag; + xRetSet->setPropertyValue("InteropGrabBag", uno::Any(aInteropGrabBag)); + + 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, std::u16string_view 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 individual 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 ); + // insert new node - will be removed at the end... + // (don't SplitNode() as it may move flys to the wrong node) + getIDocumentContentOperations().AppendTextNode(aBreakPos); + SwFormatPageDesc pageDesc(pTargetPageDesc); + pageDesc.SetNumOffset(nStartPageNumber); + // set break on the last paragraph + getIDocumentContentOperations().InsertPoolItem(SwPaM(aBreakPos), + pageDesc, SetAttrMode::DEFAULT, pTargetShell->GetLayout()); + // tdf#148309 move to the last node - so that the "flush page break" + // code below will format the frame of the node with the page break, + // which is required for new page frames to be created! Else layout + // performance will be terrible. + pTargetShell->SttEndDoc(false); + + // 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 SwContentIndex* 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" ); + assert(pTargetShell->GetCursor()->GetPoint()->GetNode().GetTextNode()->GetSwAttrSet().HasItem(RES_PAGEDESC)); + 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 + SwPaM aInsertPam( GetNodes().GetEndOfContent() ); + +#ifdef DBG_UTIL + SAL_INFO( "sw.docappend", "Pam-Nd: " << aCpyPam.GetPointNode().GetIndex() - aCpyPam.GetMarkNode().GetIndex() + 1 + << " (0x" << std::hex << static_cast<int>(aCpyPam.GetMarkNode().GetNodeType()) << std::dec + << " " << aCpyPam.GetMarkNode().GetIndex() + << " - 0x" << std::hex << static_cast<int>(aCpyPam.GetPointNode().GetNodeType()) << std::dec + << " " << aCpyPam.GetPointNode().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.GetNode()); + + --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(aIndexBefore.GetNode(), rInsPos.GetNode()); + + aPaM.GetDoc().MakeUniqueNumRules(aPaM); + + // Update the rsid of each pasted text node + SwNodes &rDestNodes = GetNodes(); + SwNodeOffset const nEndIdx = aPaM.End()->GetNodeIndex(); + + for (SwNodeOffset nIdx = aPaM.Start()->GetNodeIndex(); + nIdx <= nEndIdx; ++nIdx) + { + SwTextNode *const pTextNode = rDestNodes[nIdx]->GetTextNode(); + if ( pTextNode ) + UpdateParRsid( pTextNode ); + } + } + + { + SwNodeOffset 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(sw::SpzFrameFormat* 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 0000000000..0735380e5d --- /dev/null +++ b/sw/source/core/doc/docnum.cxx @@ -0,0 +1,2665 @@ +/* -*- 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 <ftninfo.hxx> +#include <ftnidx.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentListsAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.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 <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <tools/datetimeutils.hxx> + +#include <map> +#include <stdlib.h> + +#include <wrtsh.hxx> + +namespace { + void lcl_ResetIndentAttrs(SwDoc *pDoc, const SwPaM &rPam, + const o3tl::sorted_vector<sal_uInt16> aResetAttrsArray, + SwRootFrame const*const pLayout) + { + // #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()->GetNode().GetTextNode() ) + { + SwPaM aPam( rPam.Start()->GetNode(), 0, + rPam.End()->GetNode(), rPam.End()->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) + return; + + // ensure that selection from the Shell includes the para-props node + // to which the attributes should be applied + if (rPam.GetPoint()->GetNode().IsTextNode()) + { + rPam.GetPoint()->Assign( *sw::GetParaPropsNode(*pLayout, rPam.GetPoint()->GetNode()) ); + } + if (rPam.GetMark()->GetNode().IsTextNode()) + { + rPam.GetMark()->Assign( *sw::GetParaPropsNode(*pLayout, rPam.GetMark()->GetNode()) ); + } + } +} + +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 (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().StartUndo(SwUndoId::OUTLINE_EDIT, nullptr); + if (mpOutlineRule) + { + GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoOutlineEdit>(*mpOutlineRule, rRule, *this)); + } + } + + 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); + + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().EndUndo(SwUndoId::OUTLINE_EDIT, nullptr); + } + + getIDocumentState().SetModified(); +} + +void SwDoc::PropagateOutlineRule() +{ + SwNumRule* pMyOutlineRule = GetOutlineNumRule(); + if (!pMyOutlineRule) + return; + + 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() ) + { + 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(); + SwNode* const pSttNd = &aPam.Start()->GetNode(); + SwNode* const pEndNd = &aPam.End()->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(o3tl::narrowing<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(o3tl::narrowing<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 = *rPam.End(); + if( GetNodes().GetOutLineNds().empty() || !nOffset || + (rStt.GetNodeIndex() < GetNodes().GetEndOfExtras().GetIndex()) || + (rEnd.GetNodeIndex() < GetNodes().GetEndOfExtras().GetIndex())) + { + return false; + } + + SwOutlineNodes::size_type nCurrentPos = 0; + SwNodeIndex aSttRg( rStt.GetNode() ), aEndRg( rEnd.GetNode() ); + + 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 ]; + + SwNodeOffset 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() + SwNodeOffset(2) ); + + SwNodeOffset nOffs = nNewPos - ( 0 < nOffset ? aEndRg.GetIndex() : aSttRg.GetIndex()); + SwPaM aPam( aSttRg, aEndRg, SwNodeOffset(0), SwNodeOffset(-1) ); + return MoveParagraph( aPam, nOffs, true ); +} + +static SwTextNode* lcl_FindOutlineName(const SwOutlineNodes& rOutlNds, + SwRootFrame const*const pLayout, std::u16string_view aName, 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(aName)) + { + if (sText.getLength() == sal_Int32(aName.size())) + { + 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; + std::u16string_view sNum = o3tl::getToken(rName, 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; + std::u16string_view sName( rName ); + + while( -1 != nPos ) + { + sal_uInt16 nVal = 0; + for( size_t n = 0; n < sNum.size(); ++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.substr( nPos ); + nPos = 0; + sNum = o3tl::getToken(sName, 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; + std::u16string_view sTempNum = o3tl::getToken(sExpandedText, 0, '.', nPos); + if( sTempNum.empty() || -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.Assign(*pNd); + return true; + } + + pNd = ::lcl_FindOutlineName(rOutlNds, pLayout, rName, false); + if ( pNd ) + { + rPos.Assign(*pNd); + 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.Assign(*pNd); + 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()->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 ) + { + const o3tl::sorted_vector<sal_uInt16> attrs{ RES_MARGIN_FIRSTLINE, RES_MARGIN_TEXTLEFT, RES_MARGIN_RIGHT }; + ::lcl_ResetIndentAttrs(this, aPam, attrs, 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 ) + { + const o3tl::sorted_vector<sal_uInt16> attrs{ RES_PARATR_LIST_ISCOUNTED }; + ::lcl_ResetIndentAttrs(this, rPam, attrs, 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.GetNode().GetTextNode(); + + if (!pTextNd) + return; + + 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.GetNode().GetTextNode(); + + if (!pTextNd) + return; + + 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 ) + return; + + 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( const 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 ); + } + } + } +} + +void SwDoc::ReplaceNumRule( const SwPosition& rPos, + const OUString& rOldRule, const OUString& rNewRule ) +{ + SwNumRule *pOldRule = FindNumRulePtr( rOldRule ), + *pNewRule = FindNumRulePtr( rNewRule ); + if( !pOldRule || !pNewRule || pOldRule == pNewRule ) + return; + + 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 ); + + const SwTextNode* pGivenTextNode = rPos.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(); + } +} + +namespace +{ + struct ListStyleData + { + SwNumRule* pReplaceNumRule; + bool bCreateNewList; + OUString sListId; + + ListStyleData() + : pReplaceNumRule( nullptr ), + bCreateNewList( false ) + {} + }; +} + +void SwDoc::MakeUniqueNumRules(const SwPaM & rPaM) +{ + OSL_ENSURE( &rPaM.GetDoc() == this, "need same doc" ); + + std::map<SwNumRule *, ListStyleData> aMyNumRuleMap; + + bool bFirst = true; + + const SwNodeOffset nStt = rPaM.Start()->GetNodeIndex(); + const SwNodeOffset nEnd = rPaM.End()->GetNodeIndex(); + for (SwNodeOffset 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 + SwTextNode* pNd = rPam.GetPoint()->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); + SwNodeOffset nStt = aPam.Start()->GetNodeIndex(); + SwNodeOffset const nEnd = aPam.End()->GetNodeIndex(); + + 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->HasMergedParas()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + // not a tracked row deletion in Hide Changes mode + if (SwContentFrame* pFrame = rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout)) + { + if (sw::MergedPara* pMerged = static_cast<SwTextFrame*>(pFrame)->GetMergedPara()) + { + rIndex = pMerged->pFirstNode->GetIndex(); + } + } + } + } + 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->HasMergedParas()) + { + if (rIndex.GetNode().IsTextNode()) + { + if (rIndex.GetNode().GetRedlineMergeFlag() != SwNode::Merge::None) + { + if (SwContentFrame* pFrame = rIndex.GetNode().GetTextNode()->getLayoutFrame(pLayout)) + { + if (sw::MergedPara* pMerged = static_cast<SwTextFrame*>(pFrame)->GetMergedPara()) + { + rIndex = pMerged->pLastNode->GetIndex(); + } + } + } + } + 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.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.GetNode() ); + if( ! pNd->IsCountedInList() ) + { + bool bError = false; + do { + sw::GotoPrevLayoutTextFrame(aIdx, pLayout); + if( aIdx.GetNode().IsTextNode() ) + { + pNd = aIdx.GetNode().GetTextNode(); + const SwNumRule* pRule = pNd->GetNumRule(); + + if( pRule ) + { + sal_uInt8 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() != SwNodeOffset(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.Assign(aIdx); + 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.Assign(aIdx); + else + rPos.Assign( *pLast ); + 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.GetNode().GetTextNode(); + if (pLayout) + { + pTextNd = sw::GetParaPropsNode(*pLayout, rPos.GetNode()); + } + SwNode * pStartFromNode = pTextNd; + + if (pTextNd) + { + SwNodeIndex aIdx(rPos.GetNode()); + + // - 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); + SwNodeOffset nStt = aPam.Start()->GetNodeIndex(); + SwNodeOffset const nEnd = aPam.End()->GetNodeIndex(); + + // -> outline nodes are promoted or demoted differently + bool bOnlyOutline = true; + bool bOnlyNonOutline = true; + for (SwNodeOffset 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 (SwNodeOffset 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(SwNodeOffset 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, SwNodeOffset nOffset, bool const bIsOutlMv) +{ + MakeAllOutlineContentTemporarilyVisible a(this); + + // 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->HasMergedParas()) + { + pLayout = pLay; + } + } + if (pLayout) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*pLayout, rPam.Start()->GetNode())); + if (nodes.first && nodes.first != &rPam.Start()->GetNode()) + { + assert(nodes.second); + if (nOffset < SwNodeOffset(0)) + { + nOffset += rPam.Start()->GetNodeIndex() - nodes.first->GetIndex(); + if (SwNodeOffset(0) <= nOffset) // hack: there are callers that know what + { // node they want; those should never need + nOffset = SwNodeOffset(-1); // this; other callers just pass in -1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(nodes.first->GetIndex() < rPam.Start()->GetNodeIndex()); + rPam.Start()->Assign(*nodes.first); + } + nodes = sw::GetFirstAndLastNode(*pLayout, rPam.End()->GetNode()); + if (nodes.second && nodes.second != &rPam.End()->GetNode()) + { + assert(nodes.first); + if (SwNodeOffset(0) < nOffset) + { + nOffset -= nodes.second->GetIndex() - rPam.End()->GetNodeIndex(); + if (nOffset <= SwNodeOffset(0)) // hack: there are callers that know what + { // node they want; those should never need + nOffset = SwNodeOffset(+1); // this; other callers just pass in +1 + } // and those should still move + } + if (!rPam.HasMark()) + { + rPam.SetMark(); + } + assert(rPam.End()->GetNodeIndex() < nodes.second->GetIndex()); + // until end, otherwise Impl will detect overlapping redline + rPam.End()->Assign(*nodes.second, nodes.second->GetTextNode()->Len()); + } + + if (nOffset > SwNodeOffset(0)) + { // sw_redlinehide: avoid moving into delete redline, skip forward + if (GetNodes().GetEndOfContent().GetIndex() <= rPam.End()->GetNodeIndex() + nOffset) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.End()->GetNodeIndex() + nOffset + 1]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; ++nOffset) + { + pNode = GetNodes()[rPam.End()->GetNodeIndex() + 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()->GetNodeIndex() + nOffset < SwNodeOffset(1)) + { + return false; // can't move + } + SwNode const* pNode(GetNodes()[rPam.Start()->GetNodeIndex() + nOffset]); + if ( pNode->GetRedlineMergeFlag() != SwNode::Merge::None + && pNode->GetRedlineMergeFlag() != SwNode::Merge::First) + { + for ( ; ; --nOffset) + { + pNode = GetNodes()[rPam.Start()->GetNodeIndex() + 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, SwNodeOffset const nOffset, + bool const bIsOutlMv, SwRootFrame const*const pLayout) +{ + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + SwNodeOffset nStIdx = pStt->GetNodeIndex(); + SwNodeOffset nEndIdx = pEnd->GetNodeIndex(); + + // 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() ) + { + // coverity[copy_paste_error : FALSE] - 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. + } + + SwNodeOffset nInStIdx, nInEndIdx; + SwNodeOffset nOffs = nOffset; + if( nOffset > SwNodeOffset(0) ) + { + nInEndIdx = nEndIdx; + nEndIdx += nOffset; + ++nOffs; + } + else + { + // Impossible to move to negative index + if( 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->GetNode(), RedlineType::Delete ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwContentNode* pCNd = pEnd->GetNode().GetContentNode(); + SwPosition aStPos( pStt->GetNode() ); + SwPosition aEndPos( pEnd->GetNode(), pCNd, 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() ) + { + auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition* + 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 > SwNodeOffset(0) ? pEnd->GetNode() : pStt->GetNode(), nOffs ); + SwNodeRange aMvRg( pStt->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(+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->GetNode(), RedlineType::Insert ); + if( SwRedlineTable::npos != nRedlPos ) + { + SwRangeRedline* pTmp = getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ]; + auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition* + SwRangeRedline aTmpRedl( RedlineType::Insert, rPam ); + const SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode(); + // Is completely in the range and is the own Redline too? + if( aTmpRedl.IsOwnRedline( *pTmp ) && + (pRStt->GetNode() < pStt->GetNode() || + (pRStt->GetNode() == pStt->GetNode() && !pRStt->GetContentIndex()) ) && + (pEnd->GetNode() < pREnd->GetNode() || + (pEnd->GetNode() == pREnd->GetNode() && + pCEndNd ? pREnd->GetContentIndex() == pCEndNd->Len() + : !pREnd->GetContentIndex() )) ) + { + 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->GetNode() > aIdx.GetNode() || aIdx > pREnd->GetNode() || + // pOwnRedl doesn't start at the beginning of a node, so it's not + // possible to resize it to contain the line moved before it + ( pRStt->GetNode() == aIdx.GetNode() && pRStt->GetContentIndex() > 0 ) ) ) + { + // 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 ); + + std::optional<SwPaM> oPam( std::in_place, pStt->GetNode(), 0, aMvRg.aEnd.GetNode(), 0 ); + + SwPaM& rOrigPam(rPam); + rOrigPam.DeleteMark(); + rOrigPam.GetPoint()->Assign(aIdx.GetIndex() - 1); + + bool bDelLastPara = !aInsPos.GetNode().IsContentNode(); + SwNodeOffset nOrigIdx = aIdx.GetIndex(); + + /* 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 + + // adjust empty nodes later + SwTextNode const*const pIsEmptyNode(nOffset < SwNodeOffset(0) + ? aInsPos.GetNode().GetTextNode() + : aIdx.GetNode().GetTextNode()); + bool bIsEmptyNode = pIsEmptyNode && pIsEmptyNode->Len() == 0; + + sal_uInt32 nMovedID = getIDocumentRedlineAccess().GetRedlineTable().getNewMovedID(); + getIDocumentContentOperations().CopyRange(*oPam, aInsPos, SwCopyFlags::CheckPosInFly, + nMovedID); + + // now delete all the delete redlines that were copied +#ifndef NDEBUG + size_t nRedlines(getIDocumentRedlineAccess().GetRedlineTable().size()); +#endif + if (nOffset > SwNodeOffset(0)) + assert(oPam->End()->GetNodeIndex() - oPam->Start()->GetNodeIndex() + nOffset == aInsPos.GetNodeIndex() - oPam->End()->GetNodeIndex()); + else + assert(oPam->Start()->GetNodeIndex() - oPam->End()->GetNodeIndex() + nOffset == aInsPos.GetNodeIndex() - oPam->End()->GetNodeIndex()); + SwRedlineTable::size_type i; + getIDocumentRedlineAccess().GetRedline(*oPam->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() < *oPam->Start()) + { + break; + } + if (pRedline->GetType() == RedlineType::Delete && + // tdf#145066 skip full-paragraph deletion which was jumped over + // in Show Changes mode to avoid of deleting an extra row + *oPam->Start() <= *pRedline->Start()) + { + SwRangeRedline* pNewRedline; + { + SwPaM pam(*pRedline, nullptr); + SwNodeOffset const nCurrentOffset( + nOrigIdx - oPam->Start()->GetNodeIndex()); + pam.GetPoint()->Assign(pam.GetPoint()->GetNodeIndex() + nCurrentOffset, + pam.GetPoint()->GetContentIndex()); + pam.GetMark()->Assign(pam.GetMark()->GetNodeIndex() + nCurrentOffset, + pam.GetMark()->GetContentIndex()); + + pNewRedline = new SwRangeRedline( RedlineType::Delete, pam, nMovedID ); + } + // 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.GetNode(); + SwContentNode* pCNd = SwNodes::GoPrevious( &aInsPos ); + if (pCNd) + aInsPos.AssignEndIndex( *pCNd ); + + // 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->GetNode() == aIdx.GetNode() ) + { + pPos->Adjust(SwNodeOffset(1)); + } + pPos = &pTmp->GetBound(false); + if( pPos->GetNode() == aIdx.GetNode() ) + { + pPos->Adjust(SwNodeOffset(1)); + } + } + CorrRel( aIdx.GetNode(), aInsPos ); + + if (pCNd) + pCNd->JoinNext(); + } + + rOrigPam.GetPoint()->Adjust(SwNodeOffset(1)); + assert(*oPam->GetMark() < *oPam->GetPoint()); + if (oPam->GetPoint()->GetNode().IsEndNode()) + { // ensure redline ends on content node + oPam->GetPoint()->Adjust(SwNodeOffset(-1)); + assert(oPam->GetPoint()->GetNode().IsTextNode()); + SwTextNode *const pNode(oPam->GetPoint()->GetNode().GetTextNode()); + oPam->GetPoint()->SetContent(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>(*oPam, SwUndoId::DELETE)); + } + + SwRangeRedline* pNewRedline = new SwRangeRedline( RedlineType::Delete, *oPam, nMovedID ); + + // prevent assertion from aPam's target being deleted + SwNodeIndex bound1(oPam->GetBound().GetNode()); + SwNodeIndex bound2(oPam->GetBound(false).GetNode()); + oPam.reset(); + + getIDocumentRedlineAccess().AppendRedline( pNewRedline, true, nMovedID ); + + oPam.emplace(bound1, bound2); + sw::UpdateFramesForAddDeleteRedline(*this, *oPam); + + // avoid setting empty nodes to tracked insertion + if ( bIsEmptyNode ) + { + SwRedlineTable& rTable = getIDocumentRedlineAccess().GetRedlineTable(); + SwRedlineTable::size_type nRedlPosWithEmpty = + getIDocumentRedlineAccess().GetRedlinePos( pStt->GetNode(), RedlineType::Insert ); + if ( SwRedlineTable::npos != nRedlPosWithEmpty ) + { + pOwnRedl = rTable[nRedlPosWithEmpty]; + SwPosition *pRPos = nOffset < SwNodeOffset(0) ? pOwnRedl->End() : pOwnRedl->Start(); + SwNodeIndex aIdx2 ( pRPos->GetNode() ); + SwTextNode const*const pEmptyNode0(aIdx2.GetNode().GetTextNode()); + if ( nOffset < SwNodeOffset(0) ) + { + // move up + --aIdx2; + SwTextNode const*const pEmptyNode(aIdx2.GetNode().GetTextNode()); + if ( pEmptyNode && pEmptyNode->Len() == 0 ) + pRPos->Adjust(SwNodeOffset(-1)); + } + else if ( pEmptyNode0 && pEmptyNode0->Len() == 0 ) + { + // move down + ++aIdx2; + SwTextNode const*const pEmptyNode(aIdx2.GetNode().GetTextNode()); + if (pEmptyNode) + pRPos->Adjust(SwNodeOffset(+1)); + } + + // sort redlines, when the trimmed range results bad redline order + if ( nRedlPosWithEmpty + 1 < rTable.size() && + *rTable[nRedlPosWithEmpty + 1] < *rTable[nRedlPosWithEmpty] ) + { + rTable.Remove(nRedlPosWithEmpty); + rTable.Insert(pOwnRedl); + } + } + } + + 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); + } + + SwNodeOffset nRedlSttNd(0), nRedlEndNd(0); + if( pOwnRedl ) + { + const SwPosition *pRStt = pOwnRedl->Start(), *pREnd = pOwnRedl->End(); + nRedlSttNd = pRStt->GetNodeIndex(); + nRedlEndNd = pREnd->GetNodeIndex(); + } + + std::unique_ptr<SwUndoMoveNum> pUndo; + SwNodeOffset nMoved(0); + if (GetIDocumentUndoRedo().DoesUndo()) + { + pUndo.reset(new SwUndoMoveNum( rPam, nOffset, bIsOutlMv )); + nMoved = rPam.End()->GetNodeIndex() - rPam.Start()->GetNodeIndex() + 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.GetNode(), 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 ) + { + auto [pRStt, pREnd] = pOwnRedl->StartEnd(); // SwPosition* + if( pRStt->GetNodeIndex() != nRedlSttNd ) + { + pRStt->Assign(nRedlSttNd); + } + if( pREnd->GetNodeIndex() != nRedlEndNd ) + { + pREnd->Assign(nRedlEndNd); + SwContentNode* pCNd = pREnd->GetNode().GetContentNode(); + if(pCNd) + pREnd->SetContent( pCNd->Len() ); + } + } + + getIDocumentState().SetModified(); + return true; +} + +bool SwDoc::NumOrNoNum( SwNode& rIdx, bool bDel ) +{ + bool bResult = false; + SwTextNode * pTextNd = rIdx.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.GetNode().GetTextNode(); + + if ( pTNd != nullptr ) + { + if (pLayout && !sw::IsParaPropsNode(*pLayout, *pTNd)) + { + pTNd = static_cast<SwTextFrame*>(pTNd->getLayoutFrame(pLayout))->GetMergedPara()->pParaPropsNode; + rPos.Assign(*pTNd); + } + pRet = pTNd->GetNumRule(); + } + + return pRet; +} + +sal_uInt16 SwDoc::FindNumRule( std::u16string_view 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 = o3tl::narrowing<sal_uInt16>(o3tl::toInt32(sNm.subView( nNmLen ))); + 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 ) + { + nTmp = pSetFlags[ n ]; + if( 0xff != nTmp ) + { + // 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(*this); +} + +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.GetNode()); + 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 0000000000..3b0f8d7b9f --- /dev/null +++ b/sw/source/core/doc/docredln.cxx @@ -0,0 +1,2404 @@ +/* -*- 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 <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/datetimeutils.hxx> +#include <hintids.hxx> +#include <svl/itemiter.hxx> +#include <editeng/prntitem.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <unotools/datetime.hxx> +#include <sfx2/viewsh.hxx> +#include <o3tl/string_view.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 <algorithm> +#include <limits> +#include <utility> +#include <view.hxx> +#include <viewopt.hxx> +#include <usrpref.hxx> +#include <viewsh.hxx> +#include <viscrs.hxx> +#include <rootfrm.hxx> +#include <strings.hrc> +#include <swtypes.hxx> +#include <wrtsh.hxx> +#include <txtfld.hxx> + +#include <flowfrm.hxx> +#include <txtfrm.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 ) + { + volatile SwRedlineTable::size_type nDummy = 0; + const SwRangeRedline* pCurrent = rTable[ n ]; + const SwRangeRedline* pNext = n+1 < rTable.size() ? rTable[ n+1 ] : nullptr; + if( pCurrent == pNext ) + (void) nDummy; + if( n == nWatch ) + (void) nDummy; // Possible debugger breakpoint + } + } + +#endif + + +SwExtraRedlineTable::~SwExtraRedlineTable() +{ + DeleteAndDestroyAll(); +} + +void SwExtraRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedlineTable")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (sal_uInt16 nCurExtraRedlinePos = 0; nCurExtraRedlinePos < GetSize(); ++nCurExtraRedlinePos) + { + const SwExtraRedline* pExtraRedline = GetRedline(nCurExtraRedlinePos); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExtraRedline")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*pExtraRedline).name())); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); +} + +#if OSL_DEBUG_LEVEL > 0 +static bool CheckPosition( const SwPosition* pStt, const SwPosition* pEnd ) +{ + int nError = 0; + SwNode* pSttNode = &pStt->GetNode(); + SwNode* pEndNode = &pEnd->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& rDoc, const SwTable& rTable, bool bSaveInUndo, RedlineType nRedlineTypeToDelete ) +{ + bool bChg = false; + + if (bSaveInUndo && rDoc.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 + } + } + } + ++nCurRedlinePos; + } + + if( bChg ) + rDoc.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 sw::BroadcastingModify& rMod, const SwRootFrame* pLayout, + SwFrameType const nFrameType, const Point* pPoint) +{ + SwIterator<SwFrame, sw::BroadcastingModify, 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(); + + // Also empty the text portion cache, so it gets rebuilt, taking the new redlines + // into account. + if (pTmpFrame->IsTextFrame()) + { + auto pTextFrame = static_cast<SwTextFrame*>(pTmpFrame); + pTextFrame->ClearPara(); + } + } + } + } +} + +void lcl_LOKInvalidateStartEndFrames(SwShellCursor& rCursor) +{ + if (!(rCursor.HasMark() && + rCursor.GetPoint()->GetNode().IsContentNode() && + rCursor.GetPoint()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()) && + (rCursor.GetMark()->GetNode() == rCursor.GetPoint()->GetNode() || + (rCursor.GetMark()->GetNode().IsContentNode() && + rCursor.GetMark()->GetNode().GetContentNode()->getLayoutFrame(rCursor.GetShell()->GetLayout()))))) + { + return; + } + + auto [pStartPos, pEndPos] = rCursor.StartEnd(); // SwPosition* + + lcl_LOKInvalidateFrames(*(pStartPos->GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetSttPos()); + + lcl_LOKInvalidateFrames(*(pEndPos->GetNode().GetContentNode()), + rCursor.GetShell()->GetLayout(), + FRM_CNTNT, &rCursor.GetEndPos()); +} + +bool lcl_LOKRedlineNotificationEnabled() +{ + static bool bDisableRedlineComments = getenv("DISABLE_REDLINE") != nullptr; + if (comphelper::LibreOfficeKit::isActive() && !bDisableRedlineComments) + return true; + + return false; +} + +} // anonymous namespace + +void SwRedlineTable::setMovedIDIfNeeded(sal_uInt32 nMax) +{ + if (nMax > m_nMaxMovedID) + m_nMaxMovedID = nMax; +} + +/// 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. + if (!lcl_LOKRedlineNotificationEnabled()) + 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()); + + auto [pStartPos, pEndPos] = pRedline->StartEnd(); // SwPosition* + SwContentNode* pContentNd = pRedline->GetPointContentNode(); + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + if (pView && pContentNd) + { + SwShellCursor aCursor(pView->GetWrtShell(), *pStartPos); + aCursor.SetMark(); + *aCursor.GetMark() = *pEndPos; + + 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& rDoc = pRedline->GetDoc(); + SwViewShell* pSh; + if( !rDoc.IsInDtor() ) + { + pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh ) + for(SwNodeIndex nIdx(pStartPos->GetNode()); nIdx <= pEndPos->GetNode(); ++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) + { + if (pView && pView->GetDocId() == pViewShell->GetDocId()) + pViewShell->libreOfficeKitViewCallback(nType == RedlineNotification::Modify ? LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED : LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED, OString(aPayload)); + 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); + + // detect text moving by checking nearby redlines, except during Undo + // (apply isMoved() during OpenDocument and DOCX import, too, to fix + // missing text moving handling in ODF and e.g. web version of MSO) + if ( p->GetDoc().GetIDocumentUndoRedo().DoesUndo() || + p->GetDoc().IsInWriterfilterImport() || + p->GetDoc().IsInXMLImport() ) + { + isMoved(nP); + } + + p->CallDisplayFunc(nP); + if (rv.second) + CheckOverlapping(rv.first); + return rv.second; + } + return InsertWithValidRanges( p ); +} + +void SwRedlineTable::CheckOverlapping(vector_type::const_iterator it) +{ + if (m_bHasOverlappingElements) + return; + if (maVector.size() <= 1) // a single element cannot be overlapping + return; + auto pCurr = *it; + auto itNext = it + 1; + if (itNext != maVector.end()) + { + auto pNext = *itNext; + if (pCurr->End()->GetNodeIndex() >= pNext->Start()->GetNodeIndex()) + { + m_bHasOverlappingElements = true; + return; + } + } + if (it != maVector.begin()) + { + auto pPrev = *(it - 1); + if (pPrev->End()->GetNodeIndex() >= pCurr->Start()->GetNodeIndex()) + m_bHasOverlappingElements = true; + } +} + +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); + if (rv.second) + CheckOverlapping(rv.first); + return rv.second; + } + return InsertWithValidRanges( p, &rP ); +} + +namespace sw { + +std::vector<std::unique_ptr<SwRangeRedline>> GetAllValidRanges(std::unique_ptr<SwRangeRedline> p) +{ + std::vector<std::unique_ptr<SwRangeRedline>> ret; + // Create valid "sub-ranges" from the Selection + auto [pStt, pEnd] = p->StartEnd(); // SwPosition* + SwPosition aNewStt( *pStt ); + SwNodes& rNds = aNewStt.GetNodes(); + SwContentNode* pC; + + if( !aNewStt.GetNode().IsContentNode() ) + { + pC = rNds.GoNext( &aNewStt ); + if( !pC ) + aNewStt.Assign(rNds.GetEndOfContent()); + } + + + if( aNewStt >= *pEnd ) + return ret; + + std::unique_ptr<SwRangeRedline> pNew; + do { + if( !pNew ) + pNew.reset(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()->GetNode().StartOfSectionNode()->FindTableNode(); + // We end in a table when pTab != 0 + if( pTab && !pNew->GetMark()->GetNode().StartOfSectionNode()->FindTableNode() ) + { // but our Mark was outside the table => Correction + do + { + // We want to be before the table + pNew->GetPoint()->Assign(*pTab); + pC = GoPreviousPos( pNew->GetPoint(), false ); // here we are. + if( pC ) + pNew->GetPoint()->SetContent( 0 ); + pTab = pNew->GetPoint()->GetNode().StartOfSectionNode()->FindTableNode(); + } while( pTab ); // If there is another table we have to repeat our step backwards + } + + // insert dummy character to the empty table rows to keep their changes + SwNode& rBoxNode = pNew->GetMark()->GetNode(); + if ( rBoxNode.GetDoc().GetIDocumentUndoRedo().DoesUndo() && rBoxNode.GetTableBox() && + rBoxNode.GetTableBox()->GetUpper()->IsEmpty() && rBoxNode.GetTextNode() ) + { + ::sw::UndoGuard const undoGuard(rBoxNode.GetDoc().GetIDocumentUndoRedo()); + rBoxNode.GetTextNode()->InsertDummy(); + pNew->GetMark()->SetContent( 1 ); + } + + if( *pNew->GetPoint() > *pEnd ) + { + pC = nullptr; + if( aNewStt.GetNode() != pEnd->GetNode() ) + do { + SwNode& rCurNd = aNewStt.GetNode(); + if( rCurNd.IsStartNode() ) + { + if( rCurNd.EndOfSectionIndex() < pEnd->GetNodeIndex() ) + aNewStt.Assign( *rCurNd.EndOfSectionNode() ); + else + break; + } + else if( rCurNd.IsContentNode() ) + pC = rCurNd.GetContentNode(); + aNewStt.Adjust(SwNodeOffset(1)); + } while( aNewStt.GetNodeIndex() < pEnd->GetNodeIndex() ); + + if( aNewStt.GetNode() == pEnd->GetNode() ) + aNewStt.SetContent(pEnd->GetContentIndex()); + else if( pC ) + { + aNewStt.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(std::move(pNew)); + } + + if( aNewStt >= *pEnd ) + break; + pC = rNds.GoNext( &aNewStt ); + if( !pC ) + break; + } while( aNewStt < *pEnd ); + + return ret; +} + +} // namespace sw + +static void lcl_setRowNotTracked(SwNode& rNode) +{ + SwDoc& rDoc = rNode.GetDoc(); + if ( rDoc.GetIDocumentUndoRedo().DoesUndo() && rNode.GetTableBox() ) + { + SvxPrintItem aSetTracking(RES_PRINT, false); + SwNodeIndex aInsPos( *(rNode.GetTableBox()->GetSttNd()), 1); + SwCursor aCursor( SwPosition(aInsPos), nullptr ); + ::sw::UndoGuard const undoGuard(rNode.GetDoc().GetIDocumentUndoRedo()); + rDoc.SetRowNotTracked( aCursor, aSetTracking ); + } +} + +bool SwRedlineTable::InsertWithValidRanges(SwRangeRedline*& p, size_type* pInsPos) +{ + bool bAnyIns = false; + bool bInsert = RedlineType::Insert == p->GetType(); + SwNode* pSttNode = &p->Start()->GetNode(); + + std::vector<std::unique_ptr<SwRangeRedline>> redlines( + GetAllValidRanges(std::unique_ptr<SwRangeRedline>(p))); + + // tdf#147180 set table change tracking in the empty row with text insertion + if ( bInsert ) + lcl_setRowNotTracked(*pSttNode); + + for (std::unique_ptr<SwRangeRedline> & pRedline : redlines) + { + assert(pRedline->HasValidRange()); + size_type nInsPos; + auto pTmpRedline = pRedline.release(); + if (Insert(pTmpRedline, nInsPos)) + { + // tdf#147180 set table tracking to the table row + lcl_setRowNotTracked(pTmpRedline->GetPointNode()); + + pTmpRedline->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_at(maVector.size() - 1); + LOKRedlineNotification(RedlineNotification::Remove, pRedline); + delete pRedline; + } + m_bHasOverlappingElements = false; +} + +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 constexpr 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 constexpr 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() ) + { + auto [pRStt, pREnd] = pTmp->StartEnd(); // SwPosition* + if( bNext ? *pRStt <= rSttPos : *pRStt < rSttPos ) + { + if( bNext ? *pREnd > rSttPos : *pREnd >= rSttPos ) + { + pFnd = pTmp; + break; + } + } + else + break; + } + } + return pFnd; +} + +namespace +{ +bool lcl_CanCombineWithRange(SwRangeRedline* pOrigin, SwRangeRedline* pActual, + SwRangeRedline* pOther, bool bReverseDir, bool bCheckChilds) +{ + if (pOrigin->IsVisible() != pOther->IsVisible()) + return false; + + if (bReverseDir) + { + if (*(pOther->End()) != *(pActual->Start())) + return false; + } + else + { + if (*(pActual->End()) != *(pOther->Start())) + return false; + } + + if (!pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(0))) + { + if (!bCheckChilds || pOther->GetStackCount() <= 1 + || !pOrigin->GetRedlineData(0).CanCombineForAcceptReject(pOther->GetRedlineData(1))) + return false; + } + if (pOther->Start()->GetNode().StartOfSectionNode() + != pActual->Start()->GetNode().StartOfSectionNode()) + return false; + + return true; +} +} + +void SwRedlineTable::getConnectedArea(size_type nPosOrigin, size_type& rPosStart, + size_type& rPosEnd, bool bCheckChilds) const +{ + // Keep the original redline .. else we should memorize which children was checked + // at the last combined redline. + SwRangeRedline* pOrigin = (*this)[nPosOrigin]; + rPosStart = nPosOrigin; + rPosEnd = nPosOrigin; + SwRangeRedline* pRedline = pOrigin; + SwRangeRedline* pOther; + + // connection info is already here..only the actual text is missing at import time + // so no need to check Redline->GetContentIdx() here yet. + while (rPosStart > 0 && (pOther = (*this)[rPosStart - 1]) + && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, true, bCheckChilds)) + { + rPosStart--; + pRedline = pOther; + } + pRedline = pOrigin; + while (rPosEnd + 1 < size() && (pOther = (*this)[rPosEnd + 1]) + && lcl_CanCombineWithRange(pOrigin, pRedline, pOther, false, bCheckChilds)) + { + rPosEnd++; + pRedline = pOther; + } +} + +OUString SwRedlineTable::getTextOfArea(size_type rPosStart, size_type rPosEnd) const +{ + // Normally a SwPaM::GetText() would be enough with rPosStart-start and rPosEnd-end + // But at import time some text is not present there yet + // we have to collect them 1 by 1 + + OUString sRet = ""; + + for (size_type nIdx = rPosStart; nIdx <= rPosEnd; ++nIdx) + { + SwRangeRedline* pRedline = (*this)[nIdx]; + bool bStartWithNonTextNode = false; + + SwPaM *pPaM; + bool bDeletePaM = false; + if (nullptr == pRedline->GetContentIdx()) + { + pPaM = pRedline; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), + *pRedline->GetContentIdx()->GetNode().EndOfSectionNode()); + if (!pPaM->Start()->nNode.GetNode().GetTextNode()) + { + bStartWithNonTextNode = true; + } + bDeletePaM = true; + } + const OUString sNew = pPaM->GetText(); + + if (bStartWithNonTextNode && + sNew[0] == CH_TXTATR_NEWLINE) + { + sRet += pPaM->GetText().subView(1); + } + else + sRet += pPaM->GetText(); + if (bDeletePaM) + delete pPaM; + } + + return sRet; +} + +bool SwRedlineTable::isMoved(size_type rPos) const +{ + // If it is already a part of a movement, then don't check it. + if ((*this)[rPos]->GetMoved() != 0) + return false; + // First try with single redline. then try with combined redlines + if (isMovedImpl(rPos, false)) + return true; + else + return isMovedImpl(rPos, true); +} + +bool SwRedlineTable::isMovedImpl(size_type rPos, bool bTryCombined) const +{ + bool bRet = false; + auto constexpr nLookahead = 20; + SwRangeRedline* pRedline = (*this)[ rPos ]; + + // set redline type of the searched pair + RedlineType nPairType = pRedline->GetType(); + if ( RedlineType::Delete == nPairType ) + nPairType = RedlineType::Insert; + else if ( RedlineType::Insert == nPairType ) + nPairType = RedlineType::Delete; + else + // only deleted or inserted text can be moved + return false; + + bool bDeletePaM = false; + SwPaM* pPaM = nullptr; + OUString sTrimmed; + SwRedlineTable::size_type nPosStart = rPos; + SwRedlineTable::size_type nPosEnd = rPos; + + if (bTryCombined) + { + getConnectedArea(rPos, nPosStart, nPosEnd, false); + if (nPosStart != nPosEnd) + sTrimmed = getTextOfArea(nPosStart, nPosEnd).trim(); + } + + if (sTrimmed.isEmpty()) + { + // if this redline is visible the content is in this PaM + if (nullptr == pRedline->GetContentIdx()) + { + pPaM = pRedline; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + pPaM = new SwPaM(pRedline->GetContentIdx()->GetNode(), + *pRedline->GetContentIdx()->GetNode().EndOfSectionNode()); + bDeletePaM = true; + } + + sTrimmed = pPaM->GetText().trim(); + } + + // detection of move needs at least 6 characters with an inner + // space after stripping white spaces of the redline to skip + // frequent deleted and inserted articles or other common + // word parts, e.g. 'the' and 'of a' to detect as text moving + if (sTrimmed.getLength() < 6 || sTrimmed.indexOf(' ') == -1) + { + if (bDeletePaM) + delete pPaM; + return false; + } + + // Todo: lessen the previous condition..: + // if the source / destination is a whole node change then maybe space is not needed + + // search pair around the actual redline + size_type nEnd = rPos + nLookahead < size() + ? rPos + nLookahead + : size(); + size_type nStart = rPos > nLookahead ? rPos - nLookahead : 0; + // first, try to compare to single redlines + // next, try to compare to combined redlines + for (int nPass = 0; nPass < 2 && !bRet; nPass++) + { + for (size_type nPosAct = nStart; nPosAct < nEnd && !bRet; ++nPosAct) + { + SwRangeRedline* pPair = (*this)[nPosAct]; + + // redline must be the requested type and from the same author + if (nPairType != pPair->GetType() || pRedline->GetAuthor() != pPair->GetAuthor()) + { + continue; + } + + bool bDeletePairPaM = false; + SwPaM* pPairPaM = nullptr; + + OUString sPairTrimmed = ""; + SwRedlineTable::size_type nPairStart = nPosAct; + SwRedlineTable::size_type nPairEnd = nPosAct; + + if (nPass == 0) + { + // if this redline is visible the content is in this PaM + if (nullptr == pPair->GetContentIdx()) + { + pPairPaM = pPair; + } + else // otherwise it is saved in pContentSect, e.g. during ODT import + { + // saved in pContentSect, e.g. during ODT import + pPairPaM = new SwPaM(pPair->GetContentIdx()->GetNode(), + *pPair->GetContentIdx()->GetNode().EndOfSectionNode()); + bDeletePairPaM = true; + } + + sPairTrimmed = o3tl::trim(pPairPaM->GetText()); + } + else + { + getConnectedArea(nPosAct, nPairStart, nPairEnd, false); + if (nPairStart != nPairEnd) + sPairTrimmed = getTextOfArea(nPairStart, nPairEnd).trim(); + } + + // pair at tracked moving: same text by trimming trailing white spaces + if (abs(sTrimmed.getLength() - sPairTrimmed.getLength()) <= 2 + && sTrimmed == sPairTrimmed) + { + sal_uInt32 nMID = getNewMovedID(); + if (nPosStart != nPosEnd) + { + for (size_type nIdx = nPosStart; nIdx <= nPosEnd; ++nIdx) + { + (*this)[nIdx]->SetMoved(nMID); + if (nIdx != rPos) + (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + } + else + pRedline->SetMoved(nMID); + + //in (nPass == 0) it will only call once .. as nPairStart == nPairEnd == nPosAct + for (size_type nIdx = nPairStart; nIdx <= nPairEnd; ++nIdx) + { + (*this)[nIdx]->SetMoved(nMID); + (*this)[nIdx]->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + + bRet = true; + } + + if (bDeletePairPaM) + delete pPairPaM; + + //we can skip the combined redlines + if (nPass == 1) + nPosAct = nPairEnd; + } + } + + if ( bDeletePaM ) + delete pPaM; + + return bRet; +} + +void SwRedlineTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineTable")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + for (SwRedlineTable::size_type nCurRedlinePos = 0; nCurRedlinePos < size(); ++nCurRedlinePos) + operator[](nCurRedlinePos)->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SwRedlineExtraData::~SwRedlineExtraData() +{ +} + +void SwRedlineExtraData::Reject( SwPaM& ) const +{ +} + +bool SwRedlineExtraData::operator == ( const SwRedlineExtraData& ) const +{ + return false; +} + +SwRedlineExtraData_FormatColl::SwRedlineExtraData_FormatColl( OUString aColl, + sal_uInt16 nPoolFormatId, + const SfxItemSet* pItemSet, + bool bFormatAll ) + : m_sFormatNm(std::move(aColl)), 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& rDoc = rPam.GetDoc(); + + // What about Undo? Is it turned off? + SwTextFormatColl* pColl = USHRT_MAX == m_nPoolId + ? rDoc.FindTextFormatCollByName( m_sFormatNm ) + : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( m_nPoolId ); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + SwPaM aPam( *rPam.GetMark(), *rPam.GetPoint() ); + + const SwPosition* pEnd = rPam.End(); + + if ( !m_bFormatAll || pEnd->GetContentIndex() == 0 ) + { + // don't reject the format of the next paragraph (that is handled by the next redline) + if (aPam.GetPoint()->GetNode() > aPam.GetMark()->GetNode()) + { + aPam.GetPoint()->Adjust(SwNodeOffset(-1)); + SwContentNode* pNode = aPam.GetPoint()->GetNode().GetContentNode(); + if ( pNode ) + aPam.GetPoint()->SetContent( pNode->Len() ); + else + // tdf#147507 set it back to a content node to avoid of crashing + aPam.GetPoint()->Adjust(SwNodeOffset(+1)); + } + else if (aPam.GetPoint()->GetNode() < aPam.GetMark()->GetNode()) + { + aPam.GetMark()->Adjust(SwNodeOffset(-1)); + SwContentNode* pNode = aPam.GetMark()->GetNode().GetContentNode(); + aPam.GetMark()->SetContent( pNode->Len() ); + } + } + + if( pColl ) + rDoc.SetTextFormatColl( aPam, pColl, false ); + + if( m_pSet ) + rDoc.getIDocumentContentOperations().InsertItemSet( aPam, *m_pSet ); + + rDoc.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& rDoc = rPam.GetDoc(); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags(RedlineFlags::On | RedlineFlags::Ignore)); + + // Actually we need to reset the Attribute here! + for( const auto& rWhichId : m_aWhichIds ) + { + rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, *GetDfltAttr( rWhichId ), + SetAttrMode::DONTEXPAND ); + } + + rDoc.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, sal_uInt32 nMovedID ) + : m_pNext( nullptr ), m_pExtraData( nullptr ), + m_aStamp( DateTime::SYSTEM ), + m_nAuthor( nAut ), m_eType( eT ), m_nSeqNo( 0 ), m_bAutoFormat(false), m_nMovedID(nMovedID) +{ + 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_nAuthor( rCpy.m_nAuthor ) + , m_eType( rCpy.m_eType ) + , m_nSeqNo( rCpy.m_nSeqNo ) + , m_bAutoFormat(false) + , m_nMovedID( rCpy.m_nMovedID ) +{ +} + +// For sw3io: We now own pNext! +SwRedlineData::SwRedlineData(RedlineType eT, std::size_t nAut, const DateTime& rDT, + sal_uInt32 nMovedID, OUString aCmnt, SwRedlineData *pNxt) + : m_pNext(pNxt), m_pExtraData(nullptr), m_sComment(std::move(aCmnt)), m_aStamp(rDT), + m_nAuthor(nAut), m_eType(eT), m_nSeqNo(0), m_bAutoFormat(false), m_nMovedID(nMovedID) +{ +} + +SwRedlineData::~SwRedlineData() +{ + delete m_pExtraData; + delete m_pNext; +} + +// Check whether the absolute difference between the two dates is no larger than one minute (can +// give inaccurate results if at least one of the dates is not valid/normalized): +static bool deltaOneMinute(DateTime const & t1, DateTime const & t2) { + auto const & [min, max] = std::minmax(t1, t2); + // Avoid overflow of `min + tools::Time(0, 1)` below when min is close to the maximum valid + // DateTime: + if (min >= DateTime({31, 12, std::numeric_limits<sal_Int16>::max()}, {23, 59})) { + return true; + } + return max <= min + tools::Time(0, 1); +} + +bool SwRedlineData::CanCombine(const SwRedlineData& rCmp) const +{ + return m_nAuthor == rCmp.m_nAuthor && + m_eType == rCmp.m_eType && + m_sComment == rCmp.m_sComment && + deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) && + m_nMovedID == rCmp.m_nMovedID && + (( !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 )); +} + +// Check if we could/should accept/reject the 2 redlineData at the same time. +// No need to check its children equality +bool SwRedlineData::CanCombineForAcceptReject(const SwRedlineData& rCmp) const +{ + return m_nAuthor == rCmp.m_nAuthor && + m_eType == rCmp.m_eType && + m_sComment == rCmp.m_sComment && + deltaOneMinute(GetTimeStamp(), rCmp.GetTimeStamp()) && + m_nMovedID == rCmp.m_nMovedID && + (( !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; +} + +const TranslateId 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())]); +} + +void SwRedlineData::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRedlineData")); + + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(GetSeqNo()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("author"), BAD_CAST(SW_MOD()->GetRedlineAuthor(GetAuthor()).toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"), BAD_CAST(DateTimeToOString(GetTimeStamp()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("descr"), BAD_CAST(GetDescr().toUtf8().getStr())); + + OString sRedlineType; + switch (GetType()) + { + case RedlineType::Insert: + sRedlineType = "REDLINE_INSERT"_ostr; + break; + case RedlineType::Delete: + sRedlineType = "REDLINE_DELETE"_ostr; + break; + case RedlineType::Format: + sRedlineType = "REDLINE_FORMAT"_ostr; + break; + default: + sRedlineType = "UNKNOWN"_ostr; + break; + } + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), BAD_CAST(sRedlineType.getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("moved"), BAD_CAST(OString::number(m_nMovedID).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +sal_uInt32 SwRangeRedline::s_nLastId = 1; + +SwRangeRedline::SwRangeRedline(RedlineType eTyp, const SwPaM& rPam, sal_uInt32 nMovedID ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), m_pRedlineData( + new SwRedlineData(eTyp, GetDoc().getIDocumentRedlineAccess().GetRedlineAuthor(), nMovedID ) ) + , + m_nId( s_nLastId++ ) +{ + GetBound().SetRedline(this); + GetBound(false).SetRedline(this); + + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rPam.HasMark() ) + DeleteMark(); + + // set default comment for single annotations added or deleted + if ( IsAnnotation() ) + { + SetComment( RedlineType::Delete == eTyp + ? SwResId(STR_REDLINE_COMMENT_DELETED) + : SwResId(STR_REDLINE_COMMENT_ADDED) ); + } +} + +SwRangeRedline::SwRangeRedline( const SwRedlineData& rData, const SwPaM& rPam ) + : SwPaM( *rPam.GetMark(), *rPam.GetPoint() ), + m_pRedlineData( new SwRedlineData( rData )), + m_nId( s_nLastId++ ) +{ + GetBound().SetRedline(this); + GetBound(false).SetRedline(this); + + 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_nId( s_nLastId++ ) +{ + GetBound().SetRedline(this); + GetBound(false).SetRedline(this); + + m_bDelLastPara = false; + m_bIsVisible = true; +} + +SwRangeRedline::SwRangeRedline( const SwRangeRedline& rCpy ) + : SwPaM( *rCpy.GetMark(), *rCpy.GetPoint() ), + m_pRedlineData( new SwRedlineData( *rCpy.m_pRedlineData )), + m_nId( s_nLastId++ ) +{ + GetBound().SetRedline(this); + GetBound(false).SetRedline(this); + + m_bDelLastPara = false; + m_bIsVisible = true; + if( !rCpy.HasMark() ) + DeleteMark(); +} + +SwRangeRedline::~SwRangeRedline() +{ + if( m_oContentSect ) + { + // delete the ContentSection + if( !GetDoc().IsInDtor() ) + GetDoc().getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() ); + m_oContentSect.reset(); + } + delete m_pRedlineData; +} + +void MaybeNotifyRedlineModification(SwRangeRedline& rRedline, SwDoc& rDoc) +{ + if (!lcl_LOKRedlineNotificationEnabled()) + return; + + const SwRedlineTable& rRedTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type i = 0; i < rRedTable.size(); ++i) + { + if (rRedTable[i] == &rRedline) + { + SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, &rRedline); + break; + } + } +} + +void SwRangeRedline::MaybeNotifyRedlinePositionModification(tools::Long nTop) +{ + if (!lcl_LOKRedlineNotificationEnabled()) + 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()->GetNode(), + * pMkNd = &GetMark()->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, bool bForced) +{ + SwDoc& rDoc = GetDoc(); + + bool bIsShowChangesInMargin = false; + if ( !bForced ) + { + SwViewShell* pSh = rDoc.getIDocumentLayoutAccess().GetCurrentViewShell(); + if (pSh) + bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin(); + else + bIsShowChangesInMargin = SW_MOD()->GetUsrPref(false)->IsShowChangesInMargin(); + } + + if( 1 > nLoop && !bIsShowChangesInMargin ) + return; + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(rDoc.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 = !bIsShowChangesInMargin; + + if (m_bIsVisible) + MoveFromSection(nMyPos); + else + { + 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 + InvalidateRange(Invalidation::Add); + break; + default: + break; + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwRangeRedline::Hide(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/) +{ + SwDoc& rDoc = GetDoc(); + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(rDoc.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; + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwRangeRedline::ShowOriginal(sal_uInt16 nLoop, size_t nMyPos, bool /*bForced*/) +{ + SwDoc& rDoc = GetDoc(); + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + SwRedlineData* pCur; + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld | RedlineFlags::Ignore); + ::sw::UndoGuard const undoGuard(rDoc.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; + } + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +// trigger the Layout +void SwRangeRedline::InvalidateRange(Invalidation const eWhy) +{ + auto [pRStt, pREnd] = StartEnd(); // SwPosition* + SwNodeOffset nSttNd = pRStt->GetNodeIndex(), + nEndNd = pREnd->GetNodeIndex(); + sal_Int32 nSttCnt = pRStt->GetContentIndex(); + sal_Int32 nEndCnt = pREnd->GetContentIndex(); + + SwNodes& rNds = GetDoc().GetNodes(); + for (SwNodeOffset 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->TriggerNodeUpdate(sw::LegacyModifyHint(&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( SwNodeOffset nNdIdx, sal_Int32& rStart, sal_Int32& rEnd ) const +{ + auto [pRStt, pREnd] = StartEnd(); // SwPosition* + if( pRStt->GetNodeIndex() < nNdIdx ) + { + if( pREnd->GetNodeIndex() > nNdIdx ) + { + rStart = 0; // Paragraph is completely enclosed + rEnd = COMPLETE_STRING; + } + else if (pREnd->GetNodeIndex() == nNdIdx) + { + rStart = 0; // Paragraph is overlapped in the beginning + rEnd = pREnd->GetContentIndex(); + } + else // redline ends before paragraph + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } + } + else if( pRStt->GetNodeIndex() == nNdIdx ) + { + rStart = pRStt->GetContentIndex(); + if( pREnd->GetNodeIndex() == nNdIdx ) + rEnd = pREnd->GetContentIndex(); // Within the Paragraph + else + rEnd = COMPLETE_STRING; // Paragraph is overlapped in the end + } + else + { + rStart = COMPLETE_STRING; + rEnd = COMPLETE_STRING; + } +} + +static void lcl_storeAnnotationMarks(SwDoc& rDoc, const SwPosition* pStt, const SwPosition* pEnd) +{ + // tdf#115815 keep original start position of collapsed annotation ranges + // as temporary bookmarks (removed after file saving and file loading) + IDocumentMarkAccess& rDMA(*rDoc.getIDocumentMarkAccess()); + for (auto iter = rDMA.getAnnotationMarksBegin(); + iter != rDMA.getAnnotationMarksEnd(); ) + { + SwPosition const& rStartPos((**iter).GetMarkStart()); + if ( *pStt <= rStartPos && rStartPos < *pEnd ) + { + IDocumentMarkAccess::const_iterator_t pOldMark = + rDMA.findAnnotationBookmark((**iter).GetName()); + if ( pOldMark == rDMA.getBookmarksEnd() ) + { + // at start of redlines use a 1-character length bookmark range + // instead of a 0-character length bookmark position to avoid its losing + sal_Int32 nLen = (*pStt == rStartPos) ? 1 : 0; + SwPaM aPam( rStartPos.GetNode(), rStartPos.GetContentIndex(), + rStartPos.GetNode(), rStartPos.GetContentIndex() + nLen); + ::sw::mark::IMark* pMark = rDMA.makeAnnotationBookmark( + aPam, + (**iter).GetName(), + IDocumentMarkAccess::MarkType::BOOKMARK, sw::mark::InsertMode::New); + ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark); + if (pBookmark) + { + pBookmark->SetKeyCode(vcl::KeyCode()); + pBookmark->SetShortName(OUString()); + } + } + } + ++iter; + } +} + +void SwRangeRedline::MoveToSection() +{ + if( !m_oContentSect ) + { + auto [pStt, pEnd] = StartEnd(); // SwPosition* + + SwDoc& rDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = rDoc.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 = rDoc.GetNodes(); + if( pCSttNd || pCEndNd ) + { + SwTextFormatColl* pColl = (pCSttNd && pCSttNd->IsTextNode() ) + ? pCSttNd->GetTextNode()->GetTextColl() + : (pCEndNd && pCEndNd->IsTextNode() ) + ? pCEndNd->GetTextNode()->GetTextColl() + : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(), + SwNormalStartNode, pColl ); + SwTextNode* pTextNd = rNds[ pSttNd->GetIndex() + 1 ]->GetTextNode(); + + SwPosition aPos( *pTextNd ); + if( pCSttNd && pCEndNd ) + { + // tdf#140982 keep annotation ranges in deletions in margin mode + lcl_storeAnnotationMarks( rDoc, pStt, pEnd ); + rDoc.getIDocumentContentOperations().MoveAndJoin( aPam, aPos ); + } + else + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() ); + + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::DEFAULT ); + } + m_oContentSect.emplace( *pSttNd ); + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); + } + else + InvalidateRange(Invalidation::Remove); +} + +void SwRangeRedline::CopyToSection() +{ + if( m_oContentSect ) + return; + + auto [pStt, pEnd] = StartEnd(); // SwPosition* + + SwContentNode* pCSttNd = pStt->GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode(); + + SwStartNode* pSttNd; + SwDoc& rDoc = GetDoc(); + SwNodes& rNds = rDoc.GetNodes(); + + bool bSaveCopyFlag = rDoc.IsCopyIsMove(), + bSaveRdlMoveFlg = rDoc.getIDocumentRedlineAccess().IsRedlineMove(); + rDoc.SetCopyIsMove( true ); + + // The IsRedlineMove() flag causes the behaviour of the + // DocumentContentOperationsManager::CopyFlyInFlyImpl() method to change, + // which will eventually be called by the CopyRange() below. + rDoc.getIDocumentRedlineAccess().SetRedlineMove(true); + + if( pCSttNd ) + { + SwTextFormatColl* pColl = pCSttNd->IsTextNode() + ? pCSttNd->GetTextNode()->GetTextColl() + : rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_STANDARD); + + pSttNd = rNds.MakeTextSection( rNds.GetEndOfRedlines(), + SwNormalStartNode, pColl ); + + SwPosition aPos( *pSttNd, SwNodeOffset(1) ); + + // tdf#115815 keep original start position of collapsed annotation ranges + // as temporary bookmarks (removed after file saving and file loading) + lcl_storeAnnotationMarks( rDoc, pStt, pEnd ); + rDoc.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.GetNode().GetContentNode(); + if( pDestNd ) + { + if( pDestNd->IsTextNode() && pCEndNd->IsTextNode() ) + pCEndNd->GetTextNode()->CopyCollFormat(*pDestNd->GetTextNode()); + else + pDestNd->ChgFormatColl( pCEndNd->GetFormatColl() ); + } + } + } + else + { + pSttNd = SwNodes::MakeEmptySection( rNds.GetEndOfRedlines() ); + + if( pCEndNd ) + { + SwPosition aPos( *pSttNd->EndOfSectionNode() ); + rDoc.getIDocumentContentOperations().CopyRange(*this, aPos, SwCopyFlags::CheckPosInFly); + } + else + { + SwNodeRange aRg( pStt->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(1) ); + rDoc.GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, *pSttNd->EndOfSectionNode()); + } + } + m_oContentSect.emplace( *pSttNd ); + + rDoc.SetCopyIsMove( bSaveCopyFlag ); + rDoc.getIDocumentRedlineAccess().SetRedlineMove( bSaveRdlMoveFlg ); +} + +void SwRangeRedline::DelCopyOfSection(size_t nMyPos) +{ + if( !m_oContentSect ) + return; + + auto [pStt, pEnd] = StartEnd(); // SwPosition* + + SwDoc& rDoc = GetDoc(); + SwPaM aPam( *pStt, *pEnd ); + SwContentNode* pCSttNd = pStt->GetNode().GetContentNode(); + SwContentNode* pCEndNd = pEnd->GetNode().GetContentNode(); + + if( !pCSttNd ) + { + // In order to not move other Redlines' indices, we set them + // to the end (is exclusive) + const SwRedlineTable& rTable = rDoc.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? + rDoc.getIDocumentContentOperations().DeleteAndJoin(aPam/*, true*/); + } + else if( pCSttNd || pCEndNd ) + { + if( pCSttNd && !pCEndNd ) + m_bDelLastPara = true; + rDoc.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 = rDoc.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.DeleteMark(); + aPam.GetPoint()->SetContent(0);; + rDoc.getIDocumentContentOperations().DelFullPara( aPam ); + } + } + else + { + rDoc.getIDocumentContentOperations().DeleteRange( aPam ); + } + + if( pStt == GetPoint() ) + Exchange(); + + DeleteMark(); +} + +void SwRangeRedline::MoveFromSection(size_t nMyPos) +{ + if( m_oContentSect ) + { + SwDoc& rDoc = GetDoc(); + const SwRedlineTable& rTable = rDoc.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_oContentSect->GetNode() ); // #i95711# + { + SwPaM aPam( m_oContentSect->GetNode(), + *m_oContentSect->GetNode().EndOfSectionNode(), SwNodeOffset(1), + SwNodeOffset( m_bDelLastPara ? -2 : -1 ) ); + SwContentNode* pCNd = aPam.GetPointContentNode(); + if( pCNd ) + aPam.GetPoint()->SetContent( pCNd->Len() ); + else + aPam.GetPoint()->Adjust(SwNodeOffset(+1)); + + SwFormatColl* pColl = pCNd && pCNd->Len() && aPam.GetPoint()->GetNode() != + aPam.GetMark()->GetNode() + ? pCNd->GetFormatColl() : nullptr; + + SwNodeIndex aNdIdx( GetPoint()->GetNode(), -1 ); + const sal_Int32 nPos = GetPoint()->GetContentIndex(); + + SwPosition aPos( *GetPoint() ); + if( m_bDelLastPara && *aPam.GetPoint() == *aPam.GetMark() ) + { + aPos.Adjust(SwNodeOffset(-1)); + + rDoc.getIDocumentContentOperations().AppendTextNode( aPos ); + } + else + { + rDoc.getIDocumentContentOperations().MoveRange( aPam, aPos, + SwMoveFlags::ALLFLYS ); + } + + SetMark(); + *GetPoint() = aPos; + GetMark()->Assign(aNdIdx.GetIndex() + 1); + pCNd = GetMark()->GetNode().GetContentNode(); + if( pCNd ) + GetMark()->SetContent( nPos ); + + if( m_bDelLastPara ) + { + GetPoint()->Adjust(SwNodeOffset(+1)); + pCNd = GetPointContentNode(); + m_bDelLastPara = false; + } + else if( pColl ) + pCNd = GetPointContentNode(); + + 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_oContentSect->GetNode() == pKeptContentSectNode ) + { + rDoc.getIDocumentContentOperations().DeleteSection( &m_oContentSect->GetNode() ); + } + m_oContentSect.reset(); + + // 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& rIdx ) +{ + if( !m_oContentSect ) + { + m_oContentSect = rIdx; + m_bIsVisible = false; + } + else + { + OSL_FAIL("SwRangeRedline::SetContentIdx: invalid state"); + } +} + +// for Undo +void SwRangeRedline::ClearContentIdx() +{ + if( m_oContentSect ) + { + m_oContentSect.reset(); + } + else + { + OSL_FAIL("SwRangeRedline::ClearContentIdx: 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; +} + +bool SwRangeRedline::PopAllDataAfter(int depth) +{ + assert(depth > 0); + SwRedlineData* pCur = m_pRedlineData; + while (depth > 1) + { + pCur = pCur->m_pNext; + if (!pCur) + return false; + depth--; + } + + while (pCur->m_pNext) + { + SwRedlineData* pToDelete = pCur->m_pNext; + pCur->m_pNext = pToDelete->m_pNext; + delete pToDelete; + } + 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); +} + +sal_uInt32 SwRangeRedline::GetMovedID(sal_uInt16 nPos) const +{ + return GetRedlineData(nPos).m_nMovedID; +} + +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; +} + +bool SwRangeRedline::IsAnnotation() const +{ + return GetText().getLength() == 1 && GetText()[0] == CH_TXTATR_INWORD; +} + +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(bool bSimplified) +{ + // 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 (!m_oContentSect.has_value()) + { + pPaM = this; + } + else // otherwise it is saved in pContentSect + { + pPaM = new SwPaM( m_oContentSect->GetNode(), *m_oContentSect->GetNode().EndOfSectionNode() ); + bDeletePaM = true; + } + + OUString sDescr = DenoteSpecialCharacters(pPaM->GetText().replace('\n', ' '), /*bQuoted=*/!bSimplified); + if (const SwTextNode *pTextNode = pPaM->GetPointNode().GetTextNode()) + { + if (const SwTextAttr* pTextAttr = pTextNode->GetFieldTextAttrAt(pPaM->GetPoint()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default)) + { + sDescr = ( bSimplified ? "" : SwResId(STR_START_QUOTE) ) + + pTextAttr->GetFormatField().GetField()->GetFieldName() + + ( bSimplified ? "" : SwResId(STR_END_QUOTE) ); + } + } + + // replace $1 in description by description of the redlines text + const OUString aTmpStr = ShortenString(sDescr, nUndoStringLength, SwResId(STR_LDOTS)); + + if (!bSimplified) + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aTmpStr); + + aResult = aRewriter.Apply(aResult); + } + else + { + aResult = aTmpStr; + // more shortening + sal_Int32 nPos = aTmpStr.indexOf(SwResId(STR_LDOTS)); + if (nPos > 5) + aResult = aTmpStr.copy(0, nPos + SwResId(STR_LDOTS).getLength()); + } + + if (bDeletePaM) + delete pPaM; + + return aResult; +} + +void SwRangeRedline::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwRangeRedline")); + + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + const SwRedlineData* pRedlineData = m_pRedlineData; + while (pRedlineData) + { + pRedlineData->dumpAsXml(pWriter); + pRedlineData = pRedlineData->Next(); + } + + SwPaM::dumpAsXml(pWriter); + + (void)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 0000000000..1a1b84ffd7 --- /dev/null +++ b/sw/source/core/doc/docruby.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 <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 { + auto [pStt, pEnd] = _pStartCursor->StartEnd(); // SwPosition* + 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 ); + const o3tl::sorted_vector<sal_uInt16> aDelArr{ RES_TXTATR_CJK_RUBY }; + + SwRubyList::size_type nListEntry = 0; + + const SwPaM *_pStartCursor = rPam.GetNext(), + *_pStartCursor2 = _pStartCursor; + bool bCheckEmpty = &rPam != _pStartCursor; + do { + auto [pStt, pEnd] = _pStartCursor->StartEnd(); // SwPosition* + 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()->AdjustContent( -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->GetNode().GetTextNode(); + OUString const& rText = pTNd->GetText(); + sal_Int32 nStart = pPos->GetContentIndex(); + sal_Int32 nEnd = rText.getLength(); + + bool bHasMark = rPam.HasMark(); + if( bHasMark ) + { + // in the same node? + if( rPam.GetMark()->GetNode() == pPos->GetNode() ) + { + // then use that end + const sal_Int32 nTEnd = rPam.GetMark()->GetContentIndex(); + 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->SetContent(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->SetContent(nStart); + } + } + + bool bAlphaNum = false; + sal_Int32 nWordEnd = nEnd; + CharClass& rCC = GetAppCharClass(); + while( nStart < nEnd ) + { + if( pAttr && nStart == pAttr->GetStart() ) + { + pPos->SetContent(nStart); + if( !rPam.HasMark() ) + { + rPam.SetMark(); + pPos->SetContent(pAttr->GetAnyEnd()); + if( pPos->GetContentIndex() > nEnd ) + pPos->SetContent(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, SwCursorSkipMode::Chars ); + nStart = pPos->GetContentIndex(); + } + + nStart = rPam.GetMark()->GetContentIndex(); + rEntry.SetText( rText.copy( nStart, + rPam.GetPoint()->GetContentIndex() - 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 0000000000..d22ab372e3 --- /dev/null +++ b/sw/source/core/doc/docsort.cxx @@ -0,0 +1,933 @@ +/* -*- 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 <IDocumentRedlineAccess.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 <svl/numformat.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; +std::optional<OUString> SwSortElement::xLastAlgorithm; +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; + xLastAlgorithm.reset(); + delete pSortCollator; + pSortCollator = nullptr; + delete pLclData; + pLclData = nullptr; + pDoc = nullptr; + pBox = nullptr; +} + +SwSortElement::~SwSortElement() +{ +} + +double SwSortElement::StrToDouble( std::u16string_view 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& rSrtKey = pOptions->aKeys[ nKey ]; + if( rSrtKey.eSortOrder == SwSortOrder::Ascending ) + { + pOrig = this; + pCmp = &rCmp; + } + else + { + pOrig = &rCmp; + pCmp = this; + } + + if( rSrtKey.bIsNumeric ) + { + double n1 = pOrig->GetValue( nKey ); + double n2 = pCmp->GetValue( nKey ); + + nCmp = n1 < n2 ? -1 : n1 == n2 ? 0 : 1; + } + else + { + if( !xLastAlgorithm || *xLastAlgorithm != rSrtKey.sSortType ) + { + xLastAlgorithm = rSrtKey.sSortType; + pSortCollator->loadCollatorAlgorithm( *xLastAlgorithm, + *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) + { + nStart = rStr.indexOf( nDeli, nStart ); + if( -1 != 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( SwNodeOffset 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 + auto [pStart, pEnd] = rPaM.StartEnd(); // SwPosition* + + // Set index to the Selection's start + for(sw::SpzFrameFormat* pFormat: *GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + + if (pAnchorNode && (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) && + pStart->GetNode() <= *pAnchorNode && *pAnchorNode <= pEnd->GetNode() ) + return false; + } + + // Check if only TextNodes are within the Selection + { + SwNodeOffset nStart = pStart->GetNodeIndex(), + nEnd = pEnd->GetNodeIndex(); + 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->GetNode(), pEnd->GetNode(), SwNodeOffset(-1), SwNodeOffset(1) ); + SwContentNode* pCNd = pRedlPam->GetMarkContentNode(); + if( pCNd ) + pRedlPam->GetMark()->SetContent( 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->GetNode(), 1 ); + SwNodeRange aRg( pStart->GetNode(), aEndIdx.GetNode() ); + GetNodes().Copy_( aRg, aEndIdx.GetNode() ); + + // range is new from pEnd->nNode+1 to aEndIdx + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + + pRedlPam->GetMark()->Assign( pEnd->GetNode(), SwNodeOffset(1) ); + + pRedlPam->GetPoint()->Assign( aEndIdx.GetNode() ); + pCNd = pRedlPam->GetPointContentNode(); + sal_Int32 nCLen = 0; + if( !pCNd ) + { + pCNd = GetNodes()[ aEndIdx.GetIndex()-SwNodeOffset(1) ]->GetContentNode(); + if( pCNd ) + { + nCLen = pCNd->Len(); + pRedlPam->GetPoint()->Assign( *pCNd ); + } + } + if (pCNd) + pRedlPam->GetPoint()->SetContent( nCLen ); + + if( pRedlUndo ) + pRedlUndo->SetValues( rPaM ); + } + else + { + getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any ); + delete pRedlPam; + pRedlPam = nullptr; + } + } + + SwNodeIndex aStart(pStart->GetNode()); + SwSortElement::Init( this, rOpt ); + std::multiset<SwSortTextElement> aSortSet; + while( aStart <= pEnd->GetNode() ) + { + // Iterate over a selected range + aSortSet.insert(SwSortTextElement(aStart)); + ++aStart; + } + + // Now comes the tricky part: Move Nodes (and always keep Undo in mind) + SwNodeOffset nBeg = pStart->GetNodeIndex(); + SwNodeRange aRg( aStart, aStart ); + + if( bUndo && !pRedlUndo ) + { + pUndoSort = new SwUndoSort(rPaM, rOpt); + GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort)); + } + + GetIDocumentUndoRedo().DoUndo(false); + + SwNodeOffset 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.GetNode(), + 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()->Assign(aSttIdx); + + getIDocumentRedlineAccess().AppendRedline(pDeleteRedline, true); + + // the sorted range is inserted + getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlPam ), true); + + if( pRedlUndo ) + { + SwNodeIndex aInsEndIdx( pRedlPam->GetMark()->GetNode(), -1 ); + SwContentNode *const pContentNode = aInsEndIdx.GetNode().GetContentNode(); + pRedlPam->GetMark()->Assign( *pContentNode, pContentNode->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; + } + + pTableNd->GetTable().SwitchFormulasToRelativeRepresentation(); + + // 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 = o3tl::narrowing<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 SwNodeOffset 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(), SwNodeOffset(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.GetNode(), + 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 + SwNodeOffset nCount = pNd->EndOfSectionIndex() - pNd->StartOfSectionIndex(); + + bool bDelFirst = false; + if( nCount == SwNodeOffset(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(), SwNodeOffset(0), *pNd->EndOfSectionNode() ); + pDoc->GetNodes().SectionDown( &aRgTar ); + } + + // Insert the Source + SwNodeIndex aIns( *pTar->GetSttNd()->EndOfSectionNode() ); + pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIns.GetNode(), + 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) : + m_pDoc(pDocPtr), + m_nRow(0), + m_nCol(0) +{ // If the array is symmetric + m_bSym = CheckLineSymmetry(rBoxRef); + if( !m_bSym ) + return; + + // Determine column/row count + m_nCols = GetColCount(rBoxRef); + m_nRows = GetRowCount(rBoxRef); + + // Create linear array + size_t nCount = static_cast<size_t>(m_nRows) * m_nCols; + m_pArr = std::make_unique<FndBox_ const *[]>(nCount); + memset(m_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 = m_nRow; + for (const auto & pLine : rLines) + { + // The Boxes of a Line + const FndBoxes_t& rBoxes = pLine->GetBoxes(); + sal_uInt16 nOldCol = m_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 = m_nRow * m_nCols + m_nCol; + m_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 ) ) + { + SfxItemSetFixed< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE> + aSet(m_pDoc->GetAttrPool()); + aSet.Put( pFormat->GetAttrSet() ); + if( m_vItemSets.empty() ) + { + size_t nCount = static_cast<size_t>(m_nRows) * m_nCols; + m_vItemSets.resize(nCount); + } + m_vItemSets[nOff].emplace(std::move(aSet)); + } + + bModRow = true; + } + else + { + // Iterate recursively over the Lines of a Box + FillFlat( *pBox, ( j+1 == rBoxes.size() ) ); + } + m_nCol++; + } + if(bModRow) + m_nRow++; + m_nCol = nOldCol; + } + if(!bLastBox) + m_nRow = nOldRow; +} + +/// Access a specific Cell +const FndBox_* FlatFndBox::GetBox(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + sal_uInt16 nOff = n_Row * m_nCols + n_Col; + const FndBox_* pTmp = m_pArr[nOff]; + + OSL_ENSURE(n_Col < m_nCols && n_Row < m_nRows && pTmp, "invalid array access"); + return pTmp; +} + +const SfxItemSet* FlatFndBox::GetItemSet(sal_uInt16 n_Col, sal_uInt16 n_Row) const +{ + OSL_ENSURE( m_vItemSets.empty() || ( n_Col < m_nCols && n_Row < m_nRows), "invalid array access"); + + if (m_vItemSets.empty()) { + return nullptr; + } + auto const & el = m_vItemSets[unsigned(n_Row * m_nCols) + n_Col]; + return el ? &*el : 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 0000000000..959c61833d --- /dev/null +++ b/sw/source/core/doc/docstat.cxx @@ -0,0 +1,53 @@ +/* -*- 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), + nComments(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; + nComments = 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 0000000000..f03687d810 --- /dev/null +++ b/sw/source/core/doc/doctxm.cxx @@ -0,0 +1,2135 @@ +/* -*- 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 <o3tl/string_view.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 <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 <osl/diagnose.h> + +#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.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.GetContentIndex(); + + 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, 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 +{ + SwNodeOffset m_nNode; + sal_Int32 m_nContent; +public: + CompareNodeContent( SwNodeOffset nNd, sal_Int32 nCnt ) + : m_nNode( nNd ), m_nContent( nCnt ) {} + + bool operator==( const CompareNodeContent& rCmp ) const + { return m_nNode == rCmp.m_nNode && m_nContent == rCmp.m_nContent; } + bool operator!=( const CompareNodeContent& rCmp ) const + { return m_nNode != rCmp.m_nNode || m_nContent != rCmp.m_nContent; } + bool operator< ( const CompareNodeContent& rCmp ) const + { return m_nNode < rCmp.m_nNode || + ( m_nNode == rCmp.m_nNode && m_nContent < rCmp.m_nContent); } + bool operator<=( const CompareNodeContent& rCmp ) const + { return m_nNode < rCmp.m_nNode || + ( m_nNode == rCmp.m_nNode && m_nContent <= rCmp.m_nContent); } + bool operator> ( const CompareNodeContent& rCmp ) const + { return m_nNode > rCmp.m_nNode || + ( m_nNode == rCmp.m_nNode && m_nContent > rCmp.m_nContent); } + bool operator>=( const CompareNodeContent& rCmp ) const + { return m_nNode > rCmp.m_nNode || + ( m_nNode == rCmp.m_nNode && m_nContent >= rCmp.m_nContent); } +}; + +} + +const SwTOXMark& SwDoc::GotoTOXMark( const SwTOXMark& rCurTOXMark, + SwTOXSearch eDir, bool bInReadOnly ) +{ + const SwTextTOXMark* pMark = rCurTOXMark.GetTextTOXMark(); + + CompareNodeContent aAbsIdx(pMark ? pMark->GetpTextNd()->GetIndex() : SwNodeOffset(0), pMark ? pMark->GetStart() : 0); + CompareNodeContent aPrevPos( SwNodeOffset(0), 0 ); + CompareNodeContent aNextPos( NODE_OFFSET_MAX, SAL_MAX_INT32 ); + CompareNodeContent aMax( SwNodeOffset(0), 0 ); + CompareNodeContent aMin( NODE_OFFSET_MAX, SAL_MAX_INT32 ); + + const SwTOXMark* pNew = nullptr; + const SwTOXMark* pMax = &rCurTOXMark; + const SwTOXMark* pMin = &rCurTOXMark; + + const SwTOXType* pType = rCurTOXMark.GetTOXType(); + SwTOXMarks aMarks; + pType->CollectTextMarks(aMarks); + + for(SwTOXMark* pTOXMark : aMarks) + { + // Item PtrCompare needed here + if (areSfxPoolItemPtrsEqual( pTOXMark, &rCurTOXMark )) + continue; + + pMark = pTOXMark->GetTextTOXMark(); + if (!pMark) + continue; + + SwTextNode const*const 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::tuple<SwTOXBase const*, sw::RedlineMode, sw::FieldmarkMode, sw::ParagraphBreakMode> const tmp( + &rTOX, + pLayout && pLayout->IsHideRedlines() + ? sw::RedlineMode::Hidden + : sw::RedlineMode::Shown, + pLayout ? pLayout->GetFieldmarkMode() : sw::FieldmarkMode::ShowBoth, + pLayout ? pLayout->GetParagraphBreakMode() : sw::ParagraphBreakMode::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.GetNode(), + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + + SwSectionData headerData( SectionType::ToxHeader, pNewSection->GetTOXName()+"_Head" ); + + --aIdx; + SwSectionFormat* pSectFormat = MakeSectionFormat(); + GetNodes().InsertTextSection( + *pHeadNd, *pSectFormat, headerData, nullptr, &aIdx.GetNode(), true, false); + } + } + + GetIDocumentUndoRedo().EndUndo( SwUndoId::INSTOX, nullptr ); + + return pNewSection; +} + +void SwDoc::InsertTableOf( SwNodeOffset nSttNd, SwNodeOffset 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.GetNode(), *pFormat, aSectionData, &rTOX, &aEnd.GetNode()); + 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.GetNode(); + SwSectionNode* pSectNd = rNd.FindSectionNode(); + while( pSectNd ) + { + SectionType eT = pSectNd->GetSection().GetType(); + if( SectionType::ToxContent == eT ) + { + assert( dynamic_cast< const SwTOXBaseSection *>( &pSectNd->GetSection()) && + "no TOXBaseSection!" ); + SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>( + pSectNd->GetSection()); + return &rTOXSect; + } + pSectNd = pSectNd->StartOfSectionNode()->FindSectionNode(); + } + return nullptr; +} + +const SwAttrSet& SwDoc::GetTOXBaseAttrSet(const SwTOXBase& rTOXBase) +{ + assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "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; + assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "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 = o3tl::toInt32(rNm.subView( nNmLen )); + 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) +{ + assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "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?" ); + // tdf#151462 - search for outline node containing the current node + return pNd ? pNd->FindOutlineNodeOfLevel(pNd->GetSectionLevel() - 1, pLayout) : nullptr; + } + } + return pNd->FindOutlineNodeOfLevel(nLvl, pLayout); +} + +static bool IsHeadingContained(const SwTextNode* pChptrNd, const SwNode& rNd) +{ + const SwNode* pNd = &rNd; + const SwOutlineNodes& rONds = pNd->GetNodes().GetOutLineNds(); + bool bIsHeadingContained = false; + if (!rONds.empty()) + { + bool bCheckFirst = false; + SwOutlineNodes::size_type nPos; + + if (!rONds.Seek_Entry(const_cast<SwNode*>(pNd), &nPos)) + { + if (nPos == 0) + bCheckFirst = true; + else + nPos--; + } + + if (bCheckFirst) + { + const SwContentNode* pCNd = pNd->GetContentNode(); + + Point aPt(0, 0); + std::pair<Point, bool> const tmp(aPt, false); + + const SwFrame* pChptrFrame = pChptrNd ? pChptrNd->getLayoutFrame( + pChptrNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) : nullptr; + const SwPageFrame* pChptrPgFrame = pChptrFrame ? pChptrFrame->FindPageFrame() : nullptr; + const SwFrame* pNdFrame + = pCNd ? pCNd->getLayoutFrame( + pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, &tmp) + : nullptr; + + // Check if the one asking doesn't precede the page of the specified chapter note + bIsHeadingContained + = pNdFrame && pChptrPgFrame + && pChptrPgFrame->getFrameArea().Top() <= pNdFrame->getFrameArea().Top(); + // Check if the one asking doesn't succeed the specified chapter note + if (bIsHeadingContained) + { + const SwNode* aChptrNd = pChptrNd; + if (!rONds.Seek_Entry(const_cast<SwNode*>(aChptrNd), &nPos) && nPos) + nPos--; + // Search for the next outline node with a larger level than the specified chapter node + while (nPos < rONds.size() - 1 + && pChptrNd->GetAttrOutlineLevel() + < rONds[nPos + 1]->GetTextNode()->GetAttrOutlineLevel()) + nPos++; + // If there exists such an outline node, check if the one asking doesn't succeed + // the specified chapter node + if (nPos < rONds.size() - 1) { + nPos++; + const auto aONdsTxtNd = rONds[nPos]->GetTextNode(); + pChptrFrame = aONdsTxtNd->getLayoutFrame( + aONdsTxtNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, + &tmp); + pChptrPgFrame = pChptrFrame ? pChptrFrame->FindPageFrame() : nullptr; + bIsHeadingContained + = pNdFrame && pChptrPgFrame + && pChptrPgFrame->getFrameArea().Top() >= pNdFrame->getFrameArea().Top(); + } + } + } + else + { + // Search for the next outline node which lies not within the current chapter node + while (nPos > 0 + && pChptrNd->GetAttrOutlineLevel() + < rONds[nPos]->GetTextNode()->GetAttrOutlineLevel()) + nPos--; + bIsHeadingContained = pChptrNd == rONds[nPos]->GetTextNode(); + } + } + else + { + // If there are no outline nodes, consider the heading contained, + // otherwise the _XDocumentIndex._update() test fails + bIsHeadingContained = true; + } + return bIsHeadingContained; +} + +// 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.Assign(*pSectNd); + pSectNd->GetDoc().GetNodes().GoNext( &rPos ); + bRet = true; + } + return bRet; +} + +/// Collect table of contents content +void SwTOXBaseSection::Update(const SfxItemSet* pAttr, + SwRootFrame const*const pLayout, + const bool _bNewTOX) +{ + if (!GetFormat()) + return; + SwSectionNode const*const pSectNd(GetFormat()->GetSectionNode()); + if (nullptr == pSectNd || + !pSectNd->GetNodes().IsDocNodes() || + IsHiddenFlag() || + (pLayout->HasMergedParas() && pSectNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)) + { + return; + } + + if ( !mbKeepExpression ) + { + maMSTOCExpression.clear(); + } + + SwDoc& rDoc = const_cast<SwDoc&>(pSectNd->GetDoc()); + + if (pAttr && GetFormat()) + rDoc.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 + SwNodeOffset nPgDescNdIdx = pSectNd->GetIndex() + 1; + SwNodeOffset* 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 ); + SwContentNode* pTmp = SwNodes::GoPrevious( &aIdx ); + assert(pTmp); // make coverity happy + pDefaultPageDesc = pTmp->FindPageDesc(); + + } + if ( !pDefaultPageDesc ) + { + // determine default page description + pDefaultPageDesc = &rDoc.GetPageDesc( 0 ); + } + } + + rDoc.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 SwSectionNode* pChapterSectNd = IsFromChapter() ? pSectNd->FindSectionNode() : nullptr; + const SwTextNode* pOwnChapterNode = pChapterSectNd + ? ::lcl_FindChapterNode( *pSectNd, pLayout, pChapterSectNd->GetSectionLevel() + 1 ) + : nullptr; + + SwNode2LayoutSaveUpperFrames aN2L(*pSectNd); + const_cast<SwSectionNode*>(pSectNd)->DelFrames(); + + // This would be a good time to update the Numbering + rDoc.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); + { + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pSectNd, true, RedlineType::Any ); + + SwNodeIndex aSttIdx( *pSectNd, +1 ); + SwNodeIndex aEndIdx( *pSectNd->EndOfSectionNode() ); + pFirstEmptyNd = rDoc.GetNodes().MakeTextNode( aEndIdx.GetNode(), + rDoc.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 = rDoc.GetNodes().GoNext( &aNxtIdx ); + assert(pCNd != pFirstEmptyNd); + assert(pCNd->GetIndex() < pFirstEmptyNd->GetIndex()); + if( pCNd->HasSwAttrSet() ) + { + SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange ); + aBrkSet.Put( *pCNd->GetpSwAttrSet() ); + if( aBrkSet.Count() ) + pFirstEmptyNd->SetAttr( aBrkSet ); + } + } + + if (rDoc.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 + rDoc.GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndoUpdateIndex>(pUndo)); + } + else + { + --aEndIdx; + SwPosition aPos( aEndIdx, 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).GetNode(), aEndIdx.GetNode() ); + + rDoc.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 = rDoc.GetNodes().MakeTextNode( aIdx.GetNode(), + GetTextFormatColl( FORM_TITLE ) ); + pHeadNd->InsertText( GetTitle(), SwContentIndex( pHeadNd ) ); + + SwSectionData headerData( SectionType::ToxHeader, GetTOXName()+"_Head" ); + + --aIdx; + SwSectionFormat* pSectFormat = rDoc.MakeSectionFormat(); + rDoc.GetNodes().InsertTextSection( + *pHeadNd, *pSectFormat, headerData, nullptr, &aIdx.GetNode(), true, false); + + if (pUndo) + { + pUndo->TitleSectionInserted(*pSectFormat); + } + } + + // Sort the List of all TOC Marks and TOC Sections + std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr ); + std::unordered_map<OUString, int> markURLs; + SwNodeIndex aInsPos( *pFirstEmptyNd, 1 ); + for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) + { + ::SetProgressState( 0, rDoc.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 = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode() , 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, rDoc.GetDocShell() ); + + std::shared_ptr<sw::ToxTabStopTokenHandler> tabStopTokenHandler = + std::make_shared<sw::DefaultToxTabStopTokenHandler>( + pSectNd->GetIndex(), *pDefaultPageDesc, GetTOXForm().IsRelTabPos(), + rDoc.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(), markURLs, 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 ); + 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 = rDoc.GetNodes().GoNext( &aEndIdx ); + if( pCNd ) // Robust against defect documents, e.g. i60336 + pCNd->SetAttr( *pFirstEmptyNd->GetpSwAttrSet() ); + } + } + + // now create the new Frames + SwNodeOffset nIdx = pSectNd->GetIndex(); + // don't delete if index is empty + if(nIdx + SwNodeOffset(2) < pSectNd->EndOfSectionIndex()) + rDoc.GetNodes().Delete( aInsPos ); + + aN2L.RestoreUpperFrames( rDoc.GetNodes(), nIdx, nIdx + 1 ); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = rDoc.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; +} + +void SwTOXBaseSection::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if (auto pFindHint = dynamic_cast<const sw::FindContentFrameHint*>(&rHint)) + { + if(pFindHint->m_rpContentFrame) + return; + auto pSectFormat = GetFormat(); + if(!pSectFormat) + return; + const SwSectionNode* pSectNd = pSectFormat->GetSectionNode(); + if(!pSectNd) + return; + SwNodeIndex aIdx(*pSectNd, 1); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if(!pCNd) + pCNd = pFindHint->m_rDoc.GetNodes().GoNext(&aIdx); + if(!pCNd) + return; + if(pCNd->EndOfSectionIndex() >= pSectNd->EndOfSectionIndex()) + return; + pFindHint->m_rpContentFrame = pCNd->getLayoutFrame(&pFindHint->m_rLayout); + } else + SwTOXBase::SwClientNotify(rModify, rHint); +} + +/// Create from Marks +void SwTOXBaseSection::UpdateMarks(const SwTOXInternational& rIntl, + const SwTextNode* pOwnChapterNode, + SwRootFrame const*const pLayout) +{ + const auto pType = static_cast<SwTOXType*>(SwTOXBase::GetRegisteredIn()); + auto pShell = GetFormat()->GetDoc()->GetDocShell(); + const TOXTypes eTOXTyp = GetTOXType()->GetType(); + std::vector<std::reference_wrapper<SwTextTOXMark>> vMarks; + pType->CollectTextTOXMarksForLayout(vMarks, pLayout); + for(auto& rMark: vMarks) + { + ::SetProgressState(0, pShell); + auto& rNode = rMark.get().GetTextNode(); + if(IsFromChapter() && !IsHeadingContained(pOwnChapterNode, rNode)) + continue; + auto rTOXMark = rMark.get().GetTOXMark(); + if(TOX_INDEX == eTOXTyp) + { + // index entry mark + assert(g_pBreakIt); + lang::Locale aLocale = g_pBreakIt->GetLocale(rNode.GetLang(rMark.get().GetStart())); + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, rNode, &rMark.get(), GetOptions(), FORM_ENTRY, rIntl, aLocale)); + if(GetOptions() & SwTOIOptions::KeyAsEntry && !rTOXMark.GetPrimaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, rNode, &rMark.get(), GetOptions(), FORM_PRIMARY_KEY, rIntl, aLocale)); + if (!rTOXMark.GetSecondaryKey().isEmpty()) + { + InsertSorted(MakeSwTOXSortTabBase<SwTOXIndex>(pLayout, rNode, &rMark.get(), GetOptions(), FORM_SECONDARY_KEY, rIntl, aLocale)); + } + } + } + else if(TOX_USER == eTOXTyp || rTOXMark.GetLevel() <= GetLevel()) + { // table of content mark, also used for user marks + InsertSorted(MakeSwTOXSortTabBase<SwTOXContent>(pLayout, rNode, &rMark.get(), 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->HasMergedParas() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + ( !IsFromChapter() || IsHeadingContained(pOwnChapterNode, *pTextNd) )) + { + 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() && + // tdf#40142 - consider level settings of the various text nodes + o3tl::make_unsigned(pTextNd->GetAttrOutlineLevel()) <= GetLevel() && + (!pLayout || !pLayout->HasMergedParas() + || static_cast<SwTextFrame*>(pTextNd->getLayoutFrame(pLayout))->GetTextNodeForParaProps() == pTextNd) && + (!IsFromChapter() || IsHeadingContained(pOwnChapterNode, *pTextNd))) + { + 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() || IsHeadingContained(pOwnChapterNode, rTextNode)) + && (!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 + SwNodeOffset nIdx = rNds.GetEndOfAutotext().StartOfSectionIndex() + SwNodeOffset(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->HasMergedParas() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && ( !IsFromChapter() || IsHeadingContained(pOwnChapterNode, *pCNd))) + { + std::unique_ptr<SwTOXPara> pNew( MakeSwTOXSortTabBase<SwTOXPara>( + pLayout, *pCNd, eMyType, + ( USHRT_MAX != nSetLevel ) + ? o3tl::narrowing<sal_uInt16>(nSetLevel) + : FORM_ALPHA_DELIMITER ) ); + InsertSorted( std::move(pNew) ); + } + } + + nIdx = pNd->StartOfSectionNode()->EndOfSectionIndex() + SwNodeOffset(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(); + + for(SwTableFormat* pFrameFormat: *pDoc->GetTableFrameFormats()) + { + ::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->HasMergedParas() + || pCNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + && (!IsFromChapter() || IsHeadingContained(pOwnChapterNode, *pCNd))) + { + 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(o3tl::narrowing<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::optional< std::vector<sal_uInt16> > xCharStyleIdx; + if (pMainEntryNums) + xCharStyleIdx.emplace(); + + OUString sSrchStr + = OUStringChar(C_NUM_REPL) + SwTOXMark::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 + SwContentIndex 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 += SwTOXMark::S_PAGE_DELI; + else + aNumStr += "-"; + + aNumStr += aType.GetNumStr( nBeg + nCount ); + } + + // Create new String + nBeg = rNums[i]; + aNumStr += SwTOXMark::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 += SwTOXMark::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 += SwTOXMark::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()) + return; + + // eventually the last index must me appended + if (xCharStyleIdx->size()&0x01) + xCharStyleIdx->push_back(aNumStr.getLength()); + + // search by name + SwDoc& rDoc = pNd->GetDoc(); + sal_uInt16 nPoolId = SwStyleNameMapper::GetPoolIdFromUIName( GetMainEntryCharStyle(), SwGetPoolIdFromName::ChrFmt ); + SwCharFormat* pCharFormat = nullptr; + if(USHRT_MAX != nPoolId) + pCharFormat = rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool(nPoolId); + else + pCharFormat = rDoc.FindCharFormatByName( GetMainEntryCharStyle() ); + if(!pCharFormat) + pCharFormat = rDoc.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 + tools::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.subView(1); + } + + OSL_ENSURE(rRange.Min() >= 0 && rRange.Max() >= 0, "Min Max < 0"); + + const tools::Long nMin = rRange.Min(); + const tools::Long nMax = rRange.Max(); + + tools::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 tools::Long nStart = i+1; + const tools::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 ); +} + +/* 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 0000000000..5cd7792c99 --- /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::Any; +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, Any( 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() ) + return; + + // 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 0000000000..c8563facbd --- /dev/null +++ b/sw/source/core/doc/extinput.cxx @@ -0,0 +1,309 @@ +/* -*- 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 <contentindex.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& rDoc = GetDoc(); + if (rDoc.IsInDtor()) { return; /* #i58606# */ } + + SwTextNode* pTNd = GetPoint()->GetNode().GetTextNode(); + if( !pTNd ) + return; + + SwPosition& rPtPos = *GetPoint(); + sal_Int32 nSttCnt = rPtPos.GetContentIndex(); + sal_Int32 nEndCnt = GetMark()->GetContentIndex(); + if( nEndCnt == nSttCnt ) + return; + + // Prevent IME edited text being grouped with non-IME edited text. + bool bKeepGroupUndo = rDoc.GetIDocumentUndoRedo().DoesGroupUndo(); + bool bWasIME = rDoc.GetIDocumentUndoRedo().GetUndoActionCount() == 0 || rDoc.getIDocumentContentOperations().GetIME(); + if (!bWasIME) + { + rDoc.GetIDocumentUndoRedo().DoGroupUndo(false); + } + rDoc.getIDocumentContentOperations().SetIME(true); + if( nEndCnt < nSttCnt ) + { + std::swap(nSttCnt, nEndCnt); + } + + // In order to get Undo/Redlining etc. working correctly, + // we need to go through the Doc interface + rPtPos.SetContent(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 ) + { + rPtPos.AdjustContent(+nOWLen); + pTNd->EraseText( rPtPos, nLen - nOWLen ); + rPtPos.SetContent(nSttCnt); + pTNd->ReplaceText( rPtPos, nOWLen, m_sOverwriteText ); + if( m_bInsText ) + { + rPtPos.SetContent(nSttCnt); + rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::OVERWRITE, nullptr ); + rDoc.getIDocumentContentOperations().Overwrite( *this, sText.copy( 0, nOWLen ) ); + rDoc.getIDocumentContentOperations().InsertString( *this, sText.copy( nOWLen ) ); + rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::OVERWRITE, nullptr ); + } + } + else + { + pTNd->ReplaceText( rPtPos, nLen, m_sOverwriteText.copy( 0, nLen )); + if( m_bInsText ) + { + rPtPos.SetContent(nSttCnt); + rDoc.getIDocumentContentOperations().Overwrite( *this, sText ); + } + } + } + else + { + // 1. Insert text at start position with EMPTYEXPAND to use correct formatting + // ABC<NEW><OLD> + // 2. Then remove old (not tracked) content + // ABC<NEW> + + sal_Int32 nLenghtOfOldString = nEndCnt - nSttCnt; + + if( m_bInsText ) + { + rPtPos.SetContent(nSttCnt); + rDoc.getIDocumentContentOperations().InsertString( *this, sText, SwInsertFlags::EMPTYEXPAND ); + } + + pTNd->EraseText( rPtPos, nLenghtOfOldString ); + } + if (!bWasIME) + { + rDoc.GetIDocumentUndoRedo().DoGroupUndo(bKeepGroupUndo); + } + if (m_eInputLanguage == LANGUAGE_DONTKNOW) + return; + + 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 ); + rPtPos.SetContent(nSttCnt); + GetMark()->SetContent(nEndCnt); + rDoc.getIDocumentContentOperations().InsertPoolItem(*this, aLangItem ); + } +} + +void SwExtTextInput::SetInputData( const CommandExtTextInputData& rData ) +{ + SwTextNode* pTNd = GetPoint()->GetNode().GetTextNode(); + if( !pTNd ) + return; + + sal_Int32 nSttCnt = Start()->GetContentIndex(); + sal_Int32 nEndCnt = End()->GetContentIndex(); + + SwContentIndex 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()->Assign(*aIdx.GetContentNode(), aIdx.GetIndex()); + } + else + { + if( nSttCnt < nEndCnt ) + { + pTNd->EraseText( aIdx, nEndCnt - nSttCnt ); + } + + // NOHINTEXPAND so we can use correct formatting in destructor when we finish composing + pTNd->InsertText( rNewStr, aIdx, SwInsertFlags::NOHINTEXPAND ); + if( !HasMark() ) + SetMark(); + } + + GetPoint()->SetContent(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()->GetNode().GetTextNode(); + if (!pTNd) + return; + + const sal_Int32 nSttCnt = GetPoint()->GetContentIndex(); + const sal_Int32 nEndCnt = GetMark()->GetContentIndex(); + m_sOverwriteText = pTNd->GetText().copy( std::min(nSttCnt, nEndCnt) ); + if( m_sOverwriteText.isEmpty() ) + return; + + 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 ) + { + SwNodeOffset nNdIdx = rNd.GetIndex(); + SwExtTextInput* pTmp = mpExtInputRing; + do { + SwNodeOffset nStartNode = pTmp->Start()->GetNodeIndex(), + nEndNode = pTmp->End()->GetNodeIndex(); + sal_Int32 nStartCnt = pTmp->Start()->GetContentIndex(); + sal_Int32 nEndCnt = pTmp->End()->GetContentIndex(); + + if( nStartNode <= nNdIdx && nNdIdx <= nEndNode && + ( nContentPos<0 || + ( nStartCnt <= nContentPos && nContentPos <= nEndCnt ))) + { + 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 0000000000..4d87241a03 --- /dev/null +++ b/sw/source/core/doc/fmtcol.cxx @@ -0,0 +1,723 @@ +/* -*- 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 <editeng/fhgtitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <osl/diagnose.h> +#include <sal/macros.h> +#include <svl/intitem.hxx> +#include <calbck.hxx> +#include <doc.hxx> +#include <fmtcol.hxx> +#include <fmtcolfunc.hxx> +#include <hintids.hxx> +#include <hints.hxx> +#include <node.hxx> +#include <numrule.hxx> +#include <paratr.hxx> +#include <swfntcch.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() ) + return; + + if (!pNewNumRuleItem) + { + pNewNumRuleItem = pTextFormatColl->GetItemIfSet(RES_PARATR_NUMRULE, false); + } + 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 = rTextFormatColl.GetItemIfSet(RES_PARATR_NUMRULE, false); + 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 + +SwTextFormatColl::~SwTextFormatColl() +{ + if(m_bInSwFntCache) + pSwFontCache->Delete( this ); + + if (GetDoc()->IsInDtor()) + { + return; + } + + for (const auto& pCharFormat : *GetDoc()->GetCharFormats()) + { + if (pCharFormat->GetLinkedParaFormat() == this) + { + pCharFormat->SetLinkedParaFormat(nullptr); + } + } +} +void SwTextFormatColl::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwAutoFormatUsedHint) + { + CallSwClientNotify(rHint); + return; + } + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if(GetDoc()->IsInDtor()) + { + SwFormatColl::SwClientNotify(rModify, rHint); + return; + } + bool bNewParent( false ); // #i66431# - adjust type of <bNewParent> + const SvxULSpaceItem *pNewULSpace = nullptr, *pOldULSpace = nullptr; + const SvxFirstLineIndentItem *pNewFirstLineIndent = nullptr; + const SvxTextLeftMarginItem *pNewTextLeftMargin = nullptr; + const SvxRightMarginItem *pNewRightMargin = nullptr; + const SvxFontHeightItem* aFontSizeArr[3] = {nullptr,nullptr,nullptr}; + // #i70223# + const bool bAssignedToListLevelOfOutlineStyle(IsAssignedToListLevelOfOutlineStyle()); + const SwNumRuleItem* pNewNumRuleItem( nullptr ); + + const SwAttrSetChg *pNewChgSet = nullptr, *pOldChgSet = nullptr; + const auto pOld = pLegacy->m_pOld; + const auto pNew = pLegacy->m_pNew; + switch( pLegacy->GetWhich() ) + { + case RES_ATTRSET_CHG: + // Only recalculate if we're not the sender! + pNewChgSet = &pNew->StaticWhichCast(RES_ATTRSET_CHG); + pOldChgSet = &pOld->StaticWhichCast(RES_ATTRSET_CHG); + pNewFirstLineIndent = pNewChgSet->GetChgSet()->GetItemIfSet(RES_MARGIN_FIRSTLINE, false); + pNewTextLeftMargin = pNewChgSet->GetChgSet()->GetItemIfSet(RES_MARGIN_TEXTLEFT, false); + pNewRightMargin = pNewChgSet->GetChgSet()->GetItemIfSet(RES_MARGIN_RIGHT, false); + pNewULSpace = pNewChgSet->GetChgSet()->GetItemIfSet( RES_UL_SPACE, false ); + aFontSizeArr[0] = pNewChgSet->GetChgSet()->GetItemIfSet( RES_CHRATR_FONTSIZE, false ); + aFontSizeArr[1] = pNewChgSet->GetChgSet()->GetItemIfSet( RES_CHRATR_CJK_FONTSIZE, false ); + aFontSizeArr[2] = pNewChgSet->GetChgSet()->GetItemIfSet( RES_CHRATR_CTL_FONTSIZE, false ); + // #i70223#, #i84745# + // check, if attribute set is applied to this paragraph style + if ( bAssignedToListLevelOfOutlineStyle && + pNewChgSet->GetTheChgdSet() == &GetAttrSet() ) + { + pNewNumRuleItem = pNewChgSet->GetChgSet()->GetItemIfSet( RES_PARATR_NUMRULE, false ); + } + + break; + + case RES_FMT_CHG: + if( GetAttrSet().GetParent() ) + { + const SfxItemSet* pParent = GetAttrSet().GetParent(); + pNewFirstLineIndent = &pParent->Get(RES_MARGIN_FIRSTLINE); + pNewTextLeftMargin = &pParent->Get(RES_MARGIN_TEXTLEFT); + pNewRightMargin = &pParent->Get(RES_MARGIN_RIGHT); + 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_MARGIN_FIRSTLINE: + pNewFirstLineIndent = &pNew->StaticWhichCast(RES_MARGIN_FIRSTLINE); + break; + case RES_MARGIN_TEXTLEFT: + pNewTextLeftMargin = &pNew->StaticWhichCast(RES_MARGIN_TEXTLEFT); + break; + case RES_MARGIN_RIGHT: + pNewRightMargin = &pNew->StaticWhichCast(RES_MARGIN_RIGHT); + break; + case RES_UL_SPACE: + pNewULSpace = &pNew->StaticWhichCast(RES_UL_SPACE); + break; + case RES_CHRATR_FONTSIZE: + aFontSizeArr[0] = &pNew->StaticWhichCast(RES_CHRATR_CJK_FONTSIZE); + break; + case RES_CHRATR_CJK_FONTSIZE: + aFontSizeArr[1] = &pNew->StaticWhichCast(RES_CHRATR_CJK_FONTSIZE); + break; + case RES_CHRATR_CTL_FONTSIZE: + aFontSizeArr[2] = &pNew->StaticWhichCast(RES_CHRATR_CTL_FONTSIZE); + break; + // #i70223# + case RES_PARATR_NUMRULE: + if (bAssignedToListLevelOfOutlineStyle) + { + pNewNumRuleItem = &pNew->StaticWhichCast(RES_PARATR_NUMRULE); + } + break; + default: + break; + } + + // #i70223# + if ( bAssignedToListLevelOfOutlineStyle && pNewNumRuleItem ) + { + TextFormatCollFunc::CheckTextFormatCollForDeletionOfAssignmentToOutlineStyle( + this, pNewNumRuleItem ); + } + + bool bContinue = true; + + // Check against the own attributes + const SvxFirstLineIndentItem *pOldFirstLineIndent(GetItemIfSet(RES_MARGIN_FIRSTLINE, false)); + if (pNewFirstLineIndent && pOldFirstLineIndent) + { + if (!SfxPoolItem::areSame(pOldFirstLineIndent, pNewFirstLineIndent)) // Avoid recursion (SetAttr!) + { + bool bChg = false; + SvxFirstLineIndentItem aNew(*pOldFirstLineIndent); + // We had a relative value -> recalculate + if( 100 != aNew.GetPropTextFirstLineOffset() ) + { + short nTmp = aNew.GetTextFirstLineOffset(); // keep so that we can compare + aNew.SetTextFirstLineOffset(pNewFirstLineIndent->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(); + } + } + const SvxTextLeftMarginItem *pOldTextLeftMargin(GetItemIfSet(RES_MARGIN_TEXTLEFT, false)); + if (pNewTextLeftMargin && pOldTextLeftMargin) + { + if (!SfxPoolItem::areSame(pOldTextLeftMargin, pNewTextLeftMargin)) // Avoid recursion (SetAttr!) + { + bool bChg = false; + SvxTextLeftMarginItem aNew(*pOldTextLeftMargin); + // We had a relative value -> recalculate + if( 100 != aNew.GetPropLeft() ) + { + // note: changing from Left to TextLeft - looked wrong with Left + tools::Long nTmp = aNew.GetTextLeft(); // keep so that we can compare + aNew.SetTextLeft(pNewTextLeftMargin->GetTextLeft(), aNew.GetPropLeft()); + bChg |= nTmp != aNew.GetTextLeft(); + } + 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(); + } + } + const SvxRightMarginItem *pOldRightMargin(GetItemIfSet(RES_MARGIN_RIGHT, false)); + if (pNewRightMargin && pOldRightMargin) + { + if (!SfxPoolItem::areSame(pOldRightMargin, pNewRightMargin)) // Avoid recursion (SetAttr!) + { + bool bChg = false; + SvxRightMarginItem aNew(*pOldRightMargin); + // We had a relative value -> recalculate + if( 100 != aNew.GetPropRight() ) + { + tools::Long nTmp = aNew.GetRight(); // keep so that we can compare + aNew.SetRight(pNewRightMargin->GetRight(), aNew.GetPropRight()); + bChg |= nTmp != aNew.GetRight(); + } + 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 && (pOldULSpace = GetItemIfSet(RES_UL_SPACE, false)) && + !SfxPoolItem::areSame(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!) + !SfxPoolItem::areSame(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::SwClientNotify(rModify, rHint); +} + +void SwTextFormatColl::SetLinkedCharFormat(SwCharFormat* pLink) { mpLinkedCharFormat = pLink; } + +const SwCharFormat* SwTextFormatColl::GetLinkedCharFormat() const { return mpLinkedCharFormat; } + +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; +} + +::sw::ListLevelIndents SwTextFormatColl::AreListLevelIndentsApplicable() const +{ + ::sw::ListLevelIndents ret(::sw::ListLevelIndents::No); + if (AreListLevelIndentsApplicableImpl(RES_MARGIN_FIRSTLINE)) + { + ret |= ::sw::ListLevelIndents::FirstLine; + } + if (AreListLevelIndentsApplicableImpl(RES_MARGIN_TEXTLEFT)) + { + ret |= ::sw::ListLevelIndents::LeftMargin; + } + return ret; +} + +bool SwTextFormatColl::AreListLevelIndentsApplicableImpl(sal_uInt16 const nWhich) const +{ + bool bAreListLevelIndentsApplicable( true ); + + if ( GetItemState( RES_PARATR_NUMRULE ) != SfxItemState::SET ) + { + // no list style applied to paragraph style + bAreListLevelIndentsApplicable = false; + } + else if (GetItemState(nWhich, 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(nWhich, 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 +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", BAD_CAST(typeid(*this).name())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + if (mpNextTextFormatColl) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("next"), BAD_CAST(mpNextTextFormatColl->GetName().toUtf8().getStr())); + } + if (mpLinkedCharFormat) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("linked"), BAD_CAST(mpLinkedCharFormat->GetName().toUtf8().getStr())); + } + GetAttrSet().dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +void SwTextFormatColls::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColls")); + for (size_t i = 0; i < size(); ++i) + GetFormat(i)->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +//FEATURE::CONDCOLL + +SwCollCondition::SwCollCondition( SwTextFormatColl* pColl, Master_CollCondition nMasterCond, + sal_uInt32 nSubCond ) + : SwClient( pColl ), m_nCondition( nMasterCond ), + m_nSubCondition( nSubCond ) +{ +} + +SwCollCondition::SwCollCondition( const SwCollCondition& rCopy ) + : SwClient( const_cast<sw::BroadcastingModify*>(static_cast<const sw::BroadcastingModify*>(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_uInt32 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) ); + } +} + +void SwTextFormatColl::SetAttrOutlineLevel( int nLevel) +{ + OSL_ENSURE( 0 <= nLevel && nLevel <= MAXLEVEL ,"SwTextFormatColl: Level Out Of Range" ); + SetFormatAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL, + o3tl::narrowing<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 0000000000..81f6378c5a --- /dev/null +++ b/sw/source/core/doc/ftnidx.cxx @@ -0,0 +1,519 @@ +/* -*- 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(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 +{ + SwNodeOffset nIdxLHS = SwTextFootnote_GetIndex( lhs ); + SwNodeOffset nIdxRHS = SwTextFootnote_GetIndex( rhs ); + return ( nIdxLHS == nIdxRHS && lhs->GetStart() < rhs->GetStart() ) || nIdxLHS < nIdxRHS; +} + +void SwFootnoteIdxs::UpdateFootnote( const SwNode& rStt ) +{ + if( empty() ) + return; + + // Get the NodesArray using the first foot note's StartIndex + SwDoc& rDoc = const_cast<SwDoc&>(rStt.GetDoc()); + if( rDoc.IsInReading() ) + return ; + SwTextFootnote* pTextFootnote; + + const SwEndNoteInfo& rEndInfo = rDoc.GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = rDoc.GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(rDoc.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 = rDoc.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 = rDoc.GetNodes().GetOutLineNds(); + const SwNode *pChapterStartHidden(&rDoc.GetNodes().GetEndOfExtras()); + SwNodeOffset nChapterStart(pChapterStartHidden->GetIndex()); + SwNodeOffset nChapterEnd(rDoc.GetNodes().GetEndOfContent().GetIndex()); + SwNodeOffset 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; + 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 ]; + SwNodeOffset 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; + SwNodeOffset 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& rDoc = const_cast<SwDoc&>((*this)[ 0 ]->GetTextNode().GetDoc()); + SwTextFootnote* pTextFootnote; + const SwEndNoteInfo& rEndInfo = rDoc.GetEndNoteInfo(); + const SwFootnoteInfo& rFootnoteInfo = rDoc.GetFootnoteInfo(); + IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess()); + + SwUpdFootnoteEndNtAtEnd aNumArr; + + SwRootFrame const* pLayout = rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + o3tl::sorted_vector<SwRootFrame*> aAllLayouts = rDoc.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 = rDoc.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 ) + { + SwNodeOffset 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 SwNode& rPos, size_t* pFndPos ) const +{ + SwNodeOffset 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; + SwNodeOffset 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 0000000000..30937cadd5 --- /dev/null +++ b/sw/source/core/doc/gctable.cxx @@ -0,0 +1,463 @@ +/* -*- 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 ) +{ + if( const SvxBoxItem* pItem = rFormat.GetItemIfSet( RES_BOX ) ) + { + const SvxBorderLine* pBrd = pItem->GetLeft(); + if( pBrd ) + { + if( *m_pBorderLine == *pBrd ) + m_bAnyBorderFind = 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 SvxBorderLine* pBrd; + const SwTableBox& rBox = rCollTLB.GetBox( rStt, &nPos ); + const SvxBoxItem* pItem = rBox.GetFrameFormat()->GetItemIfSet(RES_BOX); + + if( !pItem ) + break; + pBrd = GetLineTB( pItem, bTop ); + if( !pBrd || *pBrd != rBrdLn ) + break; + nLastPos = nPos; + } + return nLastPos; +} + +static const SvxBorderLine* lcl_GCBorder_GetBorder( const SwTableBox& rBox, + bool bTop, + const SvxBoxItem** ppItem ) +{ + *ppItem = rBox.GetFrameFormat()->GetItemIfSet( RES_BOX ); + if (*ppItem) + return GetLineTB( *ppItem, bTop ); + return nullptr; +} + +static void lcl_GCBorder_DelBorder( const SwCollectTableLineBoxes& rCollTLB, + size_t& rStt, bool bTop, + const SvxBorderLine& rLine, + const SvxBoxItem* 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( *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 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( const SvxBoxItem* pItem = pBox->GetFrameFormat()->GetItemIfSet( RES_BOX ) ) + { + pBrd = pItem->GetRight(); + if( pBrd ) + { + aBPara.SetBorder( *pBrd ); + const SwTableBox* pNextBox = rBoxes[n+1]; + if( lcl_GCBorder_ChkBoxBrd_B( pNextBox, &aBPara ) && + aBPara.IsAnyBorderFound() ) + { + SvxBoxItem aBox( *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 SvxBoxItem *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 ); + + SfxPoolItem const* pRowBrush(nullptr); + pCpyLine->GetFrameFormat()->GetItemState(RES_BACKGROUND, true, &pRowBrush); + if (pRowBrush) + { + for (auto pBox : pCpyLine->GetTabBoxes()) + { + if (pBox->GetFrameFormat()->GetItemState(RES_BACKGROUND) != SfxItemState::SET) + { // set inner row background on inner cell + pBox->ClaimFrameFormat(); + pBox->GetFrameFormat()->SetFormatAttr(*pRowBrush); + } + } + } + + // 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 0000000000..4711a123ad --- /dev/null +++ b/sw/source/core/doc/htmltbl.cxx @@ -0,0 +1,1774 @@ +/* -*- 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 <utility> +#include <viewsh.hxx> +#include <tabfrm.hxx> +#include <viewopt.hxx> +#include <htmltbl.hxx> +#include <calbck.hxx> +#include <o3tl/numeric.hxx> +#include <osl/diagnose.h> +#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 m_nRow; // start row + sal_uInt16 m_nCol; // start column + sal_uInt16 m_nColSpan; // the column's COLSPAN + + std::unique_ptr<SwHTMLTableLayoutConstraints> m_pNext; // the next constraint + + sal_uLong m_nMinNoAlign, m_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 m_nMinNoAlign; } + sal_uLong GetMaxNoAlign() const { return m_nMaxNoAlign; } + + SwHTMLTableLayoutConstraints *InsertNext( SwHTMLTableLayoutConstraints *pNxt ); + SwHTMLTableLayoutConstraints* GetNext() const { return m_pNext.get(); } + + sal_uInt16 GetColSpan() const { return m_nColSpan; } + sal_uInt16 GetColumn() const { return m_nCol; } +}; + +} + +SwHTMLTableLayoutCnts::SwHTMLTableLayoutCnts(const SwStartNode *pSttNd, + std::shared_ptr<SwHTMLTableLayout> xTab, + bool bNoBrTag, + std::shared_ptr<SwHTMLTableLayoutCnts> xNxt ) : + m_xNext( std::move(xNxt) ), m_pBox( nullptr ), m_xTable( std::move(xTab) ), m_pStartNode( pSttNd ), + m_nPass1Done( 0 ), m_nWidthSet( 0 ), m_bNoBreakTag( bNoBrTag ) +{} + +const SwStartNode *SwHTMLTableLayoutCnts::GetStartNode() const +{ + return m_pBox ? m_pBox->GetSttNd() : m_pStartNode; +} + +SwHTMLTableLayoutCell::SwHTMLTableLayoutCell(std::shared_ptr<SwHTMLTableLayoutCnts> xCnts, + sal_uInt16 nRSpan, sal_uInt16 nCSpan, + sal_uInt16 nWidth, bool bPercentWidth, + bool bNWrapOpt ) : + m_xContents(std::move(xCnts)), + m_nRowSpan( nRSpan ), m_nColSpan( nCSpan ), + m_nWidthOption( nWidth ), m_bPercentWidthOption( bPercentWidth ), + m_bNoWrapOption( bNWrapOpt ) +{} + +SwHTMLTableLayoutColumn::SwHTMLTableLayoutColumn( sal_uInt16 nWidth, + bool bRelWidth, + bool bLBorder ) : + m_nMinNoAlign(MINLAY), m_nMaxNoAlign(MINLAY), m_nAbsMinNoAlign(MINLAY), + m_nMin(0), m_nMax(0), + m_nAbsColWidth(0), m_nRelColWidth(0), + m_nWidthOption( nWidth ), m_bRelWidthOption( bRelWidth ), + m_bLeftBorder( bLBorder ) +{} + +SwHTMLTableLayoutConstraints::SwHTMLTableLayoutConstraints(sal_uLong nMin, sal_uLong nMax, + sal_uInt16 nRw, sal_uInt16 nColumn, + sal_uInt16 nColSp) + : m_nRow(nRw) + , m_nCol(nColumn) + , m_nColSpan(nColSp) + , m_nMinNoAlign(nMin) + , m_nMaxNoAlign(nMax) +{} + +SwHTMLTableLayoutConstraints *SwHTMLTableLayoutConstraints::InsertNext( + SwHTMLTableLayoutConstraints *pNxt ) +{ + SwHTMLTableLayoutConstraints *pPrev = nullptr; + SwHTMLTableLayoutConstraints *pConstr = this; + while( pConstr ) + { + if (pConstr->m_nRow > pNxt->m_nRow || pConstr->GetColumn() > pNxt->GetColumn()) + break; + pPrev = pConstr; + pConstr = pConstr->GetNext(); + } + + if( pPrev ) + { + pNxt->m_pNext = std::move(pPrev->m_pNext); + pPrev->m_pNext.reset(pNxt); + pConstr = this; + } + else + { + pNxt->m_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_aResizeTimer("SwHTMLTableLayout m_aResizeTimer") + , 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 o3tl::narrowing<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 o3tl::narrowing<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; + tools::Long nRightOffset = 0, + nLeftOffset = 0; + rTabFrame.CalcFlyOffsets(nUpperDummy, nLeftOffset, nRightOffset, nullptr); + nWidth -= (nLeftOffset + nRightOffset); + + return o3tl::narrowing<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, SwNodeOffset 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& rDoc = pSttNd->GetDoc(); + SwNodeOffset nIdx = pSttNd->GetIndex(); + while (!rDoc.GetNodes()[nIdx]->IsEndNode()) + { + SwTextNode *pTextNd = (rDoc.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 = (rDoc.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); + // coverity[leaked_storage] - ownership transferred to pConstraints chain + 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 ) + return; + + 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 = + o3tl::narrowing<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 = o3tl::narrowing<sal_uInt16>((nAbsLeftFillL * nRelAvail) / nAbsAvail); + m_nRelRightFill = o3tl::narrowing<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 = o3tl::narrowing<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( + o3tl::narrowing<sal_uInt16>((nColMin * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + o3tl::narrowing<sal_uInt16>((nColMin * m_nRelTabWidth) / m_nMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + o3tl::narrowing<sal_uInt16>((nColMinD * nAbsTabWidth) / m_nMin) ); + pColumn->SetRelColWidth( + o3tl::narrowing<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( + o3tl::narrowing<sal_uInt16>((((nColMin-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + o3tl::narrowing<sal_uInt16>((((nColMin-nRealColMin) * nDistRel) / nDistMin) + nRealColMin) ); + } + else + { + double nColMinD = nColMin; + pColumn->SetAbsColWidth( + o3tl::narrowing<sal_uInt16>((((nColMinD-nRealColMin) * nDistAbs) / nDistMin) + nRealColMin) ); + pColumn->SetRelColWidth( + o3tl::narrowing<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 = o3tl::narrowing<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 = o3tl::narrowing<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( o3tl::narrowing<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( o3tl::narrowing<sal_uInt16>(nRelColWidth) ); + + nAbs = nAbs + o3tl::narrowing<sal_uInt16>(nColMax); + nRel = nRel + o3tl::narrowing<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( + o3tl::narrowing<sal_uInt16>((nColMax * nDistAbsTabWidth) / nDistMax) ); + pColumn->SetRelColWidth( + o3tl::narrowing<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( + o3tl::narrowing<sal_uInt16>((nColMax * nAbsTabWidth) / m_nMax) ); + GetColumn( i )->SetRelColWidth( + o3tl::narrowing<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 = o3tl::narrowing<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 = o3tl::narrowing<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( o3tl::narrowing<sal_uInt16>(nAbsColWidth) ); + GetColumn( i )->SetRelColWidth( o3tl::narrowing<sal_uInt16>(nRelColWidth) ); + nAbs = nAbs + o3tl::narrowing<sal_uInt16>(nAbsColWidth); + nRel = nRel + o3tl::narrowing<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) ) + return; + + // Calculate the width of additional cells we use for + // aligning inner tables. + sal_uInt16 nAbsDist = o3tl::narrowing<sal_uInt16>(nAbsAvail-nAbsTabWidth); + sal_uInt16 nRelDist = o3tl::narrowing<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() ) + return; + + 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(); +} + +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& rDoc = 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( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() && rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()->GetViewOptions()->getBrowseMode() ) + { + const sal_uInt16 nVisAreaWidth = GetBrowseWidthByVisArea( rDoc ); + 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 0000000000..628e90e321 --- /dev/null +++ b/sw/source/core/doc/lineinfo.cxx @@ -0,0 +1,132 @@ +/* -*- 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 <osl/diagnose.h> + +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(o3tl::toTwips(5, o3tl::Length::mm)), + 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::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + CheckRegistration( pLegacy->m_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 0000000000..5095d4e6c9 --- /dev/null +++ b/sw/source/core/doc/list.cxx @@ -0,0 +1,190 @@ +/* -*- 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 <numrule.hxx> +#include <ndarr.hxx> +#include <node.hxx> +#include <utility> + +SwList::SwList( OUString sListId, + SwNumRule& rDefaultListStyle, + const SwNodes& rNodes ) + : msListId( std::move(sListId) ), + msDefaultListStyleName( rDefaultListStyle.GetName() ), + mnMarkedListLevel( MAXLEVEL ) +{ + // create empty list trees for the document ranges + const SwNode* pNode = rNodes[SwNodeOffset(0)]; + std::vector<bool> aVisited(static_cast<sal_Int32>(rNodes.Count()), false); + do + { + SwNodeOffset nIndex = pNode->GetIndex(); + if (aVisited[static_cast<sal_Int32>(nIndex)]) + { + // crashtesting ooo84576-1.odt, which manages to trigger a broken document structure + // in our code. This is just a workaround to prevent an infinite loop leading to OOM. + SAL_WARN("sw.core", "corrupt document structure, bailing out of infinite loop"); + throw css::uno::RuntimeException("corrupt document structure, bailing out of infinite loop"); + } + aVisited[static_cast<sal_Int32>(nIndex)] = true; + SwPaM aPam( *pNode, *pNode->EndOfSectionNode() ); + + maListTrees.emplace_back( + std::make_unique<SwNodeNum>( &rDefaultListStyle ), + 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()) + { + nIndex = pNode->GetIndex(); + nIndex++; + pNode = rNodes[nIndex]; + } + } + while ( pNode != &rNodes.GetEndOfContent() ); +} + +SwList::~SwList() COVERITY_NOEXCEPT_FALSE +{ + for ( auto& rNumberTree : maListTrees ) + { + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRoot)); + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRootRLHidden)); + SwNodeNum::HandleNumberTreeRootNodeDelete(*(rNumberTree.pRootOrigText)); + } +} + +bool SwList::HasNodes() const +{ + for (auto const& rNumberTree : maListTrees) + { + if (rNumberTree.pRoot->GetChildCount() != 0) + { + return true; + } + } + return false; +} + +void SwList::InsertListItem(SwNodeNum& rNodeNum, SwListRedlineType const eRedline, + const int nLevel, const SwDoc& rDoc) +{ + const SwPosition aPosOfNodeNum( rNodeNum.GetPosition() ); + const SwNodes* pNodesOfNodeNum = &(aPosOfNodeNum.GetNode().GetNodes()); + + for ( const auto& rNumberTree : maListTrees ) + { + auto [pStart, pEnd] = rNumberTree.pSection->StartEnd(); // SwPosition* + const SwNodes* pRangeNodes = &(pStart->GetNode().GetNodes()); + + if ( pRangeNodes == pNodesOfNodeNum && + *pStart <= aPosOfNodeNum && aPosOfNodeNum <= *pEnd) + { + auto const& pRoot(SwListRedlineType::HIDDEN == eRedline + ? rNumberTree.pRootRLHidden + : SwListRedlineType::SHOW == eRedline + ? rNumberTree.pRoot + : rNumberTree.pRootOrigText); + pRoot->AddChild(&rNodeNum, nLevel, rDoc); + break; + } + } +} + +void SwList::RemoveListItem(SwNodeNum& rNodeNum, const SwDoc& rDoc) +{ + rNodeNum.RemoveMe(rDoc); +} + +void SwList::InvalidateListTree() +{ + for ( const auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->InvalidateTree(); + rNumberTree.pRootRLHidden->InvalidateTree(); + rNumberTree.pRootOrigText->InvalidateTree(); + } +} + +void SwList::ValidateListTree(const SwDoc& rDoc) +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyInvalidChildren(rDoc); + rNumberTree.pRootRLHidden->NotifyInvalidChildren(rDoc); + rNumberTree.pRootOrigText->NotifyInvalidChildren(rDoc); + } +} + +void SwList::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 SwList::IsListLevelMarked( const int nListLevel ) const +{ + return nListLevel == mnMarkedListLevel; +} + +void SwList::NotifyItemsOnListLevel( const int nLevel ) +{ + for ( auto& rNumberTree : maListTrees ) + { + rNumberTree.pRoot->NotifyNodesOnListLevel( nLevel ); + rNumberTree.pRootRLHidden->NotifyNodesOnListLevel( nLevel ); + rNumberTree.pRootOrigText->NotifyNodesOnListLevel( nLevel ); + } +} + +void SwList::SetDefaultListStyleName(OUString const& rNew) +{ + msDefaultListStyleName = rNew; +} + +/* 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 0000000000..14cb568030 --- /dev/null +++ b/sw/source/core/doc/notxtfrm.cxx @@ -0,0 +1,1502 @@ +/* -*- 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 <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 <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> +#include <osl/diagnose.h> + +// 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("Noto Sans"); + 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( o3tl::narrowing<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 ) +{ + 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() ) + return; + + const SvxBrushItem *pItem; + std::optional<Color> xCol; + SwRect aOrigRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( rFrame.GetBackgroundBrush( aFillAttributes, pItem, xCol, 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( vcl::PushFlags::FILLCOLOR|vcl::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 ) ) + { + auto pFindFly = FindFlyFrame(); + if (pFindFly && pFindFly->IsFlyFreeFrame()) + { + const SwFlyFreeFrame *pFly = static_cast< const SwFlyFreeFrame* >( pFindFly ); + bool bGetUnclippedFrame=true; + const SvxBoxItem* pBoxItem; + if( pFly->GetFormat() && (pBoxItem = pFly->GetFormat()->GetItemIfSet(RES_BOX, false)) ) + { + if( pBoxItem->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.Overlaps( 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. + tools::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()), tools::Long(1) ); + const double nScale = double(aFramePrintArea.Width()) / double(nLeftCrop); + nLeftCrop = tools::Long(nScale * -rCrop.GetLeft() ); + nRightCrop = tools::Long(nScale * -rCrop.GetRight() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both ) + { + tools::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()), tools::Long(1) ); + const double nScale = double(aFramePrintArea.Height()) / double(nTopCrop); + nTopCrop = tools::Long(nScale * -rCrop.GetTop() ); + nBottomCrop= tools::Long(nScale * -rCrop.GetBottom() ); + } + + // crop values have to be mirrored too + if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both ) + { + tools::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 ) + return; + + 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 = -toRadians(rSwRotationGrf.GetValue()); + + return basegfx::normalizeToRange(fRotate, 2 * M_PI); + } + } + + // no rotation + return 0.0; +} + +void SwNoTextFrame::dumpAsXml(xmlTextWriterPtr writer) const +{ + (void)xmlTextWriterStartElement(writer, reinterpret_cast<const xmlChar*>("notxt")); + dumpAsXmlAttributes(writer); + + (void)xmlTextWriterStartElement(writer, BAD_CAST("infos")); + dumpInfosAsXml(writer); + (void)xmlTextWriterEndElement(writer); + dumpChildrenAsXml(writer); + + (void)xmlTextWriterEndElement(writer); +} + +/** 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(), tools::Long(-nChgHght)) ); +} + +bool SwNoTextFrame::GetCharRect( SwRect &rRect, const SwPosition& rPos, + SwCursorMoveState *pCMS, bool /*bAllowFarAway*/ ) const +{ + if ( &rPos.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.Overlaps( 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 +{ + pPos->Assign(*GetNode()); + 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::OnGraphicArrived() +{ + if(GetNode()->GetNodeType() != SwNodeType::Grf) + { + InvalidatePrt(); + SetCompletePaint(); + return; + } + SwGrfNode* pNd = static_cast<SwGrfNode*>(GetNode()); + ClearCache(); + auto pVSh = pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell(); + if(pVSh) + pVSh->OnGraphicArrived(getFrameArea()); +} + +void SwNoTextFrame::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if(dynamic_cast<const sw::GrfRereadAndInCacheHint*>(&rHint)) + { + if(SwNodeType::Grf != GetNode()->GetNodeType()) + { + InvalidatePrt(); + SetCompletePaint(); + } + return; + } + if(rHint.GetId() == SfxHintId::SwPreGraphicArrived + || rHint.GetId() == SfxHintId::SwGraphicPieceArrived + || rHint.GetId() == SfxHintId::SwLinkedGraphicStreamArrived) + { + OnGraphicArrived(); + return; + } + else if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + sal_uInt16 nWhich = pLegacy->GetWhich(); + + SwContentFrame::SwClientNotify(rModify, rHint); + + bool bComplete = true; + + switch( nWhich ) + { + case RES_OBJECTDYING: + 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*>(pLegacy->m_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; + + default: + if ( !pLegacy->m_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. + drawinglayer::geometry::ViewInformation2D aViewInformation2D; + aViewInformation2D.setObjectTransformation(aMappingTransform); + aViewInformation2D.setViewTransformation(rOutputDevice.GetViewTransformation()); + aViewInformation2D.setViewport(rTargetRange); + + // get a primitive processor for rendering + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D( + drawinglayer::processor2d::createProcessor2DFromOutputDevice( + rOutputDevice, aViewInformation2D) ); + + // render and cleanup + pProcessor2D->process(rSequence); + return true; + } + } + + return false; +} + +// MM02 original using fallback 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()) + { + basegfx::B2DPolyPolygon aClip(rOutputDevice.GetClipRegion().GetAsB2DPolyPolygon()); + + if(0 != aClip.count()) + { + rContent.resize(1); + rContent[0] = + new drawinglayer::primitive2d::MaskPrimitive2D( + std::move(aClip), + drawinglayer::primitive2d::Primitive2DContainer(rContent)); + } + } + + if(!rName.isEmpty() || !rTitle.isEmpty() || !rDescription.isEmpty()) + { + // Embed to ObjectInfoPrimitive2D when we have Name/Title/Description + // information available + rContent.resize(1); + rContent[0] = + new drawinglayer::primitive2d::ObjectInfoPrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer(rContent), + rName, + rTitle, + rDescription); + } + + 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 void createPrimitive2DSequence( + const sdr::contact::DisplayInfo& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override; + + // tdf#155190 disable this so superclass doesn't wrongly produce NonStruct + virtual bool isExportPDFTags() const override { return false; } + +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); +}; + +void ViewObjectContactOfSwNoTextFrame::createPrimitive2DSequence( + const sdr::contact::DisplayInfo& /*rDisplayInfo*/, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) 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 + rVisitor.visit(new drawinglayer::primitive2d::GraphicPrimitive2D( + aGraphicTransform, + rGrfObj, + aGraphicAttr)); + } +} + +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 +) +: 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 (SwDrawView::IsAntiAliasing()) + pOut->SetAntialiasing( nFormerAntialiasingAtOutput | AntialiasingFlags::Enable ); + + ImplPaintPictureGraphic( pOut, pGrfNd, bPrn, aAlignedGrfArea, pShell, rNoTNd ); + + if ( SwDrawView::IsAntiAliasing() ) + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + else // bIsChart || pOLENd + { + // Fix for bug fdo#33781 + const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() ); + if (SwDrawView::IsAntiAliasing()) + { + AntialiasingFlags nNewAntialiasingAtOutput = nFormerAntialiasingAtOutput | AntialiasingFlags::Enable; + + // #i99665# + // Adjust AntiAliasing mode at output device for chart OLE + if ( pOLENd->IsChart() ) + nNewAntialiasingAtOutput |= AntialiasingFlags::PixelSnapHairline; + + pOut->SetAntialiasing( nNewAntialiasingAtOutput ); + } + + ImplPaintPictureBitmap( pOut, pOLENd, bIsChart, bPrn, aAlignedGrfArea, pShell ); + + // see #i99665# + if (SwDrawView::IsAntiAliasing()) + { + pOut->SetAntialiasing( nFormerAntialiasingAtOutput ); + } + } +} + +void SwNoTextFrame::ImplPaintPictureGraphic( vcl::RenderContext* pOut, + SwGrfNode* pGrfNd, bool bPrn, + const SwRect& rAlignedGrfArea, SwViewShell* pShell, + SwNoTextNode& rNoTNd ) const +{ + 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( rAlignedGrfArea, aText, *pShell, this, false ); + bContinue = false; + } + } + + if( !bContinue ) + return; + + if( !rGrfObj.GetGraphic().IsSupportedGraphic()) + { + ImplPaintPictureReplacement(rGrfObj, pGrfNd, rAlignedGrfArea, pShell); + return; + } + + 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 )) + { + ImplPaintPictureAnimate(pOut, pShell, pGrfNd, rAlignedGrfArea); + return; + } + + // 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->GetWin()->GetOutDev() + : 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()); + } +} + +void SwNoTextFrame::ImplPaintPictureAnimate(vcl::RenderContext* pOut, SwViewShell* pShell, + SwGrfNode* pGrfNd, const SwRect& rAlignedGrfArea) const +{ + OutputDevice* pVout; + if( pOut == pShell->GetOut() && SwRootFrame::FlushVout() ) + { + pVout = pOut; + pOut = pShell->GetOut(); + } + else if( pShell->GetWin() && pOut->IsVirtual() ) + { + pVout = pOut; + pOut = pShell->GetWin()->GetOutDev(); + } + else + pVout = nullptr; + + OSL_ENSURE( !pOut->IsVirtual() || + pShell->GetViewOptions()->IsPDFExport() || pShell->isOutputToWindow(), + "pOut should not be a virtual device" ); + + pGrfNd->StartGraphicAnimation(pOut, rAlignedGrfArea.Pos(), + rAlignedGrfArea.SSize(), reinterpret_cast<sal_IntPtr>(this), + pVout ); +} + +void SwNoTextFrame::ImplPaintPictureReplacement(const GraphicObject& rGrfObj, SwGrfNode* pGrfNd, + const SwRect& rAlignedGrfArea, SwViewShell* pShell) const +{ + TranslateId pResId; + + 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( rAlignedGrfArea, aText, *pShell, this, true ); +} + +void SwNoTextFrame::ImplPaintPictureBitmap( vcl::RenderContext* pOut, + SwOLENode* pOLENd, bool bIsChart, bool bPrn, const SwRect& rAlignedGrfArea, + SwViewShell* pShell ) const +{ + 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( + rAlignedGrfArea.Left(), rAlignedGrfArea.Top(), + rAlignedGrfArea.Right(), rAlignedGrfArea.Bottom()); + + bDone = paintUsingPrimitivesHelper( + *pOut, + aSequence, + aSourceRange, + aTargetRange); + } + } + + if(bDone || !pOLENd) + return; + + // SwOLENode does not have a known GraphicObject, need to + // work with Graphic instead + const Graphic* pGraphic = pOLENd->GetGraphic(); + const Point aPosition(rAlignedGrfArea.Pos()); + const Size aSize(rAlignedGrfArea.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()); + } +} + +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 .. 2PI] + // to [0 .. 360] and check modulo 90 + const tools::Long nRot(static_cast<tools::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( const 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 0000000000..9cef97ddc2 --- /dev/null +++ b/sw/source/core/doc/number.cxx @@ -0,0 +1,1656 @@ +/* -*- 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 <utility> +#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> +#include <wrtsh.hxx> + +using namespace ::com::sun::star; + +sal_uInt16 SwNumRule::snRefCount = 0; +SwNumFormat* SwNumRule::saBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +SwNumFormat* SwNumRule::saLabelAlignmentBaseFormats[ RULE_END ][ MAXLEVEL ] = { + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } }; + +const sal_uInt16 SwNumRule::saDefNumIndents[ MAXLEVEL ] = { + o3tl::toTwips(25, o3tl::Length::in100), + o3tl::toTwips(50, o3tl::Length::in100), + o3tl::toTwips(75, o3tl::Length::in100), + o3tl::toTwips(100, o3tl::Length::in100), + o3tl::toTwips(125, o3tl::Length::in100), + o3tl::toTwips(150, o3tl::Length::in100), + o3tl::toTwips(175, o3tl::Length::in100), + o3tl::toTwips(200, o3tl::Length::in100), + o3tl::toTwips(225, o3tl::Length::in100), + o3tl::toTwips(250, o3tl::Length::in100), +}; + +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 + ? *saBaseFormats[ meRuleType ][ i ] + : *saLabelAlignmentBaseFormats[ 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 ) + return; + + 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() ) + return; + + maTextNodeList.erase( aIter ); + + // Just in case we remove a node after we have marked the rule invalid, but before we have validated the tree + if (mbInvalidRuleFlag) + { + SwList* pList = rTextNode.GetDoc().getIDocumentListsAccess().getListByName( rTextNode.GetListId() ); + if (pList) + pList->InvalidateListTree(); + } +} + +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 saDefNumIndents[ nLvl ]; +} + +sal_uInt16 SwNumRule::GetBullIndent( sal_uInt8 nLvl ) +{ + OSL_ENSURE( MAXLEVEL > nLvl, "NumLevel is out of range" ); + return saDefNumIndents[ 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_aVertOrient( 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_aVertOrient( 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_aVertOrient( 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::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + // Look for the NumRules object in the Doc where this NumFormat is set. + // The format does not need to exist! + const SwCharFormat* pFormat = nullptr; + switch(pLegacy->GetWhich()) + { + case RES_ATTRSET_CHG: + case RES_FMT_CHG: + pFormat = GetCharFormat(); + break; + } + + if(pFormat && !pFormat->GetDoc()->IsInDtor()) + UpdateNumNodes(*const_cast<SwDoc*>(pFormat->GetDoc())); + else + CheckRegistration(pLegacy->m_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_aVertOrient.SetVertOrient( *pOrient ); + SvxNumberFormat::SetGraphicBrush( pBrushItem, pSize, pOrient); +} + +void SwNumFormat::UpdateNumNodes( SwDoc& rDoc ) +{ + bool bDocIsModified = rDoc.getIDocumentState().IsModified(); + bool bFnd = false; + for( SwNumRuleTable::size_type n = rDoc.GetNumRuleTable().size(); !bFnd && n; ) + { + const SwNumRule* pRule = rDoc.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 ) + rDoc.getIDocumentState().ResetModified(); +} + +const SwFormatVertOrient* SwNumFormat::GetGraphicOrientation() const +{ + sal_Int16 eOrient = SvxNumberFormat::GetVertOrient(); + if(text::VertOrientation::NONE == eOrient) + return nullptr; + else + { + const_cast<SwFormatVertOrient&>(m_aVertOrient).SetVertOrient(eOrient); + return &m_aVertOrient; + } +} + +SwNumRule::SwNumRule( OUString aNm, + const SvxNumberFormat::SvxNumPositionAndSpaceMode eDefaultNumberFormatPositionAndSpaceMode, + SwNumRuleType eType ) + : mpNumRuleMap(nullptr), + msName( std::move(aNm) ), + 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 ) +{ + if( !snRefCount++ ) // 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->SetListFormat("%" + OUString::number(n + 1) + "%."); + pFormat->SetBulletChar(numfunc::GetBulletChar(n)); + SwNumRule::saBaseFormats[ NUM_RULE ][ n ] = pFormat; + } + // position-and-space mode LABEL_ALIGNMENT + // first line indent of general numbering in inch: -0,25 inch + const tools::Long cFirstLineIndent = o3tl::toTwips(-0.25, o3tl::Length::in); + // indent values of general numbering in inch: + const tools::Long cIndentAt[ MAXLEVEL ] = { + o3tl::toTwips(50, o3tl::Length::in100), + o3tl::toTwips(75, o3tl::Length::in100), + o3tl::toTwips(100, o3tl::Length::in100), + o3tl::toTwips(125, o3tl::Length::in100), + o3tl::toTwips(150, o3tl::Length::in100), + o3tl::toTwips(175, o3tl::Length::in100), + o3tl::toTwips(200, o3tl::Length::in100), + o3tl::toTwips(225, o3tl::Length::in100), + o3tl::toTwips(250, o3tl::Length::in100), + o3tl::toTwips(275, o3tl::Length::in100), + }; + 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->SetListFormat( "%" + OUString::number(n + 1) + "%."); + pFormat->SetBulletChar( numfunc::GetBulletChar(n)); + SwNumRule::saLabelAlignmentBaseFormats[ 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::saBaseFormats[ 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::saLabelAlignmentBaseFormats[ OUTLINE_RULE ][ n ] = pFormat; + } + } + OSL_ENSURE( !msName.isEmpty(), "NumRule without a name!" ); +} + +SwNumRule::SwNumRule( const SwNumRule& rNumRule ) + : 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 ) +{ + ++snRefCount; + 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( !--snRefCount ) // the last one closes the door (?) + { + // Numbering: + SwNumFormat** ppFormats = &SwNumRule::saBaseFormats[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::saLabelAlignmentBaseFormats[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& rDoc ) +{ + for(auto& rpNumFormat : maFormats) + { + if( rpNumFormat ) + { + SwCharFormat* pFormat = rpNumFormat->GetCharFormat(); + if( pFormat && pFormat->GetDoc() != &rDoc ) + { + // copy + SwNumFormat* pNew = new SwNumFormat( *rpNumFormat ); + pNew->SetCharFormat( rDoc.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; +} + +void SwNumRule::Reset( const OUString& rName ) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + Set( n, nullptr); + + meRuleType = NUM_RULE; + msName = rName; + mbAutoRuleFlag = true; + mbInvalidRuleFlag = true; + mbContinusNum = false; + mbAbsSpaces = false; + mbHidden = false; + mnPoolFormatId = USHRT_MAX; + mnPoolHelpId = USHRT_MAX; + mnPoolHlpFileId = UCHAR_MAX; +} + +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(); +} + +namespace { +/// Strip out text that is not a delimiter. Used in STYLEREF for when you +/// have chapters labelled "Chapter X.Y" and want to just keep the "X.Y" +/// Only used on the prefix/infix/suffix, so the numbers are not modified +void StripNonDelimiter(OUString& rText) +{ + std::vector<sal_Unicode> charactersToKeep; + + for (int i = 0; i < rText.getLength(); i++) { + auto character = rText[i]; + + // tdf#86790# for Word compatibility: I haven't found any better way to determine whether a + // character is a delimiter than testing in Word and listing them out. Furthermore, I haven't + // found a list so I can't be certain this is the complete set- if there's a compatibility issue + // with this in the future, here's the first place to look... + if ( + character == '.' + || character == ',' + || character == ':' + || character == ';' + || character == '-' + || character == '(' + || character == ')' + || character == '[' + || character == ']' + || character == '{' + || character == '}' + || character == '/' + || character == '\\' + || character == '|' + ) + charactersToKeep.push_back(character); + } + + if (charactersToKeep.size()) + rText = OUString(charactersToKeep.data(), charactersToKeep.size()); + else + rText = OUString(); +} +} + +OUString SwNumRule::MakeNumString( const SwNumberTree::tNumberVector & rNumVector, + const bool bInclStrings, + const unsigned int _nRestrictToThisLevel, + const bool bHideNonNumerical, + 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; + } + + assert(nLevel < MAXLEVEL); + + const SwNumFormat& rMyNFormat = Get( o3tl::narrowing<sal_uInt16>(nLevel) ); + + if (rMyNFormat.GetNumberingType() == SVX_NUM_NUMBER_NONE) + { + if (!rMyNFormat.HasListFormat()) { + OUString sRet = bInclStrings ? rMyNFormat.GetPrefix() + rMyNFormat.GetSuffix() : OUString(); + StripNonDelimiter(sRet); + return sRet; + } + + // If numbering is disabled for this level we should emit just prefix/suffix + // Remove everything between first %1% and last %n% (including markers) + OUString sLevelFormat = rMyNFormat.GetListFormat(bInclStrings && !bHideNonNumerical); + + if (bInclStrings && bHideNonNumerical) { + // If hiding non numerical text, we need to strip the prefix and suffix properly, so let's add them manually + OUString sPrefix = rMyNFormat.GetPrefix(); + OUString sSuffix = rMyNFormat.GetSuffix(); + + StripNonDelimiter(sPrefix); + StripNonDelimiter(sSuffix); + + sLevelFormat = sPrefix + sLevelFormat + sSuffix; + } + + sal_Int32 nFirstPosition = sLevelFormat.indexOf("%"); + sal_Int32 nLastPosition = sLevelFormat.lastIndexOf("%"); + if (nFirstPosition >= 0 && nLastPosition >= nFirstPosition) + sLevelFormat = sLevelFormat.replaceAt(nFirstPosition, nLastPosition - nFirstPosition + 1, u""); + return sLevelFormat; + } + + css::lang::Locale aLocale( LanguageTag::convertToLocale(nLang)); + + if (rMyNFormat.HasListFormat()) + { + OUString sLevelFormat = rMyNFormat.GetListFormat(bInclStrings && !bHideNonNumerical); + + if (bInclStrings && bHideNonNumerical) { + OUString sPrefix = rMyNFormat.GetPrefix(); + OUString sSuffix = rMyNFormat.GetSuffix(); + + StripNonDelimiter(sPrefix); + StripNonDelimiter(sSuffix); + + sLevelFormat = sPrefix + sLevelFormat + sSuffix; + } + + // 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; + const SwNumFormat& rNFormat = Get(i); + + OUString sFind("%" + OUString::number(i + 1) + "%"); + sal_Int32 nPosition = sLevelFormat.indexOf(sFind); + + if (rNFormat.GetNumberingType() == SVX_NUM_NUMBER_NONE) + { + // Numbering disabled - replacement is empty + // And we should skip all level string content until next level marker: + // so %1%.%2%.%3% with second level as NONE will result 1.1, not 1..1 + sal_Int32 nPositionNext = sLevelFormat.indexOf('%', nPosition + sFind.getLength()); + if (nPosition >= 0 && nPositionNext >= nPosition) + { + sLevelFormat = sLevelFormat.replaceAt(nPosition, nPositionNext - nPosition, u""); + } + continue; + } + else if (rNumVector[i]) + sReplacement = Get(i).GetNumStr(rNumVector[i], aLocale, rMyNFormat.GetIsLegal()); + else + sReplacement = "0"; // all 0 level are a 0 + + if (nPosition >= 0) + { + if (bHideNonNumerical) + { + sal_Int32 nPositionNext = sLevelFormat.indexOf('%', nPosition + sFind.getLength()); + + if (nPositionNext >= nPosition) { + sal_Int32 nReplaceStart = nPosition + sFind.getLength(); + sal_Int32 nReplaceCount = nPositionNext - nReplaceStart; + + OUString sSeparator = sLevelFormat.copy(nReplaceStart, nReplaceCount); + StripNonDelimiter(sSeparator); + + sLevelFormat = sLevelFormat.replaceAt(nReplaceStart, nReplaceCount, sSeparator); + } + } + + sLevelFormat = sLevelFormat.replaceAt(nPosition, sFind.getLength(), sReplacement); + } + } + + 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]) + aStr.append(rNFormat.GetNumStr(rNumVector[i], aLocale, rMyNFormat.GetIsLegal())); + 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 && + SVX_NUM_CHAR_SPECIAL != rMyNFormat.GetNumberingType() && + SVX_NUM_BITMAP != rMyNFormat.GetNumberingType()) + { + OUString sPrefix = rMyNFormat.GetPrefix(); + OUString sSuffix = rMyNFormat.GetSuffix(); + + if (bHideNonNumerical) { + StripNonDelimiter(sPrefix); + StripNonDelimiter(sSuffix); + } + + 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 bool bHideNonNumerical ) 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( o3tl::narrowing<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, MAXLEVEL, + bHideNonNumerical, &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( o3tl::narrowing<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 ); + } + + if (aRefNumStr.endsWith(".")) + { + // tdf#144563: looks like a special case for refs by MS Word: if numbering is ending with dot, this dot is removed + aRefNumStr = aRefNumStr.copy(0, aRefNumStr.getLength() - 1); + } + + 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& rDoc, const SwNumRule& rNumRule ) +{ + for( sal_uInt16 n = 0; n < MAXLEVEL; ++n ) + { + Set( n, rNumRule.maFormats[ n ].get() ); + if( maFormats[ n ] && maFormats[ n ]->GetCharFormat() && + !rDoc.GetCharFormats()->ContainsFormat(maFormats[n]->GetCharFormat())) + { + // If we copy across different Documents, then copy the + // corresponding CharFormat into the new Document. + maFormats[n]->SetCharFormat( rDoc.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 ) + { + const SwNumFormat & rNumFormat = Get(n); + if(rNumFormat.GetCharFormat()) + { + SwNumFormat aNewFormat = rNumFormat; + aNewFormat.SetCharFormatName(rNumFormat.GetCharFormat()->GetName()); + aRule.SetLevel(n, aNewFormat, maFormats[n] != nullptr); + } + else + aRule.SetLevel(n, rNumFormat, maFormats[n] != nullptr); + } + return aRule; +} + +void SwNumRule::SetInvalidRule(bool bFlag) +{ + if (mbInvalidRuleFlag == bFlag) + return; + + 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 tools::Long nNewListTab = aTmpNumFormat.GetListtabPos() + nDiff; + aTmpNumFormat.SetListtabPos( nNewListTab ); + } + + const tools::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 tools::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(const SwDoc& rDoc) +{ + o3tl::sorted_vector< SwList* > aLists; + for ( const SwTextNode* pTextNode : maTextNodeList ) + { + SwList* pList = pTextNode->GetDoc().getIDocumentListsAccess().getListByName( pTextNode->GetListId() ); + aLists.insert( pList ); + } + + for ( auto aList : aLists ) + aList->InvalidateListTree(); + + for ( auto aList : aLists ) + aList->ValidateListTree(rDoc); + + 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 +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRule")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("msName"), BAD_CAST(msName.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mnPoolFormatId"), BAD_CAST(OString::number(mnPoolFormatId).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mbAutoRuleFlag"), BAD_CAST(OString::boolean(mbAutoRuleFlag).getStr())); + + for (const auto& pFormat : maFormats) + { + if (!pFormat) + { + continue; + } + + pFormat->dumpAsXml(pWriter); + } + + (void)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::optional<vcl::Font> mpFont; + }; + + } + + SwDefBulletConfig& SwDefBulletConfig::getInstance() + { + static SwDefBulletConfig theSwDefBulletConfig; + return theSwDefBulletConfig; + } + + 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() ) + return; + + 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.emplace( 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; + }; + + } + + SwNumberingUIBehaviorConfig& SwNumberingUIBehaviorConfig::getInstance() + { + static SwNumberingUIBehaviorConfig theSwNumberingUIBehaviorConfig; + return theSwNumberingUIBehaviorConfig; + } + + 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() ) + return; + + 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(); + } + + bool NumDownChangesIndent(const SwWrtShell& rShell) + { + SwPaM* pCursor = rShell.GetCursor(); + if (!pCursor) + { + return true; + } + + SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode(); + if (!pTextNode) + { + return true; + } + + const SwNumRule* pNumRule = pTextNode->GetNumRule(); + if (!pNumRule) + { + return true; + } + + int nOldLevel = pTextNode->GetActualListLevel(); + int nNewLevel = nOldLevel + 1; + if (nNewLevel >= MAXLEVEL) + { + return true; + } + + const SwNumFormat& rOldFormat = pNumRule->Get(nOldLevel); + if (rOldFormat.GetNumberingType() != SVX_NUM_NUMBER_NONE) + { + return true; + } + + const SwNumFormat& rNewFormat = pNumRule->Get(nNewLevel); + if (rNewFormat.GetNumberingType() != SVX_NUM_NUMBER_NONE) + { + return true; + } + + // This is the case when the numbering levels don't differ, so changing between them is not + // a better alternative to inserting a tab character. + return rOldFormat.GetIndentAt() != rNewFormat.GetIndentAt(); + } + + SvxNumberFormat::SvxNumPositionAndSpaceMode GetDefaultPositionAndSpaceMode() + { + if (utl::ConfigManager::IsFuzzing()) + return SvxNumberFormat::LABEL_ALIGNMENT; + + SvxNumberFormat::SvxNumPositionAndSpaceMode ePosAndSpaceMode; + switch (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 +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwNumRuleTable")); + for (SwNumRule* pNumRule : *this) + pNumRule->dumpAsXml(pWriter); + (void)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 0000000000..97b4e4cce9 --- /dev/null +++ b/sw/source/core/doc/poolfmt.cxx @@ -0,0 +1,311 @@ +/* -*- 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 <IDocumentListsAccess.hxx> +#include <list.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 sw::BroadcastingModify& rModify ) const +{ + // Check if we have dependent ContentNodes in the Nodes array + // (also indirect ones for derived Formats) + bool isUsed = false; + sw::AutoFormatUsedHint aHint(isUsed, GetNodes()); + rModify.CallSwClientNotify(aHint); + return isUsed; +} + +// 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 ) const +{ + SwList const*const pList(getIDocumentListsAccess().getListByName(rRule.GetDefaultListId())); + bool bUsed = rRule.GetTextNodeListSize() > 0 || + rRule.GetParagraphStyleListSize() > 0 || + rRule.IsUsedByRedline() + // tdf#135014 default num rule is used if any associated num rule is used + || (pList + && pList->GetDefaultListStyleName() == rRule.GetName() + && pList->HasNodes()); + + 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_ENVELOPE_ADDRESS: + case RES_POOLCOLL_SEND_ADDRESS: + case RES_POOLCOLL_HEADERFOOTER: + case RES_POOLCOLL_LABEL: + case RES_POOLCOLL_COMMENT: + 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 0000000000..d4e5d9da2b --- /dev/null +++ b/sw/source/core/doc/rdfhelper.cxx @@ -0,0 +1,265 @@ +/* -*- 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> +#include <unotext.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, nullptr)); + 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, nullptr)); + 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, nullptr)); + 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, nullptr)); + 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 0000000000..b5fc62ba84 --- /dev/null +++ b/sw/source/core/doc/sortopt.cxx @@ -0,0 +1,61 @@ +/* -*- 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) : + aKeys( rOpt.aKeys ), + eDirection( rOpt.eDirection ), + cDeli( rOpt.cDeli ), + nLanguage( rOpt.nLanguage ), + bTable( rOpt.bTable ), + bIgnoreCase( rOpt.bIgnoreCase ) +{ +} + +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 0000000000..b12ab6b6c1 --- /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 <bookmark.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( std::u16string_view(), OUString(), xWrt ); + break; + + case SotClipboardFormatId::RTF: + case SotClipboardFormatId::RICHTEXT: + // mba: no BaseURL for data exchange + ::GetRTFWriter( std::u16string_view(), 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()->Assign( *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() ) + return; + + 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 ) + { + SwNodeOffset nNd = rPos.GetNodeIndex(); + 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() ) + return; + + bool bCall = false; + const SwStartNode* pNd = nullptr; + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + 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->GetNodeIndex() < pNd->EndOfSectionIndex() && + pEnd->GetNodeIndex() >= pNd->GetIndex(); + } + + if( bCall ) + { + // Recognize recursions and flag them + IsLinkInServer( nullptr ); + SvLinkSource::NotifyDataChanged(); + } +} + +bool SwServerObject::IsLinkInServer( const SwBaseLink* pChkLnk ) const +{ + SwNodeOffset 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->GetNodeIndex(); + nEndNd = pEnd->GetNodeIndex(); + pNds = &pStt->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_rDoc( rPam.GetDoc() ) +{ + m_nContent = rPam.GetPoint()->GetContentIndex(); +} + +SwDataChanged::SwDataChanged( SwDoc& rDc, const SwPosition& rPos ) + : m_pPam( nullptr ), m_pPos( &rPos ), m_rDoc( rDc ) +{ + m_nContent = rPos.GetContentIndex(); +} + +SwDataChanged::~SwDataChanged() +{ + // JP 09.04.96: Only if the Layout is available (thus during input) + if( !m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() ) + return; + + const ::sfx2::SvLinkSources& rServers = m_rDoc.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()) + if (auto pServerObj = dynamic_cast<SwServerObject*>( refObj.get() )) + { + if( m_pPos ) + pServerObj->SendDataChanged( *m_pPos ); + else + pServerObj->SendDataChanged( *m_pPam ); + } + + // We shouldn't have a connection anymore + if( !refObj->HasDataLinks() ) + { + // Then remove from the list + m_rDoc.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 0000000000..6372ab10c8 --- /dev/null +++ b/sw/source/core/doc/swstylemanager.cxx @@ -0,0 +1,176 @@ +/* -*- 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 <swatrset.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 clear() { mMap.clear(); } +}; + +} + +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 m_aAutoCharPool; + StylePool m_aAutoParaPool; + SwStyleCache maCharCache; + SwStyleCache maParaCache; + +public: + // accept empty item set for ignorable paragraph items. + explicit SwStyleManager(SfxItemSet const* pIgnorableParagraphItems) + : m_aAutoParaPool(pIgnorableParagraphItems) + {} + virtual std::shared_ptr<SfxItemSet> getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName = nullptr ) override; + virtual std::shared_ptr<SwAttrSet> getAutomaticStyle( const SwAttrSet& 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() +{ + maCharCache.clear(); + maParaCache.clear(); +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName ) +{ + assert(eFamily != IStyleAccess::AUTO_STYLE_PARA || dynamic_cast<const SwAttrSet*>(&rSet)); + StylePool& rAutoPool + = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? m_aAutoCharPool : m_aAutoParaPool; + return rAutoPool.insertItemSet( rSet, pParentName ); +} + +std::shared_ptr<SwAttrSet> SwStyleManager::getAutomaticStyle( const SwAttrSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily, + const OUString* pParentName ) +{ + StylePool& rAutoPool + = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? m_aAutoCharPool : m_aAutoParaPool; + std::shared_ptr<SfxItemSet> pItemSet = rAutoPool.insertItemSet( rSet, pParentName ); + std::shared_ptr<SwAttrSet> pAttrSet = std::dynamic_pointer_cast<SwAttrSet>(pItemSet); + assert(bool(pItemSet) == bool(pAttrSet) && "types do not match"); + return pAttrSet; +} + +std::shared_ptr<SfxItemSet> SwStyleManager::cacheAutomaticStyle( const SfxItemSet& rSet, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + assert(eFamily != IStyleAccess::AUTO_STYLE_PARA || dynamic_cast<const SwAttrSet*>(&rSet)); + StylePool& rAutoPool + = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? m_aAutoCharPool : m_aAutoParaPool; + std::shared_ptr<SfxItemSet> pStyle = rAutoPool.insertItemSet( rSet ); + if (eFamily == IStyleAccess::AUTO_STYLE_CHAR) + { + maCharCache.addStyleName( pStyle ); + } + else + { + maParaCache.addStyleName( pStyle ); + } + return pStyle; +} + +std::shared_ptr<SfxItemSet> SwStyleManager::getByName( const OUString& rName, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool + = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? m_aAutoCharPool : m_aAutoParaPool; + SwStyleCache &rCache = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? maCharCache : maParaCache; + std::shared_ptr<SfxItemSet> pStyle = rCache.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" ); + rCache.addCompletePool( rAutoPool ); + pStyle = rCache.getByName( rName ); + } + assert(!pStyle || eFamily != IStyleAccess::AUTO_STYLE_PARA || dynamic_cast<SwAttrSet*>(pStyle.get())); + return pStyle; +} + +void SwStyleManager::getAllStyles( std::vector<std::shared_ptr<SfxItemSet>> &rStyles, + IStyleAccess::SwAutoStyleFamily eFamily ) +{ + StylePool& rAutoPool + = eFamily == IStyleAccess::AUTO_STYLE_CHAR ? m_aAutoCharPool : m_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 ) + { + assert(eFamily != IStyleAccess::AUTO_STYLE_PARA || dynamic_cast<SwAttrSet*>(pStyle.get())); + 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 0000000000..4b27b02746 --- /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 0000000000..65bff647eb --- /dev/null +++ b/sw/source/core/doc/tblafmt.cxx @@ -0,0 +1,1256 @@ +/* -*- 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/numformat.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/formatbreakitem.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 <unostyle.hxx> + +#include <memory> +#include <utility> +#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::s_pDefaultBoxAutoFormat = nullptr; + +constexpr OUString AUTOTABLE_FORMAT_NAME = u"autotbl.fmt"_ustr; + +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) + : mrStream(rStream) + , mnWhereToWriteEndOfBlock(BeginSwBlock(rStream)) + { + } + + ~WriterSpecificAutoFormatBlock() { EndSwBlock(mrStream, mnWhereToWriteEndOfBlock); } + + private: + WriterSpecificAutoFormatBlock(WriterSpecificAutoFormatBlock const&) = delete; + WriterSpecificAutoFormatBlock& operator=(WriterSpecificAutoFormatBlock const&) = delete; + + SvStream& mrStream; + sal_uInt64 mnWhereToWriteEndOfBlock; + }; + + /// 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() +: 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() +: 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_eSysLanguage(::GetAppLanguage()), + m_eNumFormatLanguage(::GetAppLanguage()) +{ + // 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>( TypedWhichId<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, TypedWhichId<SvxRotateModeItem>(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 ) +{ +} + +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(); +} + +void SwBoxAutoFormat::SetXObject(rtl::Reference<SwXTextCellStyle> const& xObject) +{ + m_xAutoFormatUnoObject = xObject.get(); +} + +SwTableAutoFormat::SwTableAutoFormat( OUString aName ) + : m_aName( std::move(aName) ) + , m_nStrResId( USHRT_MAX ) + , 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_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_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( !s_pDefaultBoxAutoFormat ) + s_pDefaultBoxAutoFormat = new SwBoxAutoFormat; + return *s_pDefaultBoxAutoFormat; + } +} + +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( !s_pDefaultBoxAutoFormat ) + s_pDefaultBoxAutoFormat = new SwBoxAutoFormat(); + *pFormat = new SwBoxAutoFormat(*s_pDefaultBoxAutoFormat); + } + return **pFormat; +} + +const SwBoxAutoFormat& SwTableAutoFormat::GetDefaultBoxFormat() +{ + if(!s_pDefaultBoxAutoFormat) + s_pDefaultBoxAutoFormat = new SwBoxAutoFormat(); + + return *s_pDefaultBoxAutoFormat; +} + +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) ) + return; + + 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( pNFormatr && (pNumFormatItem = rSet.GetItemIfSet( RES_BOXATR_FORMAT )) && + 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) ) + return; + + 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) ) + return; + + 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); + + 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); + + if (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(pShell ? SwDoc::GetRowSplit(*pShell->getShellCursor(false)) : nullptr); + m_bRowSplit = pRowSplit && pRowSplit->GetValue(); + pRowSplit.reset(); + + const SfxItemSet &rSet = pFormat->GetAttrSet(); + + 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::HasHeaderRow() const +{ // Wild guessing for PDF export: is header different from odd or body? + // It would be vastly better to do like SdrTableObj and have flags that + // determine which "special" styles apply, instead of horrible guessing. + return !(GetBoxFormat(1) == GetBoxFormat(5)) + || !(GetBoxFormat(1) == GetBoxFormat(10)); +} + +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)) + { + //this only exists for file format compat + SvxFormatBreakItem aBreak(SvxBreak::NONE, RES_BREAK); + legacy::SvxFormatBreak::Create(aBreak, rStream, 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); + //this only exists for file format compat + SvxFormatBreakItem aBreak(SvxBreak::NONE, RES_BREAK); + legacy::SvxFormatBreak::Store(aBreak, rStream, legacy::SvxFormatBreak::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( !s_pDefaultBoxAutoFormat ) + s_pDefaultBoxAutoFormat = new SwBoxAutoFormat; + pFormat = s_pDefaultBoxAutoFormat; + } + 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; +} + +void SwTableAutoFormat::SetXObject(rtl::Reference<SwXTextTableStyle> const& xObject) +{ + m_xUnoTextTableStyle = xObject.get(); +} + +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(std::u16string_view 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, SvxBorderLineWidth::VeryThin ); + 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; + sal_uInt64 nPos = rStream.Tell(); + rStream.ReadUChar( nCnt ).ReadUChar( nChrSet ); + if( rStream.Tell() != 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.FlushBuffer(); + 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(std::u16string_view 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(std::u16string_view 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 0000000000..0b11ea6c80 --- /dev/null +++ b/sw/source/core/doc/tblcpy.cxx @@ -0,0 +1,1032 @@ +/* -*- 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/numformat.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 <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() ) + return; + + 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, o3tl::narrowing<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 = o3tl::narrowing<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 ) + return; + + 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 ) + return; + + const SwTableLines &rLines = rTable.GetTabLines(); + const sal_uInt16 nLineCount = rLines.size(); + if( nLineCount < mnAddLine ) + mnAddLine = nLineCount; + sal_uInt16 nLine = o3tl::narrowing<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(); + tools::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(), SwNodeOffset(1), + *pCpyBox->GetSttNd()->EndOfSectionNode() ) : nullptr ); + + SwNodeIndex aInsIdx( *pDstBox->GetSttNd(), bDelContent ? SwNodeOffset(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.GetNode(), nullptr, false); + else + pDoc->GetNodes().MakeTextNode( aInsIdx.GetNode(), 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 ); + assert(pCNd); // keep coverity happy + aMvPos.SetContent( pCNd->Len() ); + SwDoc::CorrAbs( aInsIdx, aEndNdIdx, aMvPos ); + } + + // If we still have FlyFrames hanging around, delete them too + for(sw::SpzFrameFormat* pFly: *pDoc->GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + aInsIdx <= *pAnchorNode && *pAnchorNode <= aEndNdIdx.GetNode() ) + { + 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 ) + return; + + 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( + o3tl::narrowing<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 ) + return; + + SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aBoxAttrSet( pCpyDoc->GetAttrPool() ); + aBoxAttrSet.Put( pCpyBox->GetFrameFormat()->GetAttrSet() ); + if( !aBoxAttrSet.Count() ) + return; + + const SwTableBoxNumFormat* pItem; + SvNumberFormatter* pN = pDoc->GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && + (pItem = aBoxAttrSet.GetItemIfSet( RES_BOXATR_FORMAT, false )) ) + { + sal_uLong nOldIdx = 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 ); + + { + const_cast<SwTable*>(&rCpyTable)->SwitchFormulasToRelativeRepresentation(); + } + + // 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 = SwDoc::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(); + + const_cast<SwTable*>(&rCpyTable)->SwitchFormulasToRelativeRepresentation(); + + 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 ); + + pTmp = pCpyBox->FindNextBox( rCpyTable, pCpyBox, false ); + if( !pTmp ) + break; // no more Boxes + pCpyBox = pTmp; + + pTmp = pMyBox->FindNextBox( *this, pMyBox, false ); + if( !pTmp ) + 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; + } + + { + const_cast<SwTable*>(&rCpyTable)->SwitchFormulasToRelativeRepresentation(); + } + + // 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 0000000000..ef4b023d4f --- /dev/null +++ b/sw/source/core/doc/tblrwcl.cxx @@ -0,0 +1,3380 @@ +/* -*- 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 <osl/diagnose.h> +#include <svl/numformat.hxx> +#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& rDoc; + 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 ) + : rDoc( 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 ), rDoc(rPara.rDoc), 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 ), rDoc(rPara.rDoc), 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 SwTableLine* 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()); + + if( pCpyPara->nCpyCnt ) + { + sal_uInt16 nFndPos; + 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(); + tools::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->rDoc, 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 SwTableLine* 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; + + return pNewLine; +} + +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& rDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bBehind, bool bInsertDummy ) +{ + 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( rDoc, rBoxes, nCnt, bBehind, bInsertDummy ); + 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 = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD && nCnt) + pPCD->AddRowCols( *this, rBoxes, nCnt, bBehind ); + rDoc.UpdateCharts( GetFrameFormat()->GetName() ); + + if (SwFEShell* pFEShell = rDoc.GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(); + + return bRes; +} + +bool SwTable::InsertRow_( SwDoc* pDoc, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt, bool bBehind, bool bInsertDummy ) +{ + 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()) + { + SwTableLine* pNewTableLine = lcl_CopyRow( *rpFndLine, &aCpyPara ); + + // tracked insertion of empty table line + if ( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SvxPrintItem aSetTracking(RES_PRINT, false); + SwPosition aPos(*pNewTableLine->GetTabBoxes()[0]->GetSttNd()); + SwCursor aCursor( aPos, nullptr ); + if ( bInsertDummy ) + { + SwPaM aPaM(*pNewTableLine->GetTabBoxes()[0]->GetSttNd(), SwNodeOffset(1)); + pDoc->getIDocumentContentOperations().InsertString( aPaM, + OUStringChar(CH_TXT_TRACKED_DUMMY_CHAR) ); + } + pDoc->SetRowNotTracked( aCursor, aSetTracking, /*bAll=*/false, /*bIns=*/true ); + } + } + } + + // 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() ); + + if (SwFEShell* pFEShell = pDoc->GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(pTableNd); + + return true; +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const tools::Long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ); + +static void lcl_LastBoxSetWidthLine( SwTableLines &rLines, const tools::Long nOffset, + bool bFirst, SwShareBoxFormats& rShareFormats ) +{ + for ( auto pLine : rLines ) + ::lcl_LastBoxSetWidth( pLine->GetTabBoxes(), nOffset, bFirst, rShareFormats ); +} + +static void lcl_LastBoxSetWidth( SwTableBoxes &rBoxes, const tools::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 < o3tl::narrowing<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 < o3tl::narrowing<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 + 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 + 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 + 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 < o3tl::narrowing<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 < o3tl::narrowing<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()) ) + return; + + 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& rDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt, + bool bSameHeight ) +{ + OSL_ENSURE( !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 + rDoc.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<tools::Long[]> pRowHeights; + if ( bSameHeight ) + { + pRowHeights.reset(new tools::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; + { + SwNodeOffset nSttNd = pLastBox->GetSttIdx() + 1, + nEndNd = pLastBox->GetSttNd()->EndOfSectionIndex(); + while( nSttNd < nEndNd ) + if( !rDoc.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( rDoc, 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()+SwNodeOffset(2) != pEndNd->GetIndex() ) + { + // Move TextNodes + SwNodeRange aRg( *pLastBox->GetSttNd(), SwNodeOffset(+2), *pEndNd ); + pLastBox = pNewLine->GetTabBoxes()[0]; // reset + SwNodeIndex aInsPos( *pLastBox->GetSttNd(), 1 ); + rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aInsPos.GetNode(), false); + rDoc.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& rDoc, const SwSelBoxes& rBoxes, sal_uInt16 nCnt) +{ + OSL_ENSURE( !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 + rDoc.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( rDoc, pTableNd, pInsLine, aFindFrame.pNewFrameFormat, + pSelBox, nBoxPos + i ); // insert after + + ::InsTableBox( rDoc, 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" ); + + tools::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 + nPos = pFndTableBox->GetUpper()->GetBoxPos( pFndTableBox ); + if( 0 != nPos ) + 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 < o3tl::narrowing<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()) + return; + + 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 < o3tl::narrowing<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 < o3tl::narrowing<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 tools::Long nLineCount = static_cast<tools::Long>(rTable.GetTabLines().size()); + tools::Long nMaxSpan = nLineCount; + tools::Long nMinSpan = 1; + while( nMaxSpan ) + { + SwTableLine* pLine = rTable.GetTabLines()[ nLineCount - nMaxSpan ]; + for( auto pBox : pLine->GetTabBoxes() ) + { + sal_Int32 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 + o3tl::narrowing<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 + o3tl::narrowing<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 ) + return; + + 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->rDoc.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->rDoc.GetNodes().InsBoxen( pCpyPara->pTableNd, pCpyPara->pInsLine, + aFindFrame.pNewFrameFormat, + pCpyPara->rDoc.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 + { + SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aBoxAttrSet( pCpyPara->rDoc.GetAttrPool() ); + aBoxAttrSet.Put(rFndBox.GetBox()->GetFrameFormat()->GetAttrSet()); + if( aBoxAttrSet.Count() ) + { + const SwTableBoxNumFormat* pItem; + SvNumberFormatter* pN = pCpyPara->rDoc.GetNumberFormatter( false ); + if( pN && pN->HasMergeFormatTable() && (pItem = aBoxAttrSet. + GetItemIfSet( RES_BOXATR_FORMAT, false )) ) + { + sal_uLong nOldIdx = 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(), SwNodeOffset(1), + *rFndBox.GetBox()->GetSttNd()->EndOfSectionNode() ); + SwNodeIndex aInsIdx( *pBox->GetSttNd(), 1 ); + + pFromDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly(aCpyRg, aInsIdx.GetNode(), nullptr, false); + // Delete the initial TextNode + pCpyPara->rDoc.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->rDoc.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; + + SwitchFormulasToRelativeRepresentation(); + + 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& rInsDoc, const SwPosition& rPos, + const SwSelBoxes& rSelBoxes, + bool bCpyName, const OUString& rStyleName ) 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 != &rInsDoc ) + { + rInsDoc.CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE ) ); + rInsDoc.CopyTextColl( *pSrcDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TABLE_HDLN ) ); + } + + SwTable* pNewTable = const_cast<SwTable*>(rInsDoc.InsertTable( + SwInsertTableOptions( SwInsertTableFlags::HeadlineNoBorder, 1 ), + rPos, 1, 1, GetFrameFormat()->GetHoriOrient().GetHoriOrient(), + nullptr, nullptr, false, IsNewModel() )); + if( !pNewTable ) + return false; + + SwNodeIndex aIdx( rPos.GetNode(), -1 ); + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + ++aIdx; + OSL_ENSURE( pTableNd, "Where is the TableNode now?" ); + + pTableNd->GetTable().SetRowsToRepeat( GetRowsToRepeat() ); + + pNewTable->SetTableStyleName(pTableNd->GetTable().GetTableStyleName()); + + pTableNd->GetTable().SetTableStyleName(rStyleName); + 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 = rInsDoc.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(); + + const_cast<SwTable*>(this)->SwitchFormulasToRelativeRepresentation(); + + SwTableNumFormatMerge aTNFM(*pSrcDoc, rInsDoc); + + // Also copy Names or enforce a new unique one + if( bCpyName ) + pNewTable->GetFrameFormat()->SetFormatName( 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(); // 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 != o3tl::narrowing<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 >= o3tl::narrowing<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 >= o3tl::narrowing<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< tools::ULong >(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(); + + 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 )); + + tools::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 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 = tools::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(); + + 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; + } + } + } + break; + default: break; + } + + CHECKTABLELAYOUT + + return bRet; +} + +SwFrameFormat* SwShareBoxFormat::GetFormat( tools::Long nWidth ) const +{ + SwFrameFormat *pRet = nullptr, *pTmp; + for( auto n = m_aNewFormats.size(); n; ) + if( ( pTmp = m_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 SwFormatFrameSize& rFrameSz = m_pOldFormat->GetFormatAttr( RES_FRM_SIZE, false ); + for( auto n = m_aNewFormats.size(); n; ) + if( SfxItemState::SET == ( pTmp = m_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 ) +{ + m_aNewFormats.push_back( &rNew ); +} + +bool SwShareBoxFormat::RemoveFormat( const SwFrameFormat& rFormat ) +{ + // returns true, if we can delete + if( m_pOldFormat == &rFormat ) + return true; + + std::vector<SwFrameFormat*>::iterator it = std::find( m_aNewFormats.begin(), m_aNewFormats.end(), &rFormat ); + if( m_aNewFormats.end() != it ) + m_aNewFormats.erase( it ); + return m_aNewFormats.empty(); +} + +SwShareBoxFormats::~SwShareBoxFormats() +{ +} + +SwFrameFormat* SwShareBoxFormats::GetFormat( const SwFrameFormat& rFormat, tools::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; + if( !Seek_Entry( rOld, &nPos )) + { + SwShareBoxFormat aEntry(rOld); + aEntry.AddFormat( rNew ); + m_ShareArr.insert(m_ShareArr.begin() + nPos, aEntry); + } + else + m_ShareArr[ nPos ].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 0000000000..253e39a293 --- /dev/null +++ b/sw/source/core/doc/textboxhelper.cxx @@ -0,0 +1,1960 @@ +/* -*- 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 <IDocumentState.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 <fmtfollowtextflow.hxx> +#include <frmfmt.hxx> +#include <frameformats.hxx> +#include <dflyobj.hxx> +#include <swtable.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 <tools/UnitConversion.hxx> +#include <svx/swframetypes.hxx> +#include <drawdoc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <frmatr.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/WrapTextMode.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> + +using namespace com::sun::star; + +void SwTextBoxHelper::create(SwFrameFormat* pShape, SdrObject* pObject, bool bCopyText) +{ + assert(pShape); + assert(pObject); + + // If TextBox wasn't enabled previously + if (pShape->GetOtherTextBoxFormats() && pShape->GetOtherTextBoxFormats()->GetTextBox(pObject)) + return; + + // Store the current text content of the shape + OUString sCopyableText; + + if (bCopyText) + { + if (pObject) + { + uno::Reference<text::XText> xSrcCnt(pObject->getWeakUnoShape().get(), uno::UNO_QUERY); + auto xCur = xSrcCnt->createTextCursor(); + xCur->gotoStart(false); + xCur->gotoEnd(true); + sCopyableText = xCur->getText()->getString(); + } + } + + // 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); + 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)); + + if (!pShape->GetOtherTextBoxFormats()) + { + auto pTextBox = std::make_shared<SwTextBoxNode>(SwTextBoxNode(pShape)); + pTextBox->AddTextBox(pObject, pFormat); + pShape->SetOtherTextBoxFormats(pTextBox); + pFormat->SetOtherTextBoxFormats(pTextBox); + } + else + { + auto& pTextBox = pShape->GetOtherTextBoxFormats(); + pTextBox->AddTextBox(pObject, pFormat); + pFormat->SetOtherTextBoxFormats(pTextBox); + } + // Initialize properties. + uno::Reference<beans::XPropertySet> xPropertySet(xTextFrame, uno::UNO_QUERY); + uno::Any aEmptyBorder{ 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::Any(sal_Int32(100))); + + xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::Any(text::SizeType::FIX)); + + xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::Any(text::WrapTextMode_THROUGH)); + + uno::Reference<container::XNamed> xNamed(xTextFrame, uno::UNO_QUERY); + assert(!xNamed->getName().isEmpty()); + (void)xNamed; + + // 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.GetPointNode().StartOfSectionNode()); + aSet.Put(aContent); + pShape->SetFormatAttr(aSet); + } + + DoTextBoxZOrderCorrection(pShape, pObject); + + // Also initialize the properties, which are not constant, but inherited from the shape's ones. + uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any(xShape->getSize()), pObject); + + 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), pObject); + syncProperty(pShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, + xShapePropertySet->getPropertyValue(UNO_NAME_ANCHOR_TYPE), pObject); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT), pObject); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION), pObject); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT), pObject); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION), pObject); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION), pObject); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION), pObject); + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT), pObject); + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST), pObject); + text::WritingMode eMode; + if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObject); + + changeAnchor(pShape, pObject); + syncTextBoxSize(pShape, pObject); + + // Check if the shape had text before and move it to the new textframe + if (!bCopyText || sCopyableText.isEmpty()) + return; + + if (pObject) + { + auto pSourceText = DynCastSdrTextObj(pObject); + uno::Reference<text::XTextRange> xDestText(xRealTextFrame, uno::UNO_QUERY); + + xDestText->setString(sCopyableText); + + if (pSourceText) + pSourceText->SetText(OUString()); + + pShape->GetDoc()->getIDocumentState().SetModified(); + } +} + +void SwTextBoxHelper::set(SwFrameFormat* pShapeFormat, SdrObject* pObj, + uno::Reference<text::XTextFrame> xNew) +{ + // Do not set invalid data + assert(pShapeFormat && pObj && xNew); + // Firstly find the format of the new textbox. + SwFrameFormat* pFormat = nullptr; + if (auto pTextFrame = dynamic_cast<SwXTextFrame*>(xNew.get())) + pFormat = pTextFrame->GetFrameFormat(); + if (!pFormat) + return; + + // If there is a format, check if the shape already has a textbox assigned to. + if (auto& pTextBoxNode = pShapeFormat->GetOtherTextBoxFormats()) + { + // If it has a texbox, destroy it. + if (pTextBoxNode->GetTextBox(pObj)) + pTextBoxNode->DelTextBox(pObj, true); + // And set the new one. + pTextBoxNode->AddTextBox(pObj, pFormat); + pFormat->SetOtherTextBoxFormats(pTextBoxNode); + } + else + { + // If the shape do not have a texbox node and textbox, + // create that for the shape. + auto pTextBox = std::make_shared<SwTextBoxNode>(SwTextBoxNode(pShapeFormat)); + pTextBox->AddTextBox(pObj, pFormat); + pShapeFormat->SetOtherTextBoxFormats(pTextBox); + pFormat->SetOtherTextBoxFormats(pTextBox); + } + // Initialize its properties + uno::Reference<beans::XPropertySet> xPropertySet(xNew, uno::UNO_QUERY); + uno::Any aEmptyBorder{ 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::Any(sal_Int32(100))); + xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::Any(text::SizeType::FIX)); + xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::Any(text::WrapTextMode_THROUGH)); + // Add a new name to it + uno::Reference<container::XNamed> xNamed(xNew, uno::UNO_QUERY); + assert(!xNamed->getName().isEmpty()); + (void)xNamed; + // And sync. properties. + uno::Reference<drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY); + syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any(xShape->getSize()), pObj); + uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY); + syncProperty(pShapeFormat, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, + xShapePropertySet->getPropertyValue(UNO_NAME_ANCHOR_TYPE), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_RELATION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION), pObj); + syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION), pObj); + syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION), pObj); + syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, + xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT), pObj); + drawing::TextVerticalAdjust aVertAdj = drawing::TextVerticalAdjust_CENTER; + if ((uno::Reference<beans::XPropertyState>(xShape, uno::UNO_QUERY_THROW)) + ->getPropertyState(UNO_NAME_TEXT_VERT_ADJUST) + != beans::PropertyState::PropertyState_DEFAULT_VALUE) + { + aVertAdj = xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST) + .get<drawing::TextVerticalAdjust>(); + } + xPropertySet->setPropertyValue(UNO_NAME_TEXT_VERT_ADJUST, uno::Any(aVertAdj)); + text::WritingMode eMode; + if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode) + syncProperty(pShapeFormat, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObj); + + // Do sync for the new textframe. + synchronizeGroupTextBoxProperty(&changeAnchor, pShapeFormat, pObj); + synchronizeGroupTextBoxProperty(&syncTextBoxSize, pShapeFormat, pObj); + + updateTextBoxMargin(pObj); +} + +void SwTextBoxHelper::destroy(const SwFrameFormat* pShape, const SdrObject* pObject) +{ + // If a TextBox was enabled previously + auto& pTextBox = pShape->GetOtherTextBoxFormats(); + if (pTextBox) + { + // Unlink the TextBox's text range from the original shape. + // Delete the associated TextFrame. + pTextBox->DelTextBox(pObject, true); + } +} + +bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType, + const SdrObject* pObject) +{ + DBG_TESTSOLARMUTEX(); + assert(nType == RES_FLYFRMFMT || nType == RES_DRAWFRMFMT); + if (!pFormat || pFormat->Which() != nType) + return false; + + auto& pTextBox = pFormat->GetOtherTextBoxFormats(); + if (!pTextBox) + return false; + + if (nType == RES_DRAWFRMFMT) + { + if (pObject) + return pTextBox->GetTextBox(pObject); + if (auto pObj = pFormat->FindRealSdrObject()) + return pTextBox->GetTextBox(pObj); + } + + if (nType == RES_FLYFRMFMT) + { + return pTextBox->GetOwnerShape(); + } + + return false; +} + +bool SwTextBoxHelper::hasTextFrame(const SdrObject* pObj) +{ + if (!pObj) + return false; + + uno::Reference<drawing::XShape> xShape(pObj->getWeakUnoShape().get(), uno::UNO_QUERY); + if (!xShape) + return false; + return SwTextBoxHelper::getOtherTextBoxFormat(xShape); +} + +sal_Int32 SwTextBoxHelper::getCount(SdrPage const* pPage) +{ + sal_Int32 nRet = 0; + for (const rtl::Reference<SdrObject>& p : *pPage) + { + if (p && p->IsTextBox()) + continue; + ++nRet; + } + return nRet; +} + +sal_Int32 SwTextBoxHelper::getCount(const SwDoc& rDoc) +{ + sal_Int32 nRet = 0; + for (const sw::SpzFrameFormat* pFormat : *rDoc.GetSpzFrameFormats()) + { + 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 (const rtl::Reference<SdrObject>& p : *pPage) + { + if (p && p->IsTextBox()) + continue; + if (nCount == nIndex) + { + pRet = p.get(); + break; + } + ++nCount; + } + + if (!pRet) + throw lang::IndexOutOfBoundsException(); + + return uno::Any(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 (const rtl::Reference<SdrObject>& p : *pPage) + { + 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, const SdrObject* pObject) +{ + SolarMutexGuard aGuard; + if (!isTextBox(pFormat, nType, pObject)) + return nullptr; + + if (nType == RES_DRAWFRMFMT) + { + if (pObject) + return pFormat->GetOtherTextBoxFormats()->GetTextBox(pObject); + if (pFormat->FindRealSdrObject()) + return pFormat->GetOtherTextBoxFormats()->GetTextBox(pFormat->FindRealSdrObject()); + return nullptr; + } + if (nType == RES_FLYFRMFMT) + { + return pFormat->GetOtherTextBoxFormats()->GetOwnerShape(); + } + return nullptr; +} + +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, + SdrObject::getSdrObjectFromXShape(xShape)); +} + +uno::Reference<text::XTextFrame> +SwTextBoxHelper::getUnoTextFrame(uno::Reference<drawing::XShape> const& xShape) +{ + if (xShape) + { + auto pFrameFormat = SwTextBoxHelper::getOtherTextBoxFormat(xShape); + if (pFrameFormat) + { + auto pSdrObj = pFrameFormat->FindSdrObject(); + if (pSdrObj) + { + return { pSdrObj->getUnoShape(), uno::UNO_QUERY }; + } + } + } + return {}; +} + +template <typename T> +static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny, SdrObject* pObj) +{ + if (SwFrameFormat* pFormat + = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) + { + uno::Reference<T> const xInterface( + getXWeak(SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat).get()), + uno::UNO_QUERY); + rAny <<= xInterface; + } +} + +uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType, + SdrObject* pObj) +{ + uno::Any aRet; + + if (rType == cppu::UnoType<css::text::XTextAppend>::get()) + { + lcl_queryInterface<text::XTextAppend>(pShape, aRet, pObj); + } + else if (rType == cppu::UnoType<css::text::XText>::get()) + { + lcl_queryInterface<text::XText>(pShape, aRet, pObj); + } + else if (rType == cppu::UnoType<css::text::XTextRange>::get()) + { + lcl_queryInterface<text::XTextRange>(pShape, aRet, pObj); + } + + return aRet; +} + +tools::Rectangle SwTextBoxHelper::getRelativeTextRectangle(SdrObject* pShape) +{ + tools::Rectangle aRet; + aRet.SetEmpty(); + + assert(pShape); + + auto pCustomShape = dynamic_cast<SdrObjCustomShape*>(pShape); + 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 (pShape) + { + // fallback - get *any* bound rect we can possibly get hold of + aRet = pShape->GetCurrentBoundRect(); + } + + if (pShape) + { + // Relative, so count the logic (reference) rectangle, see the EnhancedCustomShape2d ctor. + Point aPoint(pShape->GetSnapRect().Center()); + Size aSize(pShape->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, std::u16string_view rPropertyName, + const css::uno::Any& rValue, SdrObject* pObj) +{ + // Textframes does not have valid horizontal adjust property, so map it to paragraph adjust property + if (rPropertyName == UNO_NAME_TEXT_HORZADJUST) + { + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj); + if (!pFormat) + return; + + auto xTextFrame = SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat); + uno::Reference<text::XTextCursor> xCursor = xTextFrame->getText()->createTextCursor(); + + // Select all paragraphs in the textframe + xCursor->gotoStart(false); + xCursor->gotoEnd(true); + uno::Reference<beans::XPropertySet> xFrameParaProps(xCursor, uno::UNO_QUERY); + + // And simply map the property + const auto eValue = rValue.get<drawing::TextHorizontalAdjust>(); + switch (eValue) + { + case drawing::TextHorizontalAdjust::TextHorizontalAdjust_CENTER: + xFrameParaProps->setPropertyValue( + UNO_NAME_PARA_ADJUST, + uno::Any(style::ParagraphAdjust::ParagraphAdjust_CENTER)); //3 + break; + case drawing::TextHorizontalAdjust::TextHorizontalAdjust_LEFT: + xFrameParaProps->setPropertyValue( + UNO_NAME_PARA_ADJUST, + uno::Any(style::ParagraphAdjust::ParagraphAdjust_LEFT)); //0 + break; + case drawing::TextHorizontalAdjust::TextHorizontalAdjust_RIGHT: + xFrameParaProps->setPropertyValue( + UNO_NAME_PARA_ADJUST, + uno::Any(style::ParagraphAdjust::ParagraphAdjust_RIGHT)); //1 + break; + default: + SAL_WARN("sw.core", + "SwTextBoxHelper::syncProperty: unhandled TextHorizontalAdjust: " + << static_cast<sal_Int32>(eValue)); + break; + } + return; + } + + if (rPropertyName == u"CustomShapeGeometry") + { + // CustomShapeGeometry changes the textbox position offset and size, so adjust both. + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any()); + + SdrObject* pObject = pObj ? pObj : pShape->FindRealSdrObject(); + if (pObject) + { + tools::Rectangle aRectangle(pObject->GetSnapRect()); + syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION, + uno::Any(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Left())))); + syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION, + uno::Any(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Top())))); + } + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj); + if (!pFormat) + return; + + // Older documents or documents in ODF strict do not have WritingMode, but have used the + // TextRotateAngle values -90 and -270 to emulate these text directions of frames. + // ToDo: Is TextPreRotateAngle needed for diagrams or can it be removed? + 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_RL90; + break; + case -270: + nDirection = text::WritingMode2::BT_LR; + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled property value: " + "CustomShapeGeometry:TextPreRotateAngle: " + << nAngle); + break; + } + + if (nDirection) + { + syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(nDirection), pObj); + } + } + } + else if (rPropertyName == UNO_NAME_TEXT_VERT_ADJUST) + syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_AUTOGROWHEIGHT) + syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_LEFTDIST) + syncProperty(pShape, RES_BOX, LEFT_BORDER_DISTANCE, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_RIGHTDIST) + syncProperty(pShape, RES_BOX, RIGHT_BORDER_DISTANCE, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_UPPERDIST) + syncProperty(pShape, RES_BOX, TOP_BORDER_DISTANCE, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_LOWERDIST) + syncProperty(pShape, RES_BOX, BOTTOM_BORDER_DISTANCE, rValue, pObj); + else if (rPropertyName == UNO_NAME_TEXT_WRITINGMODE) + { + text::WritingMode eMode; + sal_Int16 eMode2; + if (rValue >>= eMode) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObj); + else if (rValue >>= eMode2) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(eMode2), pObj); + } + else if (rPropertyName == u"WritingMode") + { + sal_Int16 eMode2; + if (rValue >>= eMode2) + syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(eMode2), pObj); + } + else + SAL_INFO("sw.core", "SwTextBoxHelper::syncProperty: unhandled property: " + << static_cast<OUString>(rPropertyName)); +} + +void SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + css::uno::Any& rValue) +{ + if (!pShape) + return; + + nMemberID &= ~CONVERT_TWIPS; + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); + if (!pFormat) + return; + + if (nWID != RES_CHAIN) + return; + + 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; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::getProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID)); + break; + } +} + +css::uno::Any SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, const OUString& rPropName) +{ + if (!pShape) + return {}; + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); + if (!pFormat) + return {}; + + rtl::Reference<SwXTextFrame> xPropertySet + = SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat); + + return xPropertySet->getPropertyValue(rPropName); +} + +void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID, + const css::uno::Any& rValue, SdrObject* pObj) +{ + // No shape yet? Then nothing to do, initial properties are set by create(). + if (!pShape) + return; + + uno::Any aValue(rValue); + nMemberID &= ~CONVERT_TWIPS; + + SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj); + if (!pFormat) + return; + + 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: + if (pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + aPropertyName = UNO_NAME_HORI_ORIENT_RELATION; + else + return; + break; + case MID_HORIORIENT_POSITION: + aPropertyName = UNO_NAME_HORI_ORIENT_POSITION; + bAdjustX = true; + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + 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; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + break; + } + break; + } + case RES_VERT_ORIENT: + switch (nMemberID) + { + case MID_VERTORIENT_ORIENT: + aPropertyName = UNO_NAME_VERT_ORIENT; + break; + case MID_VERTORIENT_RELATION: + if (pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + aPropertyName = UNO_NAME_VERT_ORIENT_RELATION; + else + return; + break; + case MID_VERTORIENT_POSITION: + aPropertyName = UNO_NAME_VERT_ORIENT_POSITION; + bAdjustY = true; + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + break; + } + break; + case RES_FRM_SIZE: + switch (nMemberID) + { + case MID_FRMSIZE_WIDTH_TYPE: + aPropertyName = UNO_NAME_WIDTH_TYPE; + break; + 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: + { + changeAnchor(pShape, pObj); + return; + } + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + 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; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + 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; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + 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; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) + << " (which-id: " << nWID << ")"); + break; + } + break; + default: + SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled which-id: " + << nWID << " (member-id: " + << o3tl::narrowing<sal_uInt16>(nMemberID) << ")"); + break; + } + + if (aPropertyName.isEmpty()) + return; + + // Position/size should be the text position/size, not the shape one as-is. + if (bAdjustX || bAdjustY || bAdjustSize) + { + changeAnchor(pShape, pObj); + tools::Rectangle aRect + = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject()); + if (!aRect.IsEmpty()) + { + if (bAdjustX || bAdjustY) + { + sal_Int32 nValue; + if (aValue >>= nValue) + { + nValue += convertTwipToMm100(bAdjustX ? aRect.Left() : aRect.Top()); + aValue <<= nValue; + } + } + else if (bAdjustSize) + { + awt::Size aSize(convertTwipToMm100(aRect.getOpenWidth()), + convertTwipToMm100(aRect.getOpenHeight())); + aValue <<= aSize; + } + } + } + auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats()); + rtl::Reference<SwXTextFrame> const xPropertySet + = SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat); + xPropertySet->setPropertyValue(aPropertyName, aValue); +} + +void SwTextBoxHelper::saveLinks(const sw::FrameFormats<sw::SpzFrameFormat*>& 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; + } +} + +text::TextContentAnchorType SwTextBoxHelper::mapAnchorType(const RndStdIds& rAnchorID) +{ + text::TextContentAnchorType aAnchorType; + switch (rAnchorID) + { + case RndStdIds::FLY_AS_CHAR: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AS_CHARACTER; + break; + case RndStdIds::FLY_AT_CHAR: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_CHARACTER; + break; + case RndStdIds::FLY_AT_PARA: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH; + break; + case RndStdIds::FLY_AT_PAGE: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PAGE; + break; + case RndStdIds::FLY_AT_FLY: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_FRAME; + break; + default: + aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH; + SAL_WARN("sw.core", "SwTextBoxHelper::mapAnchorType: Unknown AnchorType!"); + break; + } + return aAnchorType; +} + +void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet, + SdrObject* pObj) +{ + SwFrameFormat* pFormat = getOtherTextBoxFormat(&rShape, RES_DRAWFRMFMT, pObj); + if (!pFormat) + return; + + const bool bInlineAnchored = rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR; + const bool bLayoutInCell = rShape.GetFollowTextFlow().GetValue() + && rShape.GetAnchor().GetAnchorNode() + && rShape.GetAnchor().GetAnchorNode()->FindTableNode(); + SfxItemSet aTextBoxSet(pFormat->GetDoc()->GetAttrPool(), aFrameFormatSetRange); + + SfxItemIter aIter(rSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + + do + { + switch (pItem->Which()) + { + case RES_VERT_ORIENT: + { + // The new position can be with anchor changing so sync it! + const text::TextContentAnchorType aNewAnchorType + = mapAnchorType(rShape.GetAnchor().GetAnchorId()); + syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, uno::Any(aNewAnchorType), + pObj); + if (bInlineAnchored || bLayoutInCell) + return; + SwFormatVertOrient aOrient(pItem->StaticWhichCast(RES_VERT_ORIENT)); + + tools::Rectangle aRect + = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject()); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.Top()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE + && rShape.GetAnchor().GetPageNum() != 0) + 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.getOpenHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + case RES_HORI_ORIENT: + { + // The new position can be with anchor changing so sync it! + const text::TextContentAnchorType aNewAnchorType + = mapAnchorType(rShape.GetAnchor().GetAnchorId()); + syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, uno::Any(aNewAnchorType), + pObj); + if (bInlineAnchored || bLayoutInCell) + return; + SwFormatHoriOrient aOrient(pItem->StaticWhichCast(RES_HORI_ORIENT)); + + tools::Rectangle aRect + = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject()); + if (!aRect.IsEmpty()) + aOrient.SetPos(aOrient.GetPos() + aRect.Left()); + + if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE + && rShape.GetAnchor().GetPageNum() != 0) + 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 + = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject()); + if (!aRect.IsEmpty()) + { + if (!bInlineAnchored) + { + aVertOrient.SetPos( + (pObj ? pObj->GetRelativePos().getX() : aVertOrient.GetPos()) + + aRect.Top()); + aHoriOrient.SetPos( + (pObj ? pObj->GetRelativePos().getY() : aHoriOrient.GetPos()) + + aRect.Left()); + + aTextBoxSet.Put(aVertOrient); + aTextBoxSet.Put(aHoriOrient); + } + + aSize.SetWidth(aRect.getOpenWidth()); + aSize.SetHeight(aRect.getOpenHeight()); + aTextBoxSet.Put(aSize); + } + } + break; + case RES_ANCHOR: + { + if (pItem->StaticWhichCast(RES_ANCHOR) == rShape.GetAnchor()) + // the anchor have to be synced + { + const text::TextContentAnchorType aNewAnchorType + = mapAnchorType(rShape.GetAnchor().GetAnchorId()); + syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, + uno::Any(aNewAnchorType), pObj); + } + else + { + SAL_WARN("sw.core", "SwTextBoxHelper::syncFlyFrameAttr: The anchor of the " + "shape different from the textframe!"); + } + } + 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()) + { + auto aGuard = SwTextBoxLockGuard(*rShape.GetOtherTextBoxFormats()); + pFormat->SetFormatAttr(aTextBoxSet); + } + DoTextBoxZOrderCorrection(&rShape, pObj); +} + +void SwTextBoxHelper::updateTextBoxMargin(SdrObject* pObj) +{ + if (!pObj) + return; + uno::Reference<drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY); + if (!xShape) + return; + uno::Reference<beans::XPropertySet> const xPropertySet(xShape, uno::UNO_QUERY); + + auto pParentFormat = getOtherTextBoxFormat(getOtherTextBoxFormat(xShape), RES_FLYFRMFMT); + if (!pParentFormat) + return; + + // Sync the padding + syncProperty(pParentFormat, UNO_NAME_TEXT_LEFTDIST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_LEFTDIST), pObj); + syncProperty(pParentFormat, UNO_NAME_TEXT_RIGHTDIST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_RIGHTDIST), pObj); + syncProperty(pParentFormat, UNO_NAME_TEXT_UPPERDIST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_UPPERDIST), pObj); + syncProperty(pParentFormat, UNO_NAME_TEXT_LOWERDIST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_LOWERDIST), pObj); + + // Sync the text aligning + syncProperty(pParentFormat, UNO_NAME_TEXT_VERTADJUST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_VERTADJUST), pObj); + syncProperty(pParentFormat, UNO_NAME_TEXT_HORZADJUST, + xPropertySet->getPropertyValue(UNO_NAME_TEXT_HORZADJUST), pObj); + + // tdf137803: Sync autogrow: + const bool bIsAutoGrow + = xPropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT).get<bool>(); + const bool bIsAutoWrap = xPropertySet->getPropertyValue(UNO_NAME_TEXT_WORDWRAP).get<bool>(); + + syncProperty(pParentFormat, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, uno::Any(bIsAutoGrow), + pObj); + + syncProperty(pParentFormat, RES_FRM_SIZE, MID_FRMSIZE_WIDTH_TYPE, + uno::Any(bIsAutoWrap ? text::SizeType::FIX : text::SizeType::MIN), pObj); + + changeAnchor(pParentFormat, pObj); + DoTextBoxZOrderCorrection(pParentFormat, pObj); +} + +bool SwTextBoxHelper::changeAnchor(SwFrameFormat* pShape, SdrObject* pObj) +{ + if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) + { + if (!isAnchorSyncNeeded(pShape, pFormat)) + { + doTextBoxPositioning(pShape, pObj); + DoTextBoxZOrderCorrection(pShape, pObj); + if (pShape->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR + && pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR + && pFormat->GetVertOrient().GetRelationOrient() != text::RelOrientation::PRINT_AREA) + { + SwFormatVertOrient aTmp = pFormat->GetVertOrient(); + aTmp.SetRelationOrient(text::RelOrientation::PRINT_AREA); + pFormat->SetFormatAttr(aTmp); + } + + return false; + } + + const SwFormatAnchor& rOldAnch = pFormat->GetAnchor(); + const SwFormatAnchor& rNewAnch = pShape->GetAnchor(); + + const auto pOldCnt = rOldAnch.GetContentAnchor(); + const auto pNewCnt = rNewAnch.GetContentAnchor(); + + const uno::Any aShapeHorRelOrient(pShape->GetHoriOrient().GetRelationOrient()); + + try + { + auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats()); + ::sw::UndoGuard const UndoGuard(pShape->GetDoc()->GetIDocumentUndoRedo()); + rtl::Reference<SwXTextFrame> const xPropertySet + = SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat); + if (pOldCnt && rNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE + && rNewAnch.GetPageNum()) + { + uno::Any aValue(text::TextContentAnchorType_AT_PAGE); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, aShapeHorRelOrient); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_PAGE_NO, + uno::Any(rNewAnch.GetPageNum())); + } + else if (rOldAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE && pNewCnt) + { + if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + assert(pNewCnt); + uno::Any aValue(text::TextContentAnchorType_AT_CHARACTER); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, + uno::Any(text::RelOrientation::CHAR)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, + uno::Any(text::RelOrientation::PRINT_AREA)); + SwFormatAnchor aPos(pFormat->GetAnchor()); + aPos.SetAnchor(pNewCnt); + pFormat->SetFormatAttr(aPos); + } + else + { + uno::Any aValue(mapAnchorType(rNewAnch.GetAnchorId())); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, + aShapeHorRelOrient); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue); + pFormat->SetFormatAttr(rNewAnch); + } + } + else + { + if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + assert(pNewCnt); + uno::Any aValue(text::TextContentAnchorType_AT_CHARACTER); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, + uno::Any(text::RelOrientation::CHAR)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, + uno::Any(text::RelOrientation::PRINT_AREA)); + SwFormatAnchor aPos(pFormat->GetAnchor()); + aPos.SetAnchor(pNewCnt); + pFormat->SetFormatAttr(aPos); + } + else + { + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, + aShapeHorRelOrient); + if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE + && rNewAnch.GetPageNum() == 0) + { + pFormat->SetFormatAttr(SwFormatAnchor(RndStdIds::FLY_AT_PAGE, 1)); + } + else + pFormat->SetFormatAttr(pShape->GetAnchor()); + } + } + } + catch (uno::Exception& e) + { + SAL_WARN("sw.core", "SwTextBoxHelper::changeAnchor(): " << e.Message); + } + + doTextBoxPositioning(pShape, pObj); + DoTextBoxZOrderCorrection(pShape, pObj); + return true; + } + + return false; +} + +bool SwTextBoxHelper::doTextBoxPositioning(SwFrameFormat* pShape, SdrObject* pObj) +{ + // Set the position of the textboxes according to the position of its shape-pair + const bool bIsGroupObj = (pObj != pShape->FindRealSdrObject()) && pObj; + if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) + { + // Do not create undo entry for the positioning + ::sw::UndoGuard const UndoGuard(pShape->GetDoc()->GetIDocumentUndoRedo()); + auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats()); + // Special treatment for AS_CHAR textboxes: + if (pShape->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + // Get the text area of the shape + tools::Rectangle aRect + = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject()); + + // Get the left spacing of the text area of the shape + auto nLeftSpace = pShape->GetLRSpace().GetLeft(); + + // Set the textbox position at the X-axis: + SwFormatHoriOrient aNewHOri(pFormat->GetHoriOrient()); + if (bIsGroupObj && aNewHOri.GetHoriOrient() != text::HoriOrientation::NONE) + aNewHOri.SetHoriOrient(text::HoriOrientation::NONE); + aNewHOri.SetPos(aRect.Left() + nLeftSpace + + (bIsGroupObj ? pObj->GetRelativePos().getX() : 0)); + SwFormatVertOrient aNewVOri(pFormat->GetVertOrient()); + + // Special handling of group textboxes + if (bIsGroupObj) + { + // There are the following cases: + // case 1: The textbox should be in that position where the shape is. + // case 2: The shape has negative offset so that have to be subtracted + // case 3: The shape and its parent shape also has negative offset, so subtract + aNewVOri.SetPos( + ((pObj->GetRelativePos().getY()) > 0 + ? (pShape->GetVertOrient().GetPos() > 0 + ? pObj->GetRelativePos().getY() + : pObj->GetRelativePos().getY() - pShape->GetVertOrient().GetPos()) + : (pShape->GetVertOrient().GetPos() > 0 + ? 0 // Is this can be a variation? + : pObj->GetRelativePos().getY() - pShape->GetVertOrient().GetPos())) + + aRect.Top()); + } + else + { + // Simple textboxes: vertical position equals to the vertical offset of the shape + aNewVOri.SetPos( + ((pShape->GetVertOrient().GetPos()) > 0 ? pShape->GetVertOrient().GetPos() : 0) + + aRect.Top()); + } + + // Special cases when the shape is aligned to the line + if (pShape->GetVertOrient().GetVertOrient() != text::VertOrientation::NONE) + { + aNewVOri.SetVertOrient(text::VertOrientation::NONE); + switch (pShape->GetVertOrient().GetVertOrient()) + { + // Top aligned shape + case text::VertOrientation::TOP: + case text::VertOrientation::CHAR_TOP: + case text::VertOrientation::LINE_TOP: + { + aNewVOri.SetPos(aNewVOri.GetPos() - pShape->GetFrameSize().GetHeight()); + break; + } + // Bottom aligned shape + case text::VertOrientation::BOTTOM: + case text::VertOrientation::CHAR_BOTTOM: + case text::VertOrientation::LINE_BOTTOM: + { + aNewVOri.SetPos(aNewVOri.GetPos() + pShape->GetFrameSize().GetHeight()); + break; + } + // Center aligned shape + case text::VertOrientation::CENTER: + case text::VertOrientation::CHAR_CENTER: + case text::VertOrientation::LINE_CENTER: + { + aNewVOri.SetPos(aNewVOri.GetPos() + + std::lroundf(pShape->GetFrameSize().GetHeight() / 2)); + break; + } + default: + break; + } + } + + pFormat->SetFormatAttr(aNewHOri); + pFormat->SetFormatAttr(aNewVOri); + } + // Other cases when the shape has different anchor from AS_CHAR + else + { + // Text area of the shape + tools::Rectangle aRect + = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject()); + + // X Offset of the shape spacing + auto nLeftSpace = pShape->GetLRSpace().GetLeft(); + + // Set the same position as the (child) shape has + SwFormatHoriOrient aNewHOri(pShape->GetHoriOrient()); + if (bIsGroupObj && aNewHOri.GetHoriOrient() != text::HoriOrientation::NONE) + aNewHOri.SetHoriOrient(text::HoriOrientation::NONE); + + aNewHOri.SetPos( + (bIsGroupObj && pObj ? pObj->GetRelativePos().getX() : aNewHOri.GetPos()) + + aRect.Left()); + SwFormatVertOrient aNewVOri(pShape->GetVertOrient()); + aNewVOri.SetPos( + (bIsGroupObj && pObj ? pObj->GetRelativePos().getY() : aNewVOri.GetPos()) + + aRect.Top()); + + // Get the distance of the child shape inside its parent + const auto& nInshapePos + = pObj ? pObj->GetRelativePos() - pShape->FindRealSdrObject()->GetRelativePos() + : Point(); + + // Special case: the shape has relative position from the page + if (pShape->GetHoriOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME + && pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_PAGE) + { + aNewHOri.SetRelationOrient(text::RelOrientation::PAGE_FRAME); + aNewHOri.SetPos(pShape->GetHoriOrient().GetPos() + nInshapePos.getX() + + aRect.Left()); + } + + if (pShape->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME + && pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_PAGE) + { + aNewVOri.SetRelationOrient(text::RelOrientation::PAGE_FRAME); + aNewVOri.SetPos(pShape->GetVertOrient().GetPos() + nInshapePos.getY() + + aRect.Top()); + } + + // Other special case: shape is inside a table or floating table following the text flow + if (pShape->GetFollowTextFlow().GetValue() && pShape->GetAnchor().GetAnchorNode() + && pShape->GetAnchor().GetAnchorNode()->FindTableNode()) + { + // Table position + Point nTableOffset; + // Floating table + if (auto pFly + = pShape->GetAnchor().GetAnchorNode()->FindTableNode()->FindFlyStartNode()) + { + if (auto pFlyFormat = pFly->GetFlyFormat()) + { + nTableOffset.setX(pFlyFormat->GetHoriOrient().GetPos()); + nTableOffset.setY(pFlyFormat->GetVertOrient().GetPos()); + } + } + else + // Normal table + { + auto pTableNode = pShape->GetAnchor().GetAnchorNode()->FindTableNode(); + if (auto pTableFormat = pTableNode->GetTable().GetFrameFormat()) + { + nTableOffset.setX(pTableFormat->GetHoriOrient().GetPos()); + nTableOffset.setY(pTableFormat->GetVertOrient().GetPos()); + } + } + + // Add the table positions to the textbox. + aNewHOri.SetPos(aNewHOri.GetPos() + nTableOffset.getX() + nLeftSpace); + if (pShape->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME + || pShape->GetVertOrient().GetRelationOrient() + == text::RelOrientation::PAGE_PRINT_AREA) + aNewVOri.SetPos(aNewVOri.GetPos() + nTableOffset.getY()); + } + + pFormat->SetFormatAttr(aNewHOri); + pFormat->SetFormatAttr(aNewVOri); + } + return true; + } + + return false; +} + +bool SwTextBoxHelper::syncTextBoxSize(SwFrameFormat* pShape, SdrObject* pObj) +{ + if (!pShape || !pObj) + return false; + + if (auto pTextBox = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj)) + { + auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats()); + const auto& rSize = getRelativeTextRectangle(pObj).GetSize(); + if (!rSize.IsEmpty()) + { + SwFormatFrameSize aSize(pTextBox->GetFrameSize()); + aSize.SetSize(rSize); + return pTextBox->SetFormatAttr(aSize); + } + } + + return false; +} + +bool SwTextBoxHelper::DoTextBoxZOrderCorrection(SwFrameFormat* pShape, const SdrObject* pObj) +{ + // TODO: do this with group shape textboxes. + SdrObject* pShpObj = nullptr; + + pShpObj = pShape->FindRealSdrObject(); + + if (pShpObj) + { + auto pTextBox = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj); + if (!pTextBox) + return false; + SdrObject* pFrmObj = pTextBox->FindRealSdrObject(); + if (!pFrmObj) + { + // During loading there is no ready SdrObj for z-ordering, so create and cache it here + pFrmObj + = SwXTextFrame::GetOrCreateSdrObject(*dynamic_cast<SwFlyFrameFormat*>(pTextBox)); + } + if (pFrmObj) + { + // Get the draw model from the doc + SwDrawModel* pDrawModel + = pShape->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel(); + if (pDrawModel) + { + // Not really sure this will work on all pages, but it seems it will. + auto pPage = pDrawModel->GetPage(0); + // Recalc all Z-orders + pPage->RecalcObjOrdNums(); + // Here is a counter avoiding running to in infinity: + sal_uInt16 nIterator = 0; + // If the shape is behind the frame, is good, but if there are some objects + // between of them that is wrong so put the frame exactly one level higher + // than the shape. + if (pFrmObj->GetOrdNum() > pShpObj->GetOrdNum()) + pPage->SetObjectOrdNum(pFrmObj->GetOrdNum(), pShpObj->GetOrdNum() + 1); + else + // Else, if the frame is behind the shape, bring to the front of it. + while (pFrmObj->GetOrdNum() <= pShpObj->GetOrdNum()) + { + pPage->SetObjectOrdNum(pFrmObj->GetOrdNum(), pFrmObj->GetOrdNum() + 1); + // If there is any problem with the indexes, do not run over the infinity + if (pPage->GetObjCount() == pFrmObj->GetOrdNum()) + break; + ++nIterator; + if (nIterator > 300) + break; // Do not run to infinity + } + pPage->RecalcObjOrdNums(); + return true; // Success + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid Draw model for SdrObject for the shape!"); + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid SdrObject for the frame!"); + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid SdrObject for the shape!"); + + return false; +} + +void SwTextBoxHelper::synchronizeGroupTextBoxProperty(bool pFunc(SwFrameFormat*, SdrObject*), + SwFrameFormat* pFormat, SdrObject* pObj) +{ + if (auto pChildren = pObj->getChildrenOfSdrObject()) + { + for (const rtl::Reference<SdrObject>& pChildObj : *pChildren) + synchronizeGroupTextBoxProperty(pFunc, pFormat, pChildObj.get()); + } + else + { + (*pFunc)(pFormat, pObj); + } +} + +std::vector<SwFrameFormat*> SwTextBoxHelper::CollectTextBoxes(const SdrObject* pGroupObject, + SwFrameFormat* pFormat) +{ + std::vector<SwFrameFormat*> vRet; + if (auto pChildren = pGroupObject->getChildrenOfSdrObject()) + { + for (const rtl::Reference<SdrObject>& pObj : *pChildren) + { + auto pChildTextBoxes = CollectTextBoxes(pObj.get(), pFormat); + for (auto& rChildTextBox : pChildTextBoxes) + vRet.push_back(rChildTextBox); + } + } + else + { + if (isTextBox(pFormat, RES_DRAWFRMFMT, pGroupObject)) + vRet.push_back(getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT, pGroupObject)); + } + return vRet; +} + +bool SwTextBoxHelper::isAnchorSyncNeeded(const SwFrameFormat* pFirst, const SwFrameFormat* pSecond) +{ + if (!pFirst) + return false; + + if (!pSecond) + return false; + + if (pFirst == pSecond) + return false; + + if (!pFirst->GetOtherTextBoxFormats()) + return false; + + if (!pSecond->GetOtherTextBoxFormats()) + return false; + + if (pFirst->GetOtherTextBoxFormats() != pSecond->GetOtherTextBoxFormats()) + return false; + + if (pFirst->GetOtherTextBoxFormats()->GetOwnerShape() == pSecond + || pFirst == pSecond->GetOtherTextBoxFormats()->GetOwnerShape()) + { + const auto& rShapeAnchor + = pFirst->Which() == RES_DRAWFRMFMT ? pFirst->GetAnchor() : pSecond->GetAnchor(); + const auto& rFrameAnchor + = pFirst->Which() == RES_FLYFRMFMT ? pFirst->GetAnchor() : pSecond->GetAnchor(); + + if (rShapeAnchor.GetAnchorId() == rFrameAnchor.GetAnchorId()) + { + if (rShapeAnchor.GetAnchorNode() && rFrameAnchor.GetAnchorNode()) + { + if (*rShapeAnchor.GetContentAnchor() != *rFrameAnchor.GetContentAnchor()) + return true; + + return false; + } + + if (rShapeAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE + && rFrameAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { + if (rShapeAnchor.GetPageNum() == rFrameAnchor.GetPageNum()) + return false; + else + return true; + } + + return true; + } + + if (rShapeAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR + && rFrameAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + if (rShapeAnchor.GetAnchorNode() && rFrameAnchor.GetAnchorNode()) + { + if (*rShapeAnchor.GetContentAnchor() != *rFrameAnchor.GetContentAnchor()) + return true; + + return false; + } + } + return true; + } + return false; +} + +SwTextBoxNode::SwTextBoxNode(SwFrameFormat* pOwnerShape) +{ + assert(pOwnerShape); + assert(pOwnerShape->Which() == RES_DRAWFRMFMT); + + m_bIsCloningInProgress = false; + m_bLock = false; + + m_pOwnerShapeFormat = pOwnerShape; + if (!m_pTextBoxes.empty()) + m_pTextBoxes.clear(); +} + +SwTextBoxNode::~SwTextBoxNode() +{ + if (m_pTextBoxes.size() != 0) + { + SAL_WARN("sw.core", "SwTextBoxNode::~SwTextBoxNode(): Text-Box-Vector still not empty!"); + assert(false); + } +} + +void SwTextBoxNode::AddTextBox(SdrObject* pDrawObject, SwFrameFormat* pNewTextBox) +{ + assert(pNewTextBox); + assert(pNewTextBox->Which() == RES_FLYFRMFMT); + + assert(pDrawObject); + + SwTextBoxElement aElem; + aElem.m_pDrawObject = pDrawObject; + aElem.m_pTextBoxFormat = pNewTextBox; + + for (const auto& rE : m_pTextBoxes) + { + if (rE.m_pDrawObject == pDrawObject || rE.m_pTextBoxFormat == pNewTextBox) + { + SAL_WARN("sw.core", "SwTextBoxNode::AddTextBox(): Already exist!"); + return; + } + } + + auto pSwFlyDraw = dynamic_cast<SwFlyDrawObj*>(pDrawObject); + if (pSwFlyDraw) + { + pSwFlyDraw->SetTextBox(true); + } + m_pTextBoxes.push_back(aElem); +} + +void SwTextBoxNode::DelTextBox(const SdrObject* pDrawObject, bool bDelFromDoc) +{ + assert(pDrawObject); + if (m_pTextBoxes.empty()) + return; + + for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end();) + { + if (it->m_pDrawObject == pDrawObject) + { + if (bDelFromDoc) + { + it->m_pTextBoxFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( + it->m_pTextBoxFormat); + // What about m_pTextBoxes? So, when the DelLayoutFormat() removes the format + // then the ~SwFrameFormat() will call this method again to remove the entry. + return; + } + else + { + it = m_pTextBoxes.erase(it); + return; + } + } + ++it; + } + + SAL_WARN("sw.core", "SwTextBoxNode::DelTextBox(): Not found!"); +} + +void SwTextBoxNode::DelTextBox(const SwFrameFormat* pTextBox, bool bDelFromDoc) +{ + if (m_pTextBoxes.empty()) + return; + + for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end();) + { + if (it->m_pTextBoxFormat == pTextBox) + { + if (bDelFromDoc) + { + it->m_pTextBoxFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( + it->m_pTextBoxFormat); + // What about m_pTextBoxes? So, when the DelLayoutFormat() removes the format + // then the ~SwFrameFormat() will call this method again to remove the entry. + return; + } + else + { + it = m_pTextBoxes.erase(it); + return; + } + } + ++it; + } + + SAL_WARN("sw.core", "SwTextBoxNode::DelTextBox(): Not found!"); +} + +SwFrameFormat* SwTextBoxNode::GetTextBox(const SdrObject* pDrawObject) const +{ + assert(pDrawObject); + assert(m_pOwnerShapeFormat); + + if (auto& pTextBoxes = m_pOwnerShapeFormat->GetOtherTextBoxFormats()) + { + if (size_t(pTextBoxes.use_count()) != pTextBoxes->GetTextBoxCount() + size_t(1)) + { + SAL_WARN("sw.core", "SwTextBoxNode::GetTextBox(): RefCount and TexBox count mismatch!"); + } + } + + if (m_bLock) + return nullptr; + + if (!m_pTextBoxes.empty()) + { + for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++) + { + if (it->m_pDrawObject == pDrawObject) + { + return it->m_pTextBoxFormat; + } + } + SAL_WARN("sw.core", "SwTextBoxNode::GetTextBox(): Not found!"); + } + + return nullptr; +} + +void SwTextBoxNode::ClearAll() +{ + // If this called from ~SwDoc(), then only the address entries + // have to be removed, the format will be deleted by the + // the mpSpzFrameFormatTable->DeleteAndDestroyAll() in ~SwDoc()! + if (m_pOwnerShapeFormat->GetDoc()->IsInDtor()) + { + m_pTextBoxes.clear(); + return; + } + + // For loop control + sal_uInt16 nLoopCount = 0; + + // Reference not enough, copy needed. + const size_t nTextBoxCount = m_pTextBoxes.size(); + + // For loop has problems: When one entry deleted, the iterator has + // to be refreshed according to the new situation. So using While() instead. + while (!m_pTextBoxes.empty()) + { + // Delete the last textbox of the vector from the doc + // (what will call deregister in ~SwFrameFormat() + m_pOwnerShapeFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( + m_pTextBoxes.back().m_pTextBoxFormat); + + // Check if we are looping + if (nLoopCount > (nTextBoxCount + 1)) + { + SAL_WARN("sw.core", "SwTextBoxNode::ClearAll(): Maximum loop count reached!"); + break; + } + else + { + nLoopCount++; + } + } + + // Ensure the vector is empty. + if (!m_pTextBoxes.empty()) + { + SAL_WARN("sw.core", "SwTextBoxNode::ClearAll(): Text-Box-Vector still not empty!"); + assert(false); + } +} + +bool SwTextBoxNode::IsGroupTextBox() const { return m_pTextBoxes.size() > 1; } + +std::map<SdrObject*, SwFrameFormat*> SwTextBoxNode::GetAllTextBoxes() const +{ + std::map<SdrObject*, SwFrameFormat*> aRet; + for (auto& rElem : m_pTextBoxes) + { + aRet.emplace(rElem.m_pDrawObject, rElem.m_pTextBoxFormat); + } + return aRet; +} + +void SwTextBoxNode::Clone(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* o_pTarget, + bool bSetAttr, bool bMakeFrame) const +{ + if (!o_pTarget || !pDoc) + return; + + if (o_pTarget->Which() != RES_DRAWFRMFMT) + return; + + if (m_bIsCloningInProgress) + return; + + m_bIsCloningInProgress = true; + + Clone_Impl(pDoc, rNewAnc, o_pTarget, m_pOwnerShapeFormat->FindSdrObject(), + o_pTarget->FindSdrObject(), bSetAttr, bMakeFrame); + + m_bIsCloningInProgress = false; + + for (auto& rElem : m_pTextBoxes) + { + SwTextBoxHelper::changeAnchor(m_pOwnerShapeFormat, rElem.m_pDrawObject); + SwTextBoxHelper::doTextBoxPositioning(m_pOwnerShapeFormat, rElem.m_pDrawObject); + SwTextBoxHelper::DoTextBoxZOrderCorrection(m_pOwnerShapeFormat, rElem.m_pDrawObject); + SwTextBoxHelper::syncTextBoxSize(m_pOwnerShapeFormat, rElem.m_pDrawObject); + } +} + +void SwTextBoxNode::Clone_Impl(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* o_pTarget, + const SdrObject* pSrcObj, SdrObject* pDestObj, bool bSetAttr, + bool bMakeFrame) const +{ + if (!pSrcObj || !pDestObj) + return; + + auto pSrcList = pSrcObj->getChildrenOfSdrObject(); + auto pDestList = pDestObj->getChildrenOfSdrObject(); + + if (pSrcList && pDestList) + { + if (pSrcList->GetObjCount() != pDestList->GetObjCount()) + { + SAL_WARN("sw.core", "SwTextBoxNode::Clone_Impl(): Difference between the shapes!"); + return; + } + + for (auto itSrc = pSrcList->begin(), itDest = pDestList->begin(); itSrc != pSrcList->end(); + ++itSrc, ++itDest) + { + Clone_Impl(pDoc, rNewAnc, o_pTarget, itSrc->get(), itDest->get(), bSetAttr, bMakeFrame); + } + return; + } + + if (!pSrcList && !pDestList) + { + if (auto pSrcFormat = GetTextBox(pSrcObj)) + { + SwFormatAnchor aNewAnchor(rNewAnc); + if (aNewAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + aNewAnchor.SetType(RndStdIds::FLY_AT_CHAR); + + if (!bMakeFrame) + bMakeFrame = true; + } + + if (auto pTargetFormat = pDoc->getIDocumentLayoutAccess().CopyLayoutFormat( + *pSrcFormat, aNewAnchor, bSetAttr, bMakeFrame)) + { + if (!o_pTarget->GetOtherTextBoxFormats()) + { + auto pNewTextBoxes = std::make_shared<SwTextBoxNode>(SwTextBoxNode(o_pTarget)); + o_pTarget->SetOtherTextBoxFormats(pNewTextBoxes); + pNewTextBoxes->AddTextBox(pDestObj, pTargetFormat); + pTargetFormat->SetOtherTextBoxFormats(pNewTextBoxes); + } + else + { + o_pTarget->GetOtherTextBoxFormats()->AddTextBox(pDestObj, pTargetFormat); + pTargetFormat->SetOtherTextBoxFormats(o_pTarget->GetOtherTextBoxFormats()); + } + o_pTarget->SetFormatAttr(pTargetFormat->GetContent()); + } + } + } +} + +/* 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 0000000000..24db9230b3 --- /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& rD ) + : m_rDoc( rD ) +{ + StartListening( *INetURLHistory::GetOrCreate() ); +} + +SwURLStateChanged::~SwURLStateChanged() +{ + EndListening( *INetURLHistory::GetOrCreate() ); +} + +void SwURLStateChanged::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + if( !(dynamic_cast<const INetURLHistoryHint*>(&rHint) && m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()) ) + return; + + // This URL has been changed: + const INetURLObject* pIURL = static_cast<const INetURLHistoryHint&>(rHint).GetObject(); + OUString sURL( pIURL->GetMainURL( INetURLObject::DecodeMechanism::NONE ) ), sBkmk; + + SwEditShell* pESh = m_rDoc.GetEditShell(); + + if( m_rDoc.GetDocShell() && m_rDoc.GetDocShell()->GetMedium() && + // If this is our Doc, we can also have local jumps! + m_rDoc.GetDocShell()->GetMedium()->GetName() == sURL ) + sBkmk = "#" + pIURL->GetMark(); + + bool bAction = false, bUnLockView = false; + for (const SfxPoolItem* pItem : m_rDoc.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)->TriggerNodeUpdate(sw::LegacyModifyHint(&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( std::u16string_view rURL ) +{ + bool bRet = false; + if( !rURL.empty() ) + { + INetURLHistory *pHist = INetURLHistory::GetOrCreate(); + if( '#' == rURL[0] && mpDocShell && mpDocShell->GetMedium() ) + { + INetURLObject aIObj( mpDocShell->GetMedium()->GetURLObject() ); + aIObj.SetMark( rURL.substr( 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: */ |