1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <libxml/xmlwriter.h>
23 #include <DocumentContentOperationsManager.hxx>
24 #include <IDocumentStylePoolAccess.hxx>
26 #include <rootfrm.hxx>
27 #include <pagefrm.hxx>
30 #include <ftninfo.hxx>
32 #include <poolfmt.hxx>
34 #include <ndindex.hxx>
35 #include <fmtftntx.hxx>
36 #include <section.hxx>
40 #include <rtl/ustrbuf.hxx>
41 #include <vcl/svapp.hxx>
42 #include <unotextrange.hxx>
43 #include <osl/diagnose.h>
44 #include <unofootnote.hxx>
47 /// Get a sorted list of the used footnote reference numbers.
48 /// @param[in] rDoc The active document.
49 /// @param[in] pExclude A footnote whose reference number should be excluded from the set.
50 /// @param[out] rUsedRef The set of used reference numbers.
51 /// @param[out] rInvalid A returned list of all items that had an invalid reference number.
52 void lcl_FillUsedFootnoteRefNumbers(SwDoc
&rDoc
,
53 SwTextFootnote
const *pExclude
,
54 std::set
<sal_uInt16
> &rUsedRef
,
55 std::vector
<SwTextFootnote
*> &rInvalid
)
57 SwFootnoteIdxs
& ftnIdxs
= rDoc
.GetFootnoteIdxs();
61 for( size_t n
= 0; n
< ftnIdxs
.size(); ++n
)
63 SwTextFootnote
* pTextFootnote
= ftnIdxs
[ n
];
64 if ( pTextFootnote
!= pExclude
)
66 if ( USHRT_MAX
== pTextFootnote
->GetSeqRefNo() )
68 rInvalid
.push_back(pTextFootnote
);
72 rUsedRef
.insert( pTextFootnote
->GetSeqRefNo() );
78 /// Check whether a requested reference number is available.
79 /// @param[in] rUsedNums Set of used reference numbers.
80 /// @param[in] requested The requested reference number.
81 /// @returns true if the number is available, false if not.
82 bool lcl_IsRefNumAvailable(std::set
<sal_uInt16
> const &rUsedNums
,
85 if ( USHRT_MAX
== requested
)
86 return false; // Invalid sequence number.
87 if ( rUsedNums
.count(requested
) )
88 return false; // Number already used.
92 /// Get the first few unused sequential reference numbers.
93 /// @param[out] rLowestUnusedNums The lowest unused sequential reference numbers.
94 /// @param[in] rUsedNums The set of used sequential reference numbers.
95 /// @param[in] numRequired The number of reference number required.
96 void lcl_FillUnusedSeqRefNums(std::vector
<sal_uInt16
> &rLowestUnusedNums
,
97 const std::set
<sal_uInt16
> &rUsedNums
,
103 rLowestUnusedNums
.reserve(numRequired
);
104 sal_uInt16 newNum
= 0;
105 //Start by using numbers from gaps in rUsedNums
106 for( const auto& rNum
: rUsedNums
)
108 while ( newNum
< rNum
)
110 rLowestUnusedNums
.push_back( newNum
++ );
111 if ( --numRequired
== 0)
116 //Filled in all gaps. Fill the rest of the list with new numbers.
119 rLowestUnusedNums
.push_back( newNum
++ );
121 while ( --numRequired
> 0 );
126 SwFormatFootnote::SwFormatFootnote( bool bEndNote
)
127 : SfxPoolItem( RES_TXTATR_FTN
)
128 , m_pTextAttr(nullptr)
130 , m_nNumberRLHidden(0)
131 , m_bEndNote(bEndNote
)
136 void SwFormatFootnote::SetXFootnote(rtl::Reference
<SwXFootnote
> const& xNote
)
137 { m_wXFootnote
= xNote
.get(); }
139 bool SwFormatFootnote::operator==( const SfxPoolItem
& rAttr
) const
141 assert(SfxPoolItem::operator==(rAttr
));
142 return m_nNumber
== static_cast<const SwFormatFootnote
&>(rAttr
).m_nNumber
&&
144 m_aNumber
== static_cast<const SwFormatFootnote
&>(rAttr
).m_aNumber
&&
145 m_bEndNote
== static_cast<const SwFormatFootnote
&>(rAttr
).m_bEndNote
;
148 SwFormatFootnote
* SwFormatFootnote::Clone( SfxItemPool
* ) const
150 SwFormatFootnote
* pNew
= new SwFormatFootnote
;
151 pNew
->m_aNumber
= m_aNumber
;
152 pNew
->m_nNumber
= m_nNumber
;
153 pNew
->m_nNumberRLHidden
= m_nNumberRLHidden
;
154 pNew
->m_bEndNote
= m_bEndNote
;
158 void SwFormatFootnote::InvalidateFootnote()
160 if (auto xUnoFootnote
= m_wXFootnote
.get())
162 xUnoFootnote
->OnFormatFootnoteDeleted();
163 m_wXFootnote
.clear();
167 void SwFormatFootnote::SetEndNote( bool b
)
169 if ( b
!= m_bEndNote
)
171 if ( GetTextFootnote() )
173 GetTextFootnote()->DelFrames(nullptr);
179 SwFormatFootnote::~SwFormatFootnote()
183 OUString
SwFormatFootnote::GetFootnoteText(SwRootFrame
const& rLayout
) const
186 if( m_pTextAttr
->GetStartNode() )
188 SwNodeIndex
aIdx( *m_pTextAttr
->GetStartNode(), 1 );
189 SwContentNode
* pCNd
= aIdx
.GetNode().GetTextNode();
191 pCNd
= SwNodes::GoNext(&aIdx
);
193 if( pCNd
->IsTextNode() ) {
194 buf
.append(static_cast<SwTextNode
*>(pCNd
)->GetExpandText(&rLayout
));
197 while ( !aIdx
.GetNode().IsEndNode() ) {
198 if ( aIdx
.GetNode().IsTextNode() )
200 buf
.append(" " + aIdx
.GetNode().GetTextNode()->GetExpandText(&rLayout
));
206 return buf
.makeStringAndClear();
209 /// return the view string of the foot/endnote
210 OUString
SwFormatFootnote::GetViewNumStr(const SwDoc
& rDoc
,
211 SwRootFrame
const*const pLayout
, bool bInclStrings
) const
213 OUString
sRet( GetNumStr() );
216 // in this case the number is needed, get it via SwDoc's FootnoteInfo
217 bool bMakeNum
= true;
218 const SwSectionNode
* pSectNd
= m_pTextAttr
219 ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr
)
221 sal_uInt16
const nNumber(pLayout
&& pLayout
->IsHideRedlines()
222 ? GetNumberRLHidden()
227 const SwFormatFootnoteEndAtTextEnd
& rFootnoteEnd
= static_cast<const SwFormatFootnoteEndAtTextEnd
&>(
228 pSectNd
->GetSection().GetFormat()->GetFormatAttr(
230 o3tl::narrowing
<sal_uInt16
>(RES_END_AT_TXTEND
) :
231 o3tl::narrowing
<sal_uInt16
>(RES_FTN_AT_TXTEND
) ) );
233 if( FTNEND_ATTXTEND_OWNNUMANDFMT
== rFootnoteEnd
.GetValue() )
236 sRet
= rFootnoteEnd
.GetSwNumType().GetNumStr( nNumber
);
239 sRet
= rFootnoteEnd
.GetPrefix() + sRet
+ rFootnoteEnd
.GetSuffix();
246 const SwEndNoteInfo
* pInfo
;
248 pInfo
= &rDoc
.GetEndNoteInfo();
250 pInfo
= &rDoc
.GetFootnoteInfo();
251 sRet
= pInfo
->m_aFormat
.GetNumStr( nNumber
);
254 sRet
= pInfo
->GetPrefix() + sRet
+ pInfo
->GetSuffix();
261 rtl::Reference
<SwXTextRange
> SwFormatFootnote::getAnchor(SwDoc
& rDoc
) const
263 SolarMutexGuard aGuard
;
266 SwPaM
aPam(m_pTextAttr
->GetTextNode(), m_pTextAttr
->GetStart());
268 aPam
.GetMark()->AdjustContent(+1);
269 rtl::Reference
<SwXTextRange
> xRet
=
270 SwXTextRange::CreateXTextRange(rDoc
, *aPam
.Start(), aPam
.End());
274 void SwFormatFootnote::dumpAsXml(xmlTextWriterPtr pWriter
) const
276 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwFormatFootnote"));
277 (void)xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("ptr"), "%p", this);
278 (void)xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("text-attr"), "%p", m_pTextAttr
);
279 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("endnote"),
280 BAD_CAST(OString::boolean(m_bEndNote
).getStr()));
282 SfxPoolItem::dumpAsXml(pWriter
);
284 (void)xmlTextWriterEndElement(pWriter
);
287 SwTextFootnote::SwTextFootnote(
288 const SfxPoolItemHolder
& rAttr
,
289 sal_Int32 nStartPos
)
290 : SwTextAttr( rAttr
, nStartPos
)
291 , m_pTextNode( nullptr )
292 , m_nSeqNo( USHRT_MAX
)
294 SwFormatFootnote
& rSwFormatFootnote(static_cast<SwFormatFootnote
&>(GetAttr()));
295 rSwFormatFootnote
.m_pTextAttr
= this;
296 SetHasDummyChar(true);
299 SwTextFootnote::~SwTextFootnote()
301 SetStartNode( nullptr );
304 void SwTextFootnote::SetStartNode( const SwNodeIndex
*pNewNode
, bool bDelNode
)
308 m_oStartNode
= *pNewNode
;
310 else if ( m_oStartNode
)
312 // need to do 2 things:
313 // 1) unregister footnotes at their pages
314 // 2) delete the footnote section in the Inserts of the nodes-array
318 pDoc
= &m_pTextNode
->GetDoc();
322 //JP 27.01.97: the sw3-Reader creates a StartNode but the
323 // attribute isn't anchored in the TextNode yet.
324 // If it is deleted (e.g. Insert File with footnote
325 // inside fly frame), the content must also be deleted.
326 pDoc
= &m_oStartNode
->GetNodes().GetDoc();
329 // If called from ~SwDoc(), must not delete the footnote nodes,
330 // and not necessary to delete the footnote frames.
331 if( !pDoc
->IsInDtor() )
335 // 2) delete the section for the footnote nodes
336 // it's possible that the Inserts have already been deleted (how???)
337 pDoc
->getIDocumentContentOperations().DeleteSection( &m_oStartNode
->GetNode() );
340 // If the nodes are not deleted, their frames must be removed
341 // from the page (deleted), there is nothing else that deletes
342 // them (particularly not Undo)
343 DelFrames( nullptr );
345 m_oStartNode
.reset();
347 // remove the footnote from the SwDoc's array
348 for( size_t n
= 0; n
< pDoc
->GetFootnoteIdxs().size(); ++n
)
349 if( this == pDoc
->GetFootnoteIdxs()[n
] )
351 pDoc
->GetFootnoteIdxs().erase( pDoc
->GetFootnoteIdxs().begin() + n
);
352 // if necessary, update following footnotes
353 if( !pDoc
->IsInDtor() && n
< pDoc
->GetFootnoteIdxs().size() )
355 pDoc
->GetFootnoteIdxs().UpdateFootnote( pDoc
->GetFootnoteIdxs()[n
]->GetTextNode() );
362 void SwTextFootnote::SetNumber(const sal_uInt16 nNewNum
,
363 sal_uInt16
const nNumberRLHidden
, const OUString
&sNumStr
)
365 SwFormatFootnote
& rFootnote
= const_cast<SwFormatFootnote
&>(GetFootnote());
367 rFootnote
.m_aNumber
= sNumStr
;
368 if ( sNumStr
.isEmpty() )
370 rFootnote
.m_nNumber
= nNewNum
;
371 rFootnote
.m_nNumberRLHidden
= nNumberRLHidden
;
373 InvalidateNumberInLayout();
376 void SwTextFootnote::InvalidateNumberInLayout()
379 SwNodes
&rNodes
= m_pTextNode
->GetDoc().GetNodes();
380 const sw::LegacyModifyHint
aHint(nullptr, &GetFootnote());
381 m_pTextNode
->TriggerNodeUpdate(aHint
);
384 // must iterate over all TextNodes because of footnotes on other pages
385 SwNodeOffset nSttIdx
= m_oStartNode
->GetIndex() + 1;
386 SwNodeOffset nEndIdx
= m_oStartNode
->GetNode().EndOfSectionIndex();
387 for( ; nSttIdx
< nEndIdx
; ++nSttIdx
)
390 if( ( pNd
= rNodes
[ nSttIdx
] )->IsTextNode() )
391 static_cast<SwTextNode
*>(pNd
)->TriggerNodeUpdate(aHint
);
396 void SwTextFootnote::CopyFootnote(
397 SwTextFootnote
& rDest
,
398 SwTextNode
& rDestNode
) const
400 if (m_oStartNode
&& !rDest
.GetStartNode())
402 // dest missing node section? create it here!
403 // (happens in SwTextNode::CopyText if pDest == this)
404 rDest
.MakeNewTextSection( rDestNode
.GetNodes() );
406 if (m_oStartNode
&& rDest
.GetStartNode())
408 // footnotes not necessarily in same document!
409 SwDoc
& rDstDoc
= rDestNode
.GetDoc();
410 SwNodes
&rDstNodes
= rDstDoc
.GetNodes();
412 // copy only the content of the section
413 SwNodeRange
aRg( m_oStartNode
->GetNode(), SwNodeOffset(1),
414 *m_oStartNode
->GetNode().EndOfSectionNode() );
416 // insert at the end of rDest, i.e., the nodes are appended.
417 // nDestLen contains number of ContentNodes in rDest _before_ copy.
418 SwNodeIndex
aStart( *(rDest
.GetStartNode()) );
419 SwNodeIndex
aEnd( *aStart
.GetNode().EndOfSectionNode() );
420 SwNodeOffset nDestLen
= aEnd
.GetIndex() - aStart
.GetIndex() - 1;
422 m_pTextNode
->GetDoc().GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg
, aEnd
.GetNode());
424 // in case the destination section was not empty, delete the old nodes
425 // before: Src: SxxxE, Dst: SnE
426 // now: Src: SxxxE, Dst: SnxxxE
427 // after: Src: SxxxE, Dst: SxxxE
429 rDstNodes
.Delete( aStart
, nDestLen
);
432 // also copy user defined number string
433 if( !GetFootnote().m_aNumber
.isEmpty() )
435 const_cast<SwFormatFootnote
&>(rDest
.GetFootnote()).m_aNumber
= GetFootnote().m_aNumber
;
439 /// create a new nodes-array section for the footnote
440 void SwTextFootnote::MakeNewTextSection( SwNodes
& rNodes
)
445 // set the footnote style on the SwTextNode
446 SwTextFormatColl
*pFormatColl
;
447 const SwEndNoteInfo
* pInfo
;
450 if( GetFootnote().IsEndNote() )
452 pInfo
= &rNodes
.GetDoc().GetEndNoteInfo();
453 nPoolId
= RES_POOLCOLL_ENDNOTE
;
457 pInfo
= &rNodes
.GetDoc().GetFootnoteInfo();
458 nPoolId
= RES_POOLCOLL_FOOTNOTE
;
461 pFormatColl
= pInfo
->GetFootnoteTextColl();
462 if( nullptr == pFormatColl
)
463 pFormatColl
= rNodes
.GetDoc().getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId
);
465 SwStartNode
* pSttNd
= rNodes
.MakeTextSection( rNodes
.GetEndOfInserts(),
466 SwFootnoteStartNode
, pFormatColl
);
467 m_oStartNode
= *pSttNd
;
470 void SwTextFootnote::DelFrames(SwRootFrame
const*const pRoot
)
472 // delete the FootnoteFrames from the pages
473 OSL_ENSURE( m_pTextNode
, "SwTextFootnote: where is my TextNode?" );
477 bool bFrameFnd
= false;
479 SwIterator
<SwContentFrame
, SwTextNode
, sw::IteratorMode::UnwrapMulti
> aIter(*m_pTextNode
);
480 for( SwContentFrame
* pFnd
= aIter
.First(); pFnd
; pFnd
= aIter
.Next() )
482 if( pRoot
!= pFnd
->getRootFrame() && pRoot
)
484 SwPageFrame
* pPage
= pFnd
->FindPageFrame();
487 // note: we have found the correct frame only if the footnote
488 // was actually removed; in case this is called from
489 // SwTextFrame::DestroyImpl(), then that frame isn't connected
490 // to SwPageFrame any more, and RemoveFootnote on any follow
491 // must not prevent the fall-back to the !bFrameFnd code.
492 bFrameFnd
= pPage
->RemoveFootnote(pFnd
, this);
496 //JP 13.05.97: if the layout is deleted before the footnotes are deleted,
497 // try to delete the footnote's frames by another way
498 if ( bFrameFnd
|| !m_oStartNode
)
501 SwNodeIndex
aIdx( *m_oStartNode
);
502 SwContentNode
* pCNd
= SwNodes::GoNext(&aIdx
);
506 SwIterator
<SwContentFrame
, SwContentNode
, sw::IteratorMode::UnwrapMulti
> aIter(*pCNd
);
507 for( SwContentFrame
* pFnd
= aIter
.First(); pFnd
; pFnd
= aIter
.Next() )
509 if( pRoot
!= pFnd
->getRootFrame() && pRoot
)
511 SwPageFrame
* pPage
= pFnd
->FindPageFrame();
513 SwFrame
*pFrame
= pFnd
->GetUpper();
514 while ( pFrame
&& !pFrame
->IsFootnoteFrame() )
515 pFrame
= pFrame
->GetUpper();
517 SwFootnoteFrame
*pFootnote
= static_cast<SwFootnoteFrame
*>(pFrame
);
518 while ( pFootnote
&& pFootnote
->GetMaster() )
519 pFootnote
= pFootnote
->GetMaster();
520 OSL_ENSURE( pFootnote
->GetAttr() == this, "Footnote mismatch error." );
524 SwFootnoteFrame
*pFoll
= pFootnote
->GetFollow();
526 SwFrame::DestroyFrame(pFootnote
);
530 // #i20556# During hiding of a section, the connection
531 // to the layout is already lost. pPage may be 0:
533 pPage
->UpdateFootnoteNum();
537 /// Set the sequence number for the current footnote.
538 /// @returns The new sequence number or USHRT_MAX if invalid.
539 void SwTextFootnote::SetSeqRefNo()
544 SwDoc
& rDoc
= m_pTextNode
->GetDoc();
545 if( rDoc
.IsInReading() )
548 std::set
<sal_uInt16
> aUsedNums
;
549 std::vector
<SwTextFootnote
*> badRefNums
;
550 ::lcl_FillUsedFootnoteRefNumbers(rDoc
, this, aUsedNums
, badRefNums
);
551 if ( ::lcl_IsRefNumAvailable(aUsedNums
, m_nSeqNo
) )
553 std::vector
<sal_uInt16
> unused
;
554 ::lcl_FillUnusedSeqRefNums(unused
, aUsedNums
, 1);
555 m_nSeqNo
= unused
[0];
558 /// Set a unique sequential reference number for every footnote in the document.
559 /// @param[in] rDoc The document to be processed.
560 void SwTextFootnote::SetUniqueSeqRefNo( SwDoc
& rDoc
)
562 std::set
<sal_uInt16
> aUsedNums
;
563 std::vector
<SwTextFootnote
*> badRefNums
;
564 ::lcl_FillUsedFootnoteRefNumbers(rDoc
, nullptr, aUsedNums
, badRefNums
);
565 std::vector
<sal_uInt16
> aUnused
;
566 ::lcl_FillUnusedSeqRefNums(aUnused
, aUsedNums
, badRefNums
.size());
568 for (size_t i
= 0; i
< badRefNums
.size(); ++i
)
570 badRefNums
[i
]->m_nSeqNo
= aUnused
[i
];
574 void SwTextFootnote::CheckCondColl()
577 static_cast<SwStartNode
&>(GetStartNode()->GetNode()).CheckSectionCondColl();
580 void SwTextFootnote::dumpAsXml(xmlTextWriterPtr pWriter
) const
582 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwTextFootnote"));
583 SwTextAttr::dumpAsXml(pWriter
);
587 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_oStartNode"));
588 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("index"),
589 BAD_CAST(OString::number(sal_Int32(m_oStartNode
->GetIndex())).getStr()));
590 (void)xmlTextWriterEndElement(pWriter
);
594 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_pTextNode"));
595 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("index"),
596 BAD_CAST(OString::number(sal_Int32(m_pTextNode
->GetIndex())).getStr()));
597 (void)xmlTextWriterEndElement(pWriter
);
599 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_nSeqNo"));
600 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("value"),
601 BAD_CAST(OString::number(m_nSeqNo
).getStr()));
602 (void)xmlTextWriterEndElement(pWriter
);
604 (void)xmlTextWriterEndElement(pWriter
);
607 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */