diff options
Diffstat (limited to 'sw/source/core/txtnode/atrftn.cxx')
-rw-r--r-- | sw/source/core/txtnode/atrftn.cxx | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/sw/source/core/txtnode/atrftn.cxx b/sw/source/core/txtnode/atrftn.cxx new file mode 100644 index 000000000..82485f4c5 --- /dev/null +++ b/sw/source/core/txtnode/atrftn.cxx @@ -0,0 +1,608 @@ +/* -*- 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 <fmtftn.hxx> + +#include <doc.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <cntfrm.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtftn.hxx> +#include <ftnidx.hxx> +#include <ftninfo.hxx> +#include <ndtxt.hxx> +#include <poolfmt.hxx> +#include <ftnfrm.hxx> +#include <ndindex.hxx> +#include <fmtftntx.hxx> +#include <section.hxx> +#include <calbck.hxx> +#include <hints.hxx> +#include <pam.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/svapp.hxx> +#include <unotextrange.hxx> +#include <osl/diagnose.h> + +namespace { + /// Get a sorted list of the used footnote reference numbers. + /// @param[in] rDoc The active document. + /// @param[in] pExclude A footnote whose reference number should be excluded from the set. + /// @param[out] rUsedRef The set of used reference numbers. + /// @param[out] rInvalid A returned list of all items that had an invalid reference number. + void lcl_FillUsedFootnoteRefNumbers(SwDoc &rDoc, + SwTextFootnote const *pExclude, + std::set<sal_uInt16> &rUsedRef, + std::vector<SwTextFootnote*> &rInvalid) + { + SwFootnoteIdxs& ftnIdxs = rDoc.GetFootnoteIdxs(); + + rInvalid.clear(); + + for( size_t n = 0; n < ftnIdxs.size(); ++n ) + { + SwTextFootnote* pTextFootnote = ftnIdxs[ n ]; + if ( pTextFootnote != pExclude ) + { + if ( USHRT_MAX == pTextFootnote->GetSeqRefNo() ) + { + rInvalid.push_back(pTextFootnote); + } + else + { + rUsedRef.insert( pTextFootnote->GetSeqRefNo() ); + } + } + } + } + + /// Check whether a requested reference number is available. + /// @param[in] rUsedNums Set of used reference numbers. + /// @param[in] requested The requested reference number. + /// @returns true if the number is available, false if not. + bool lcl_IsRefNumAvailable(std::set<sal_uInt16> const &rUsedNums, + sal_uInt16 requested) + { + if ( USHRT_MAX == requested ) + return false; // Invalid sequence number. + if ( rUsedNums.count(requested) ) + return false; // Number already used. + return true; + } + + /// Get the first few unused sequential reference numbers. + /// @param[out] rLowestUnusedNums The lowest unused sequential reference numbers. + /// @param[in] rUsedNums The set of used sequential reference numbers. + /// @param[in] numRequired The number of reference number required. + void lcl_FillUnusedSeqRefNums(std::vector<sal_uInt16> &rLowestUnusedNums, + const std::set<sal_uInt16> &rUsedNums, + size_t numRequired) + { + if (!numRequired) + return; + + rLowestUnusedNums.reserve(numRequired); + sal_uInt16 newNum = 0; + //Start by using numbers from gaps in rUsedNums + for( const auto& rNum : rUsedNums ) + { + while ( newNum < rNum ) + { + rLowestUnusedNums.push_back( newNum++ ); + if ( --numRequired == 0) + return; + } + newNum++; + } + //Filled in all gaps. Fill the rest of the list with new numbers. + do + { + rLowestUnusedNums.push_back( newNum++ ); + } + while ( --numRequired > 0 ); + } + +} + +SwFormatFootnote::SwFormatFootnote( bool bEndNote ) + : SfxPoolItem( RES_TXTATR_FTN ) + , sw::BroadcastingModify() + , m_pTextAttr(nullptr) + , m_nNumber(0) + , m_nNumberRLHidden(0) + , m_bEndNote(bEndNote) +{ +} + +bool SwFormatFootnote::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return m_nNumber == static_cast<const SwFormatFootnote&>(rAttr).m_nNumber && + //FIXME? + m_aNumber == static_cast<const SwFormatFootnote&>(rAttr).m_aNumber && + m_bEndNote == static_cast<const SwFormatFootnote&>(rAttr).m_bEndNote; +} + +SwFormatFootnote* SwFormatFootnote::Clone( SfxItemPool* ) const +{ + SwFormatFootnote* pNew = new SwFormatFootnote; + pNew->m_aNumber = m_aNumber; + pNew->m_nNumber = m_nNumber; + pNew->m_nNumberRLHidden = m_nNumberRLHidden; + pNew->m_bEndNote = m_bEndNote; + return pNew; +} + +void SwFormatFootnote::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + CallSwClientNotify(rHint); + if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich()) + SetXFootnote(css::uno::Reference<css::text::XFootnote>(nullptr)); +} + +void SwFormatFootnote::InvalidateFootnote() +{ + SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT, + &static_cast<sw::BroadcastingModify&>(*this)); // cast to base class (void*) + CallSwClientNotify(sw::LegacyModifyHint(&item, &item)); +} + +void SwFormatFootnote::SetEndNote( bool b ) +{ + if ( b != m_bEndNote ) + { + if ( GetTextFootnote() ) + { + GetTextFootnote()->DelFrames(nullptr); + } + m_bEndNote = b; + } +} + +SwFormatFootnote::~SwFormatFootnote() +{ +} + +OUString SwFormatFootnote::GetFootnoteText(SwRootFrame const& rLayout) const +{ + OUStringBuffer buf; + if( m_pTextAttr->GetStartNode() ) + { + SwNodeIndex aIdx( *m_pTextAttr->GetStartNode(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetTextNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + + if( pCNd->IsTextNode() ) { + buf.append(static_cast<SwTextNode*>(pCNd)->GetExpandText(&rLayout)); + + ++aIdx; + while ( !aIdx.GetNode().IsEndNode() ) { + if ( aIdx.GetNode().IsTextNode() ) + { + buf.append(" "); + buf.append(aIdx.GetNode().GetTextNode()->GetExpandText(&rLayout)); + } + ++aIdx; + } + } + } + return buf.makeStringAndClear(); +} + +/// return the view string of the foot/endnote +OUString SwFormatFootnote::GetViewNumStr(const SwDoc& rDoc, + SwRootFrame const*const pLayout, bool bInclStrings) const +{ + OUString sRet( GetNumStr() ); + if( sRet.isEmpty() ) + { + // in this case the number is needed, get it via SwDoc's FootnoteInfo + bool bMakeNum = true; + const SwSectionNode* pSectNd = m_pTextAttr + ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr ) + : nullptr; + sal_uInt16 const nNumber(pLayout && pLayout->IsHideRedlines() + ? GetNumberRLHidden() + : GetNumber()); + + if( pSectNd ) + { + const SwFormatFootnoteEndAtTextEnd& rFootnoteEnd = static_cast<const SwFormatFootnoteEndAtTextEnd&>( + pSectNd->GetSection().GetFormat()->GetFormatAttr( + IsEndNote() ? + o3tl::narrowing<sal_uInt16>(RES_END_AT_TXTEND) : + o3tl::narrowing<sal_uInt16>(RES_FTN_AT_TXTEND) ) ); + + if( FTNEND_ATTXTEND_OWNNUMANDFMT == rFootnoteEnd.GetValue() ) + { + bMakeNum = false; + sRet = rFootnoteEnd.GetSwNumType().GetNumStr( nNumber ); + if( bInclStrings ) + { + sRet = rFootnoteEnd.GetPrefix() + sRet + rFootnoteEnd.GetSuffix(); + } + } + } + + if( bMakeNum ) + { + const SwEndNoteInfo* pInfo; + if( IsEndNote() ) + pInfo = &rDoc.GetEndNoteInfo(); + else + pInfo = &rDoc.GetFootnoteInfo(); + sRet = pInfo->m_aFormat.GetNumStr( nNumber ); + if( bInclStrings ) + { + sRet = pInfo->GetPrefix() + sRet + pInfo->GetSuffix(); + } + } + } + return sRet; +} + +uno::Reference<text::XTextRange> SwFormatFootnote::getAnchor(SwDoc& rDoc) const +{ + SolarMutexGuard aGuard; + if (!m_pTextAttr) + return uno::Reference<text::XTextRange>(); + SwPaM aPam(m_pTextAttr->GetTextNode(), m_pTextAttr->GetStart()); + aPam.SetMark(); + ++aPam.GetMark()->nContent; + const uno::Reference<text::XTextRange> xRet = + SwXTextRange::CreateXTextRange(rDoc, *aPam.Start(), aPam.End()); + return xRet; +} + +SwTextFootnote::SwTextFootnote( SwFormatFootnote& rAttr, sal_Int32 nStartPos ) + : SwTextAttr( rAttr, nStartPos ) + , m_pTextNode( nullptr ) + , m_nSeqNo( USHRT_MAX ) +{ + rAttr.m_pTextAttr = this; + SetHasDummyChar(true); +} + +SwTextFootnote::~SwTextFootnote() +{ + SetStartNode( nullptr ); +} + +void SwTextFootnote::SetStartNode( const SwNodeIndex *pNewNode, bool bDelNode ) +{ + if( pNewNode ) + { + if ( !m_pStartNode ) + { + m_pStartNode.reset(new SwNodeIndex(*pNewNode)); + } + else + { + *m_pStartNode = *pNewNode; + } + } + else if ( m_pStartNode ) + { + // need to do 2 things: + // 1) unregister footnotes at their pages + // 2) delete the footnote section in the Inserts of the nodes-array + SwDoc* pDoc; + if ( m_pTextNode ) + { + pDoc = &m_pTextNode->GetDoc(); + } + else + { + //JP 27.01.97: the sw3-Reader creates a StartNode but the + // attribute isn't anchored in the TextNode yet. + // If it is deleted (e.g. Insert File with footnote + // inside fly frame), the content must also be deleted. + pDoc = &m_pStartNode->GetNodes().GetDoc(); + } + + // If called from ~SwDoc(), must not delete the footnote nodes, + // and not necessary to delete the footnote frames. + if( !pDoc->IsInDtor() ) + { + if( bDelNode ) + { + // 2) delete the section for the footnote nodes + // it's possible that the Inserts have already been deleted (how???) + pDoc->getIDocumentContentOperations().DeleteSection( &m_pStartNode->GetNode() ); + } + else + // If the nodes are not deleted, their frames must be removed + // from the page (deleted), there is nothing else that deletes + // them (particularly not Undo) + DelFrames( nullptr ); + } + m_pStartNode.reset(); + + // remove the footnote from the SwDoc's array + for( size_t n = 0; n < pDoc->GetFootnoteIdxs().size(); ++n ) + if( this == pDoc->GetFootnoteIdxs()[n] ) + { + pDoc->GetFootnoteIdxs().erase( pDoc->GetFootnoteIdxs().begin() + n ); + // if necessary, update following footnotes + if( !pDoc->IsInDtor() && n < pDoc->GetFootnoteIdxs().size() ) + { + SwNodeIndex aTmp( pDoc->GetFootnoteIdxs()[n]->GetTextNode() ); + pDoc->GetFootnoteIdxs().UpdateFootnote( aTmp ); + } + break; + } + } +} + +void SwTextFootnote::SetNumber(const sal_uInt16 nNewNum, + sal_uInt16 const nNumberRLHidden, const OUString &sNumStr) +{ + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(GetFootnote()); + + rFootnote.m_aNumber = sNumStr; + if ( sNumStr.isEmpty() ) + { + rFootnote.m_nNumber = nNewNum; + rFootnote.m_nNumberRLHidden = nNumberRLHidden; + } + InvalidateNumberInLayout(); +} + +void SwTextFootnote::InvalidateNumberInLayout() +{ + assert(m_pTextNode); + SwNodes &rNodes = m_pTextNode->GetDoc().GetNodes(); + const sw::LegacyModifyHint aHint(nullptr, &GetFootnote()); + m_pTextNode->TriggerNodeUpdate(aHint); + if ( m_pStartNode ) + { + // must iterate over all TextNodes because of footnotes on other pages + SwNodeOffset nSttIdx = m_pStartNode->GetIndex() + 1; + SwNodeOffset nEndIdx = m_pStartNode->GetNode().EndOfSectionIndex(); + for( ; nSttIdx < nEndIdx; ++nSttIdx ) + { + SwNode* pNd; + if( ( pNd = rNodes[ nSttIdx ] )->IsTextNode() ) + static_cast<SwTextNode*>(pNd)->TriggerNodeUpdate(aHint); + } + } +} + +void SwTextFootnote::CopyFootnote( + SwTextFootnote & rDest, + SwTextNode & rDestNode ) const +{ + if (m_pStartNode && !rDest.GetStartNode()) + { + // dest missing node section? create it here! + // (happens in SwTextNode::CopyText if pDest == this) + rDest.MakeNewTextSection( rDestNode.GetNodes() ); + } + if (m_pStartNode && rDest.GetStartNode()) + { + // footnotes not necessarily in same document! + SwDoc& rDstDoc = rDestNode.GetDoc(); + SwNodes &rDstNodes = rDstDoc.GetNodes(); + + // copy only the content of the section + SwNodeRange aRg( *m_pStartNode, SwNodeOffset(1), + *m_pStartNode->GetNode().EndOfSectionNode() ); + + // insert at the end of rDest, i.e., the nodes are appended. + // nDestLen contains number of ContentNodes in rDest _before_ copy. + SwNodeIndex aStart( *(rDest.GetStartNode()) ); + SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() ); + SwNodeOffset nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1; + + m_pTextNode->GetDoc().GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aEnd); + + // in case the destination section was not empty, delete the old nodes + // before: Src: SxxxE, Dst: SnE + // now: Src: SxxxE, Dst: SnxxxE + // after: Src: SxxxE, Dst: SxxxE + ++aStart; + rDstNodes.Delete( aStart, nDestLen ); + } + + // also copy user defined number string + if( !GetFootnote().m_aNumber.isEmpty() ) + { + const_cast<SwFormatFootnote &>(rDest.GetFootnote()).m_aNumber = GetFootnote().m_aNumber; + } +} + +/// create a new nodes-array section for the footnote +void SwTextFootnote::MakeNewTextSection( SwNodes& rNodes ) +{ + if ( m_pStartNode ) + return; + + // set the footnote style on the SwTextNode + SwTextFormatColl *pFormatColl; + const SwEndNoteInfo* pInfo; + sal_uInt16 nPoolId; + + if( GetFootnote().IsEndNote() ) + { + pInfo = &rNodes.GetDoc().GetEndNoteInfo(); + nPoolId = RES_POOLCOLL_ENDNOTE; + } + else + { + pInfo = &rNodes.GetDoc().GetFootnoteInfo(); + nPoolId = RES_POOLCOLL_FOOTNOTE; + } + + pFormatColl = pInfo->GetFootnoteTextColl(); + if( nullptr == pFormatColl ) + pFormatColl = rNodes.GetDoc().getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId ); + + SwStartNode* pSttNd = rNodes.MakeTextSection( SwNodeIndex( rNodes.GetEndOfInserts() ), + SwFootnoteStartNode, pFormatColl ); + m_pStartNode.reset(new SwNodeIndex(*pSttNd)); +} + +void SwTextFootnote::DelFrames(SwRootFrame const*const pRoot) +{ + // delete the FootnoteFrames from the pages + OSL_ENSURE( m_pTextNode, "SwTextFootnote: where is my TextNode?" ); + if ( !m_pTextNode ) + return; + + bool bFrameFnd = false; + { + SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*m_pTextNode); + for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + if( pRoot != pFnd->getRootFrame() && pRoot ) + continue; + SwPageFrame* pPage = pFnd->FindPageFrame(); + if( pPage ) + { + // note: we have found the correct frame only if the footnote + // was actually removed; in case this is called from + // SwTextFrame::DestroyImpl(), then that frame isn't connected + // to SwPageFrame any more, and RemoveFootnote on any follow + // must not prevent the fall-back to the !bFrameFnd code. + bFrameFnd = pPage->RemoveFootnote(pFnd, this); + } + } + } + //JP 13.05.97: if the layout is deleted before the footnotes are deleted, + // try to delete the footnote's frames by another way + if ( bFrameFnd || !m_pStartNode ) + return; + + SwNodeIndex aIdx( *m_pStartNode ); + SwContentNode* pCNd = m_pTextNode->GetNodes().GoNext( &aIdx ); + if( !pCNd ) + return; + + SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pCNd); + for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + if( pRoot != pFnd->getRootFrame() && pRoot ) + continue; + SwPageFrame* pPage = pFnd->FindPageFrame(); + + SwFrame *pFrame = pFnd->GetUpper(); + while ( pFrame && !pFrame->IsFootnoteFrame() ) + pFrame = pFrame->GetUpper(); + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pFrame); + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + OSL_ENSURE( pFootnote->GetAttr() == this, "Footnote mismatch error." ); + + while ( pFootnote ) + { + SwFootnoteFrame *pFoll = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pFoll; + } + + // #i20556# During hiding of a section, the connection + // to the layout is already lost. pPage may be 0: + if ( pPage ) + pPage->UpdateFootnoteNum(); + } +} + +/// Set the sequence number for the current footnote. +/// @returns The new sequence number or USHRT_MAX if invalid. +void SwTextFootnote::SetSeqRefNo() +{ + if( !m_pTextNode ) + return; + + SwDoc& rDoc = m_pTextNode->GetDoc(); + if( rDoc.IsInReading() ) + return; + + std::set<sal_uInt16> aUsedNums; + std::vector<SwTextFootnote*> badRefNums; + ::lcl_FillUsedFootnoteRefNumbers(rDoc, this, aUsedNums, badRefNums); + if ( ::lcl_IsRefNumAvailable(aUsedNums, m_nSeqNo) ) + return; + std::vector<sal_uInt16> unused; + ::lcl_FillUnusedSeqRefNums(unused, aUsedNums, 1); + m_nSeqNo = unused[0]; +} + +/// Set a unique sequential reference number for every footnote in the document. +/// @param[in] rDoc The document to be processed. +void SwTextFootnote::SetUniqueSeqRefNo( SwDoc& rDoc ) +{ + std::set<sal_uInt16> aUsedNums; + std::vector<SwTextFootnote*> badRefNums; + ::lcl_FillUsedFootnoteRefNumbers(rDoc, nullptr, aUsedNums, badRefNums); + std::vector<sal_uInt16> aUnused; + ::lcl_FillUnusedSeqRefNums(aUnused, aUsedNums, badRefNums.size()); + + for (size_t i = 0; i < badRefNums.size(); ++i) + { + badRefNums[i]->m_nSeqNo = aUnused[i]; + } +} + +void SwTextFootnote::CheckCondColl() +{ +//FEATURE::CONDCOLL + if( GetStartNode() ) + static_cast<SwStartNode&>(GetStartNode()->GetNode()).CheckSectionCondColl(); +//FEATURE::CONDCOLL +} + +void SwTextFootnote::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFootnote")); + SwTextAttr::dumpAsXml(pWriter); + + if (m_pStartNode) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pStartNode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(sal_Int32(m_pStartNode->GetIndex())).getStr())); + (void)xmlTextWriterEndElement(pWriter); + } + if (m_pTextNode) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pTextNode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(sal_Int32(m_pTextNode->GetIndex())).getStr())); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_nSeqNo")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::number(m_nSeqNo).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |