/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include constexpr OUString S_ANNOTATION_BOOKMARK = u"____"_ustr; using namespace ::sw::mark; static bool IsAnnotationMark(const sw::mark::MarkBase& rBkmk); namespace { bool lcl_GreaterThan( const SwPosition& rPos, const SwNode& rNdIdx, std::optional 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 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(); } template bool lcl_MarkOrderingByStart(const MarkT *const pFirst, const MarkT *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; } bool const bCRFirst (IsCrossRefBookmark(*pFirst)); bool const bCRSecond(IsCrossRefBookmark(*pSecond)); if (bCRFirst == bCRSecond) { return false; // equal } return bCRFirst; // cross-ref sorts *before* } // specialise to avoid loplugin:faileddyncast template<> bool lcl_MarkOrderingByStart(const AnnotationMark *const pFirst, const AnnotationMark *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; } return false; // equal } template bool lcl_MarkOrderingByEnd(const MarkT *const pFirst, const MarkT *const pSecond) { return pFirst->GetMarkEnd() < pSecond->GetMarkEnd(); } template void lcl_InsertMarkSorted(std::vector& io_vMarks, MarkT *const pMark) { io_vMarks.insert( lower_bound( io_vMarks.begin(), io_vMarks.end(), pMark, &lcl_MarkOrderingByStart), pMark); } void lcl_PositionFromContentNode( std::optional& 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& 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 = SwNodes::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; } template struct CompareIMarkStartsBefore { bool operator()(SwPosition const& rPos, const MarkT* pMark) { return rPos < pMark->GetMarkStart(); } bool operator()(const MarkT* pMark, SwPosition const& rPos) { return pMark->GetMarkStart() < rPos; } bool operator()(const MarkT* pMark, SwNode const& rPos) { return pMark->GetMarkStart().GetNode() < rPos; } }; // Apple llvm-g++ 4.2.1 with _GLIBCXX_DEBUG won't eat boost::bind for this // Neither will MSVC 2008 with _DEBUG template struct CompareIMarkStartsAfter { bool operator()(SwPosition const& rPos, const MarkT* pMark) { return pMark->GetMarkStart() > rPos; } }; struct CompareIMarkStartsAfterReverse { bool operator()(const sw::mark::MarkBase* pMark, SwPosition const& rPos) { return pMark->GetMarkStart() > rPos; } }; template MarkT* lcl_getMarkAfter(const std::vector& 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; }; template MarkT* lcl_getMarkBefore(const std::vector& rMarks, const SwPosition& rPos, bool bLoop) { // candidates from which to choose the mark before std::vector 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 MarkT *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 ( IsAnnotationMark(*io_pMark) ) { // 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; } template bool lcl_MarkEqualByStart(const MarkT *const pFirst, const MarkT *const pSecond) { return !lcl_MarkOrderingByStart(pFirst, pSecond) && !lcl_MarkOrderingByStart(pSecond, pFirst); } template typename std::vector::const_iterator lcl_FindMark( std::vector& rMarks, const MarkT *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 ppCurrentMark; } ++ppCurrentMark; } // reached a mark starting on a later start pos or the end of the // vector => not found return rMarks.end(); }; template typename std::vector::const_iterator lcl_FindMarkAtPos( std::vector& 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 ppCurrentMark; } } // reached a mark starting on a later start pos or the end of the // vector => not found return rMarks.end(); }; template typename std::vector::const_iterator lcl_FindMarkByName( const OUString& rName, const typename std::vector::const_iterator& ppMarksBegin, const typename std::vector::const_iterator& ppMarksEnd) { return find_if( ppMarksBegin, ppMarksEnd, [&rName] (MarkT const*const pMark) { return pMark->GetName() == rName; } ); } template void lcl_DebugMarks(std::vector const& rMarks) { #if OSL_DEBUG_LEVEL > 0 SAL_INFO("sw.core", rMarks.size() << " Marks"); for (auto ppMark = rMarks.begin(); ppMark != rMarks.end(); ++ppMark) { MarkT* 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)); }; } static bool IsNavigatorReminder(const MarkBase& rBkmk) { const std::type_info* const pMarkTypeInfo = &typeid(rBkmk); // not using dynamic_cast<> here for performance return (*pMarkTypeInfo == typeid(NavigatorReminder)); } static bool IsCrossRefBookmark(const sw::mark::MarkBase& rBkmk) { // not using dynamic_cast<> here for performance const std::type_info* const pMarkTypeInfo = &typeid(rBkmk); return (*pMarkTypeInfo == typeid(CrossRefHeadingBookmark)) || (*pMarkTypeInfo == typeid(CrossRefNumItemBookmark)); } static bool IsAnnotationMark(const sw::mark::MarkBase& rBkmk) { // not using dynamic_cast<> here for performance const std::type_info* const pMarkTypeInfo = &typeid(rBkmk); return (*pMarkTypeInfo == typeid(AnnotationMark)); } IDocumentMarkAccess::MarkType IDocumentMarkAccess::GetType(const MarkBase& 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 u"__RefHeading__"_ustr; } 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::Fieldmark 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::MarkBase* 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(rPaM, rName); break; case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: pMark = std::make_unique(rPaM, rName); break; case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: pMark = std::make_unique(rPaM, rName); break; case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: pMark = std::make_unique(rPaM); break; case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: pMark = std::make_unique(rPaM); break; case IDocumentMarkAccess::MarkType::BOOKMARK: pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); break; case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: pMark = std::make_unique(rPaM); break; case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); break; case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: pMark = std::make_unique(rPaM, vcl::KeyCode(), rName); break; case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: pMark = std::make_unique(rPaM); break; case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: pMark = std::make_unique( 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, static_cast(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, static_cast(pMark.get())); break; case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: lcl_InsertMarkSorted( m_vAnnotationMarks, static_cast(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::Fieldmark* 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::MarkBase* 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::Fieldmark* pFieldMark = dynamic_cast( pMark ); if (pFieldMark) pFieldMark->SetFieldname( rType ); if (bUndoIsEnabled) { m_rDoc.GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); if (pFieldMark) m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pFieldMark)); } return pFieldMark; } ::sw::mark::Fieldmark* 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::MarkBase* 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::Fieldmark* pFieldMark = dynamic_cast( pMark ); if (pFieldMark) pFieldMark->SetFieldname( rType ); if (bUndoIsEnabled) { m_rDoc.GetIDocumentUndoRedo().DoUndo(bUndoIsEnabled); if (pFieldMark) m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique(*pFieldMark)); } m_rDoc.getIDocumentState().SetEnableSetModified(bEnableSetModified); m_rDoc.getIDocumentState().SetModified(); return pFieldMark; } ::sw::mark::MarkBase* 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::MarkBase* MarkManager::makeAnnotationMark( const SwPaM& rPaM, const OUString& rName ) { return makeMark(rPaM, rName, IDocumentMarkAccess::MarkType::ANNOTATIONMARK, sw::mark::InsertMode::New); } void MarkManager::repositionMark( ::sw::mark::MarkBase* const io_pMark, const SwPaM& rPaM) { assert(&io_pMark->GetMarkPos().GetDoc() == &m_rDoc && "" " - Mark is not in my doc."); MarkBase* const pMarkBase = 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::MarkBase* io_pMark, const OUString& rNewName ) { assert(&io_pMark->GetMarkPos().GetDoc() == &m_rDoc && "" " - 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; const OUString sOldName(io_pMark->GetName()); io_pMark->SetName(rNewName); if (dynamic_cast< ::sw::mark::Bookmark* >(io_pMark)) { if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique(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 oStartContentIdx, std::optional oEndContentIdx, bool & rbIsPosInRange, bool & rbIsOtherPosInRange) { assert(pMark); // navigator marks should not be moved // TODO: Check if this might make them invalid if (IsNavigatorReminder(*pMark)) { 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: 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* pSaveBkmk, std::optional oStartContentIdx, std::optional oEndContentIdx, bool const isReplace) { std::vector 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 > 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 >::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 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 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 MarkManager::deleteMark(const const_iterator& ppMark, bool const isMoveNodes) { std::unique_ptr ret; if (ppMark == m_vAllMarks.end()) return ret; MarkBase* pMark = *ppMark; switch(IDocumentMarkAccess::GetType(*pMark)) { case IDocumentMarkAccess::MarkType::BOOKMARK: { auto const ppBookmark = lcl_FindMark(m_vBookmarks, static_cast(pMark)); if ( ppBookmark != m_vBookmarks.end() ) { Bookmark* pBookmark = *ppBookmark; if(pBookmark) pBookmark->sendLOKDeleteCallback(); m_vBookmarks.erase(ppBookmark); } else { assert(false && " - 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, static_cast(pMark)); if ( ppBookmark != m_vBookmarks.end() ) { m_vBookmarks.erase(ppBookmark); } else { assert(false && " - 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, static_cast(pMark)); if ( ppFieldmark != m_vFieldmarks.end() ) { if(m_pLastActiveFieldmark == *ppFieldmark) ClearFieldActivation(); m_vFieldmarks.erase(ppFieldmark); ret.reset(new LazyFieldmarkDeleter(static_cast(pMark), m_rDoc, isMoveNodes)); pMark = nullptr; } else { assert(false && " - Fieldmark not found in Fieldmark container."); } } break; case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: { auto const ppAnnotationMark = lcl_FindMark(m_vAnnotationMarks, static_cast(pMark)); assert(ppAnnotationMark != m_vAnnotationMarks.end() && " - 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(aI, ppMark)); DdeBookmark* const pDdeBookmark = dynamic_cast(pMark); if (pDdeBookmark) { ret.reset(new LazyDdeBookmarkDeleter(pDdeBookmark, m_rDoc)); pMark = nullptr; } m_vAllMarks.erase(aI); // delete after we remove from the list, because the destructor can // recursively call into this method. delete pMark; // If we have a lazy deleter, pMark was null-ed return ret; } void MarkManager::deleteMark(const MarkBase* const pMark) { assert(&pMark->GetMarkPos().GetDoc() == &m_rDoc && "" " - 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(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 MarkManager::findMark(const OUString& rName) const { return lcl_FindMarkByName(rName, m_vAllMarks.begin(), m_vAllMarks.end()); } std::vector::const_iterator MarkManager::findBookmark(const OUString& rName) const { return lcl_FindMarkByName(rName, m_vBookmarks.begin(), m_vBookmarks.end()); } // find the first Bookmark that does not start before std::vector::const_iterator MarkManager::findFirstBookmarkNotStartsBefore(const SwPosition& rPos) const { return std::lower_bound( m_vBookmarks.begin(), m_vBookmarks.end(), rPos, CompareIMarkStartsBefore()); } IDocumentMarkAccess::const_iterator MarkManager::getAllMarksBegin() const { return m_vAllMarks.begin(); } IDocumentMarkAccess::const_iterator MarkManager::getAllMarksEnd() const { return m_vAllMarks.end(); } sal_Int32 MarkManager::getAllMarksCount() const { return m_vAllMarks.size(); } std::vector::const_iterator MarkManager::getBookmarksBegin() const { return m_vBookmarks.begin(); } std::vector::const_iterator MarkManager::getBookmarksEnd() const { return m_vBookmarks.end(); } sal_Int32 MarkManager::getBookmarksCount() const { return m_vBookmarks.size(); } std::vector::const_iterator MarkManager::getFieldmarksBegin() const { return m_vFieldmarks.begin(); } std::vector::const_iterator MarkManager::getFieldmarksEnd() const { return m_vFieldmarks.end(); } sal_Int32 MarkManager::getFieldmarksCount() const { return m_vFieldmarks.size(); } // finds the first that is starting after std::vector::const_iterator MarkManager::findFirstBookmarkStartsAfter(const SwPosition& rPos) const { return std::upper_bound( m_vBookmarks.begin(), m_vBookmarks.end(), rPos, CompareIMarkStartsAfter()); } Fieldmark* MarkManager::getFieldmarkAt(const SwPosition& rPos) const { auto const pFieldmark = find_if( m_vFieldmarks.begin(), m_vFieldmarks.end(), [&rPos] (::sw::mark::MarkBase const*const pMark) { auto [/*const SwPosition&*/ rStartPos, rEndPos] = pMark->GetMarkStartEnd(); return rStartPos == rPos // end position includes the CH_TXT_ATR_FIELDEND || (rEndPos.GetContentIndex() == rPos.GetContentIndex() + 1 && rEndPos.GetNode() == rPos.GetNode()); } ); return (pFieldmark == m_vFieldmarks.end()) ? nullptr : *pFieldmark; } Fieldmark* MarkManager::getInnerFieldmarkFor(const SwPosition& rPos) const { // find the first mark starting on or before the position in reverse order // (as we are reverse searching, this is the one closest to the position) // m_vFieldmarks should be ordered by mark start, so we can bisect with lower_bound auto itEnd = m_vFieldmarks.rend(); auto itStart = lower_bound( m_vFieldmarks.rbegin(), itEnd, rPos, CompareIMarkStartsAfterReverse()); // now continue a linear search for the first (still in reverse order) ending behind the position auto itCurrent = find_if( itStart, itEnd, [&rPos](const sw::mark::MarkBase* const pMark) { return rPos < pMark->GetMarkEnd(); }); // if we reached the end (in reverse order) there is no match if(itCurrent == itEnd) return nullptr; // we found our first candidate covering the position ... auto pMark = *itCurrent; auto aMarkStartEndPair = pMark->GetMarkStartEnd(); const SwPosition* pMarkStart = &aMarkStartEndPair.first; const SwPosition* pMarkEnd = &aMarkStartEndPair.second; // ... however we still need to check if there is a smaller/'more inner' one with the same start position for(++itCurrent; itCurrent != itEnd; ++itCurrent) { if((*itCurrent)->GetMarkStart() < *pMarkStart) // any following mark (in reverse order) will have an earlier // start and thus can not be more 'inner' than our previous // match, so we are done. break; const SwPosition& rCurrentMarkEnd = (*itCurrent)->GetMarkEnd(); if(rPos < rCurrentMarkEnd && rCurrentMarkEnd <= *pMarkEnd) { // both covering the position and more inner/smaller => use this one instead pMark = *itCurrent; pMarkEnd = &rCurrentMarkEnd; } } return pMark; } sw::mark::Bookmark* MarkManager::getOneInnermostBookmarkFor(const SwPosition& rPos) const { auto it = std::find_if(m_vBookmarks.begin(), m_vBookmarks.end(), [&rPos](const sw::mark::Bookmark* pMark) { return pMark->IsCoveringPosition(rPos); }); if (it == m_vBookmarks.end()) { return nullptr; } sw::mark::Bookmark* pBookmark = *it; // See if any bookmarks after the first hit are closer to rPos. ++it; for (; it != m_vBookmarks.end(); ++it) { // Find the innermost bookmark. auto [/*const SwPosition&*/ rMarkStart, rMarkEnd] = (*it)->GetMarkStartEnd(); if (rMarkStart > rPos) break; if (rPos < rMarkEnd && (pBookmark->GetMarkStart() < rMarkStart || rMarkEnd < pBookmark->GetMarkEnd())) { pBookmark = *it; } } return pBookmark; } void MarkManager::deleteFieldmarkAt(const SwPosition& rPos) { Fieldmark* const pFieldmark = getFieldmarkAt(rPos); assert(pFieldmark); // currently all callers require it to be there deleteMark(lcl_FindMark(m_vAllMarks, pFieldmark), false); } ::sw::mark::Fieldmark* MarkManager::changeFormFieldmarkType(::sw::mark::Fieldmark* 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(rCursorShell.GetSfxViewShell()); if(!pSwView) return; SwEditWin& rEditWin = pSwView->GetEditWin(); SwPosition aPos(*rCursorShell.GetCursor()->GetPoint()); Fieldmark* 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(*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) { Fieldmark *pMark = *aIter; if (pMark && pMark->GetFieldname() == ODF_FORMDROPDOWN) { bDropDownFieldExist = true; break; } } if (bDropDownFieldExist) ::sw::mark::DropDownFieldmark::SendLOKHideMessage(pViewShell); } } Fieldmark* MarkManager::getDropDownFor(const SwPosition& rPos) const { Fieldmark *pMark = getFieldmarkAt(rPos); if (!pMark || pMark->GetFieldname() != ODF_FORMDROPDOWN) return nullptr; return pMark; } std::vector MarkManager::getNoTextFieldmarksIn(const SwPaM &rPaM) const { std::vector aRet; for (auto aI = m_vFieldmarks.begin(), aEnd = m_vFieldmarks.end(); aI != aEnd; ++aI) { ::sw::mark::MarkBase* pI = *aI; const SwPosition &rStart = pI->GetMarkPos(); if (!rPaM.ContainsPosition(rStart)) continue; Fieldmark *pMark = dynamic_cast(pI); if (!pMark || (pMark->GetFieldname() != ODF_FORMDROPDOWN && pMark->GetFieldname() != ODF_FORMCHECKBOX)) { continue; } aRet.push_back(pMark); } return aRet; } Fieldmark* MarkManager::getFieldmarkAfter(const SwPosition& rPos, bool bLoop) const { return lcl_getMarkAfter(m_vFieldmarks, rPos, bLoop); } Fieldmark* MarkManager::getFieldmarkBefore(const SwPosition& rPos, bool bLoop) const { return lcl_getMarkBefore(m_vFieldmarks, rPos, bLoop); } std::vector::const_iterator MarkManager::getAnnotationMarksBegin() const { return m_vAnnotationMarks.begin(); } std::vector::const_iterator MarkManager::getAnnotationMarksEnd() const { return m_vAnnotationMarks.end(); } sal_Int32 MarkManager::getAnnotationMarksCount() const { return m_vAnnotationMarks.size(); } std::vector::const_iterator MarkManager::findAnnotationMark( const OUString& rName ) const { return lcl_FindMarkByName( rName, m_vAnnotationMarks.begin(), m_vAnnotationMarks.end() ); } AnnotationMark* MarkManager::getAnnotationMarkFor(const SwPosition& rPos) const { auto const pAnnotationMark = find_if( m_vAnnotationMarks.begin(), m_vAnnotationMarks.end(), [&rPos] (const ::sw::mark::AnnotationMark *const pMark) { return pMark->IsCoveringPosition(rPos); } ); if (pAnnotationMark == m_vAnnotationMarks.end()) return nullptr; return *pAnnotationMark; } // create helper bookmark for annotations on tracked deletions ::sw::mark::Bookmark* MarkManager::makeAnnotationBookmark(const SwPaM& rPaM, const OUString& rName, sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) { OUString sAnnotationBookmarkName(rName + S_ANNOTATION_BOOKMARK); return static_cast(makeMark( rPaM, sAnnotationBookmarkName, MarkType::BOOKMARK, eMode, pSepPos)); } // find the first AnnotationMark that does not start before std::vector::const_iterator MarkManager::findFirstAnnotationMarkNotStartsBefore(const SwPosition& rPos) const { return std::lower_bound( m_vAnnotationMarks.begin(), m_vAnnotationMarks.end(), rPos, CompareIMarkStartsBefore()); } // find the first AnnotationMark that does not start before std::vector::const_iterator MarkManager::findFirstAnnotationMarkNotStartsBefore(const SwNode& rPos) const { return std::lower_bound( m_vAnnotationMarks.begin(), m_vAnnotationMarks.end(), rPos, CompareIMarkStartsBefore()); } // find helper bookmark of annotations on tracked deletions std::vector::const_iterator 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()); auto 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(), " - a name should be proposed"); if( m_rDoc.IsInMailMerge()) { OUString newName = rName + "MailMergeMark" + DateTimeToOUString( DateTime( DateTime::SYSTEM ) ) + 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 "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::sortMarks() { sort(m_vAllMarks.begin(), m_vAllMarks.end(), &lcl_MarkOrderingByStart); sortSubsetMarks(); } 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); } template static void lcl_assureSortedMarkContainers(typename std::vector& rContainer, sal_Int32 nMinIndexModified) { // We know that the range nMinIndexModified.. has been modified, now we need to extend that range // to find the total range of elements that need to be sorted. // We know that the marks have been modified in fairly limited ways, see ContentIdxStoreImpl. sal_Int32 nMin = nMinIndexModified; while (nMin != 0) { nMin--; if (rContainer[nMin]->GetMarkStart() < rContainer[nMinIndexModified]->GetMarkStart()) break; } sort(rContainer.begin() + nMin, rContainer.end(), &lcl_MarkOrderingByStart); } template static void lcl_assureSortedMarkSubContainers(typename std::vector& rContainer, MarkT* pFound) { if (pFound) { auto it = std::find(rContainer.rbegin(), rContainer.rend(), pFound); sal_Int32 nFirstModified = std::distance(rContainer.begin(), (it+1).base()); lcl_assureSortedMarkContainers(rContainer, nFirstModified); } } /** * called when we need to sort a sub-range of the container, elements starting * at nMinIndexModified were modified. This is used from ContentIdxStoreImpl::RestoreBkmks, * where we are only modifying a small range at the end of the container. */ void MarkManager::assureSortedMarkContainers(sal_Int32 nMinIndexModified) const { // check if the modified range contains elements from the other sorted containers Bookmark* pBookmark = nullptr; Fieldmark* pFieldmark = nullptr; AnnotationMark* pAnnotationMark = nullptr; for (auto it = m_vAllMarks.begin() + nMinIndexModified; it != m_vAllMarks.end(); ++it) { switch(IDocumentMarkAccess::GetType(**it)) { case IDocumentMarkAccess::MarkType::BOOKMARK: case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK: case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK: if (!pBookmark) pBookmark = static_cast(*it); break; case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: if (!pFieldmark) pFieldmark = static_cast(*it); break; case IDocumentMarkAccess::MarkType::ANNOTATIONMARK: if (!pAnnotationMark) pAnnotationMark = static_cast(*it); break; case IDocumentMarkAccess::MarkType::DDE_BOOKMARK: case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER: case IDocumentMarkAccess::MarkType::UNO_BOOKMARK: // no special marks container break; } } auto pThis = const_cast(this); lcl_assureSortedMarkContainers(pThis->m_vAllMarks, nMinIndexModified); lcl_assureSortedMarkSubContainers(pThis->m_vBookmarks, pBookmark); lcl_assureSortedMarkSubContainers(pThis->m_vFieldmarks, pFieldmark); lcl_assureSortedMarkSubContainers(pThis->m_vAnnotationMarks, pAnnotationMark); } template static void dumpContainerAsXml(xmlTextWriterPtr pWriter, const std::vector& rContainer, const char* pName) { if (!rContainer.empty()) { (void)xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); for (auto it = rContainer.begin(); it != rContainer.end(); ++it) (*it)->dumpAsXml(pWriter); (void)xmlTextWriterEndElement(pWriter); } } void MarkManager::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkManager")); dumpContainerAsXml(pWriter, m_vAllMarks, "allmarks"); dumpContainerAsXml(pWriter, m_vBookmarks, "bookmarks"); dumpContainerAsXml(pWriter, m_vFieldmarks, "fieldmarks"); dumpContainerAsXml(pWriter, m_vAnnotationMarks, "annotationmarks"); (void)xmlTextWriterEndElement(pWriter); } } // namespace ::sw::mark namespace { bool lcl_Greater( const SwPosition& rPos, const SwNode& rNdIdx, std::optional oContentIdx ) { return rPos.GetNode() > rNdIdx || ( oContentIdx && rPos.GetNode() == rNdIdx && rPos.GetContentIndex() > *oContentIdx ); } } MarkManager& SwDoc::GetMarkManager() { return *mpMarkManager; } // 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 MarkBase& rBkmk, const SwNode& rMvPos, std::optional oContentIdx) : m_aName(rBkmk.GetName()) , m_bHidden(false) , m_eOrigBkmType(IDocumentMarkAccess::GetType(rBkmk)) { const Bookmark* const pBookmark = dynamic_cast< const Bookmark* >(&rBkmk); if(pBookmark) { m_aShortName = pBookmark->GetShortName(); m_aCode = pBookmark->GetKeyCode(); m_bHidden = pBookmark->IsHidden(); m_aHideCondition = pBookmark->GetHideCondition(); m_pMetadataUndo = pBookmark->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 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::Bookmark* const pBookmark = dynamic_cast<::sw::mark::Bookmark*>( 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) pBookmark->RestoreMetadata(m_pMetadataUndo); } // DelBookmarks void DelBookmarks( SwNode& rStt, const SwNode& rEnd, std::vector * pSaveBkmk, std::optional oStartContentIdx, std::optional 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 = SwNodes::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 = SwNodes::GoNext(pREnd); if( !pCNd ) { *pREnd = *pRStt; pCNd = pREnd->GetNode().GetContentNode(); } } if (pCNd && !bStt) pREnd->AssignEndIndex( *pCNd ); } if( lcl_Greater( *pRStt, rEnd, oEndContentIdx ) ) break; } } } 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: */