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 , sw::BroadcastingModify()
129 , m_pTextAttr(nullptr)
131 , m_nNumberRLHidden(0)
132 , 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::SwClientNotify(const SwModify
&, const SfxHint
& rHint
)
160 if (rHint
.GetId() != SfxHintId::SwLegacyModify
)
162 auto pLegacy
= static_cast<const sw::LegacyModifyHint
*>(&rHint
);
163 CallSwClientNotify(rHint
);
164 if(RES_REMOVE_UNO_OBJECT
== pLegacy
->GetWhich())
165 SetXFootnote(nullptr);
168 void SwFormatFootnote::InvalidateFootnote()
170 SwPtrMsgPoolItem
const item(RES_REMOVE_UNO_OBJECT
,
171 &static_cast<sw::BroadcastingModify
&>(*this)); // cast to base class (void*)
172 CallSwClientNotify(sw::LegacyModifyHint(&item
, &item
));
175 void SwFormatFootnote::SetEndNote( bool b
)
177 if ( b
!= m_bEndNote
)
179 if ( GetTextFootnote() )
181 GetTextFootnote()->DelFrames(nullptr);
187 SwFormatFootnote::~SwFormatFootnote()
191 OUString
SwFormatFootnote::GetFootnoteText(SwRootFrame
const& rLayout
) const
194 if( m_pTextAttr
->GetStartNode() )
196 SwNodeIndex
aIdx( *m_pTextAttr
->GetStartNode(), 1 );
197 SwContentNode
* pCNd
= aIdx
.GetNode().GetTextNode();
199 pCNd
= aIdx
.GetNodes().GoNext( &aIdx
);
201 if( pCNd
->IsTextNode() ) {
202 buf
.append(static_cast<SwTextNode
*>(pCNd
)->GetExpandText(&rLayout
));
205 while ( !aIdx
.GetNode().IsEndNode() ) {
206 if ( aIdx
.GetNode().IsTextNode() )
208 buf
.append(" " + aIdx
.GetNode().GetTextNode()->GetExpandText(&rLayout
));
214 return buf
.makeStringAndClear();
217 /// return the view string of the foot/endnote
218 OUString
SwFormatFootnote::GetViewNumStr(const SwDoc
& rDoc
,
219 SwRootFrame
const*const pLayout
, bool bInclStrings
) const
221 OUString
sRet( GetNumStr() );
224 // in this case the number is needed, get it via SwDoc's FootnoteInfo
225 bool bMakeNum
= true;
226 const SwSectionNode
* pSectNd
= m_pTextAttr
227 ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr
)
229 sal_uInt16
const nNumber(pLayout
&& pLayout
->IsHideRedlines()
230 ? GetNumberRLHidden()
235 const SwFormatFootnoteEndAtTextEnd
& rFootnoteEnd
= static_cast<const SwFormatFootnoteEndAtTextEnd
&>(
236 pSectNd
->GetSection().GetFormat()->GetFormatAttr(
238 o3tl::narrowing
<sal_uInt16
>(RES_END_AT_TXTEND
) :
239 o3tl::narrowing
<sal_uInt16
>(RES_FTN_AT_TXTEND
) ) );
241 if( FTNEND_ATTXTEND_OWNNUMANDFMT
== rFootnoteEnd
.GetValue() )
244 sRet
= rFootnoteEnd
.GetSwNumType().GetNumStr( nNumber
);
247 sRet
= rFootnoteEnd
.GetPrefix() + sRet
+ rFootnoteEnd
.GetSuffix();
254 const SwEndNoteInfo
* pInfo
;
256 pInfo
= &rDoc
.GetEndNoteInfo();
258 pInfo
= &rDoc
.GetFootnoteInfo();
259 sRet
= pInfo
->m_aFormat
.GetNumStr( nNumber
);
262 sRet
= pInfo
->GetPrefix() + sRet
+ pInfo
->GetSuffix();
269 uno::Reference
<text::XTextRange
> SwFormatFootnote::getAnchor(SwDoc
& rDoc
) const
271 SolarMutexGuard aGuard
;
273 return uno::Reference
<text::XTextRange
>();
274 SwPaM
aPam(m_pTextAttr
->GetTextNode(), m_pTextAttr
->GetStart());
276 aPam
.GetMark()->AdjustContent(+1);
277 const rtl::Reference
<SwXTextRange
> xRet
=
278 SwXTextRange::CreateXTextRange(rDoc
, *aPam
.Start(), aPam
.End());
282 void SwFormatFootnote::dumpAsXml(xmlTextWriterPtr pWriter
) const
284 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwFormatFootnote"));
285 (void)xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("ptr"), "%p", this);
286 (void)xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("text-attr"), "%p", m_pTextAttr
);
287 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("endnote"),
288 BAD_CAST(OString::boolean(m_bEndNote
).getStr()));
290 SfxPoolItem::dumpAsXml(pWriter
);
292 (void)xmlTextWriterEndElement(pWriter
);
295 SwTextFootnote::SwTextFootnote( SwFormatFootnote
& rAttr
, sal_Int32 nStartPos
)
296 : SwTextAttr( rAttr
, nStartPos
)
297 , m_pTextNode( nullptr )
298 , m_nSeqNo( USHRT_MAX
)
300 rAttr
.m_pTextAttr
= this;
301 SetHasDummyChar(true);
304 SwTextFootnote::~SwTextFootnote()
306 SetStartNode( nullptr );
309 void SwTextFootnote::SetStartNode( const SwNodeIndex
*pNewNode
, bool bDelNode
)
313 m_oStartNode
= *pNewNode
;
315 else if ( m_oStartNode
)
317 // need to do 2 things:
318 // 1) unregister footnotes at their pages
319 // 2) delete the footnote section in the Inserts of the nodes-array
323 pDoc
= &m_pTextNode
->GetDoc();
327 //JP 27.01.97: the sw3-Reader creates a StartNode but the
328 // attribute isn't anchored in the TextNode yet.
329 // If it is deleted (e.g. Insert File with footnote
330 // inside fly frame), the content must also be deleted.
331 pDoc
= &m_oStartNode
->GetNodes().GetDoc();
334 // If called from ~SwDoc(), must not delete the footnote nodes,
335 // and not necessary to delete the footnote frames.
336 if( !pDoc
->IsInDtor() )
340 // 2) delete the section for the footnote nodes
341 // it's possible that the Inserts have already been deleted (how???)
342 pDoc
->getIDocumentContentOperations().DeleteSection( &m_oStartNode
->GetNode() );
345 // If the nodes are not deleted, their frames must be removed
346 // from the page (deleted), there is nothing else that deletes
347 // them (particularly not Undo)
348 DelFrames( nullptr );
350 m_oStartNode
.reset();
352 // remove the footnote from the SwDoc's array
353 for( size_t n
= 0; n
< pDoc
->GetFootnoteIdxs().size(); ++n
)
354 if( this == pDoc
->GetFootnoteIdxs()[n
] )
356 pDoc
->GetFootnoteIdxs().erase( pDoc
->GetFootnoteIdxs().begin() + n
);
357 // if necessary, update following footnotes
358 if( !pDoc
->IsInDtor() && n
< pDoc
->GetFootnoteIdxs().size() )
360 pDoc
->GetFootnoteIdxs().UpdateFootnote( pDoc
->GetFootnoteIdxs()[n
]->GetTextNode() );
367 void SwTextFootnote::SetNumber(const sal_uInt16 nNewNum
,
368 sal_uInt16
const nNumberRLHidden
, const OUString
&sNumStr
)
370 SwFormatFootnote
& rFootnote
= const_cast<SwFormatFootnote
&>(GetFootnote());
372 rFootnote
.m_aNumber
= sNumStr
;
373 if ( sNumStr
.isEmpty() )
375 rFootnote
.m_nNumber
= nNewNum
;
376 rFootnote
.m_nNumberRLHidden
= nNumberRLHidden
;
378 InvalidateNumberInLayout();
381 void SwTextFootnote::InvalidateNumberInLayout()
384 SwNodes
&rNodes
= m_pTextNode
->GetDoc().GetNodes();
385 const sw::LegacyModifyHint
aHint(nullptr, &GetFootnote());
386 m_pTextNode
->TriggerNodeUpdate(aHint
);
389 // must iterate over all TextNodes because of footnotes on other pages
390 SwNodeOffset nSttIdx
= m_oStartNode
->GetIndex() + 1;
391 SwNodeOffset nEndIdx
= m_oStartNode
->GetNode().EndOfSectionIndex();
392 for( ; nSttIdx
< nEndIdx
; ++nSttIdx
)
395 if( ( pNd
= rNodes
[ nSttIdx
] )->IsTextNode() )
396 static_cast<SwTextNode
*>(pNd
)->TriggerNodeUpdate(aHint
);
401 void SwTextFootnote::CopyFootnote(
402 SwTextFootnote
& rDest
,
403 SwTextNode
& rDestNode
) const
405 if (m_oStartNode
&& !rDest
.GetStartNode())
407 // dest missing node section? create it here!
408 // (happens in SwTextNode::CopyText if pDest == this)
409 rDest
.MakeNewTextSection( rDestNode
.GetNodes() );
411 if (m_oStartNode
&& rDest
.GetStartNode())
413 // footnotes not necessarily in same document!
414 SwDoc
& rDstDoc
= rDestNode
.GetDoc();
415 SwNodes
&rDstNodes
= rDstDoc
.GetNodes();
417 // copy only the content of the section
418 SwNodeRange
aRg( m_oStartNode
->GetNode(), SwNodeOffset(1),
419 *m_oStartNode
->GetNode().EndOfSectionNode() );
421 // insert at the end of rDest, i.e., the nodes are appended.
422 // nDestLen contains number of ContentNodes in rDest _before_ copy.
423 SwNodeIndex
aStart( *(rDest
.GetStartNode()) );
424 SwNodeIndex
aEnd( *aStart
.GetNode().EndOfSectionNode() );
425 SwNodeOffset nDestLen
= aEnd
.GetIndex() - aStart
.GetIndex() - 1;
427 m_pTextNode
->GetDoc().GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg
, aEnd
.GetNode());
429 // in case the destination section was not empty, delete the old nodes
430 // before: Src: SxxxE, Dst: SnE
431 // now: Src: SxxxE, Dst: SnxxxE
432 // after: Src: SxxxE, Dst: SxxxE
434 rDstNodes
.Delete( aStart
, nDestLen
);
437 // also copy user defined number string
438 if( !GetFootnote().m_aNumber
.isEmpty() )
440 const_cast<SwFormatFootnote
&>(rDest
.GetFootnote()).m_aNumber
= GetFootnote().m_aNumber
;
444 /// create a new nodes-array section for the footnote
445 void SwTextFootnote::MakeNewTextSection( SwNodes
& rNodes
)
450 // set the footnote style on the SwTextNode
451 SwTextFormatColl
*pFormatColl
;
452 const SwEndNoteInfo
* pInfo
;
455 if( GetFootnote().IsEndNote() )
457 pInfo
= &rNodes
.GetDoc().GetEndNoteInfo();
458 nPoolId
= RES_POOLCOLL_ENDNOTE
;
462 pInfo
= &rNodes
.GetDoc().GetFootnoteInfo();
463 nPoolId
= RES_POOLCOLL_FOOTNOTE
;
466 pFormatColl
= pInfo
->GetFootnoteTextColl();
467 if( nullptr == pFormatColl
)
468 pFormatColl
= rNodes
.GetDoc().getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId
);
470 SwStartNode
* pSttNd
= rNodes
.MakeTextSection( rNodes
.GetEndOfInserts(),
471 SwFootnoteStartNode
, pFormatColl
);
472 m_oStartNode
= *pSttNd
;
475 void SwTextFootnote::DelFrames(SwRootFrame
const*const pRoot
)
477 // delete the FootnoteFrames from the pages
478 OSL_ENSURE( m_pTextNode
, "SwTextFootnote: where is my TextNode?" );
482 bool bFrameFnd
= false;
484 SwIterator
<SwContentFrame
, SwTextNode
, sw::IteratorMode::UnwrapMulti
> aIter(*m_pTextNode
);
485 for( SwContentFrame
* pFnd
= aIter
.First(); pFnd
; pFnd
= aIter
.Next() )
487 if( pRoot
!= pFnd
->getRootFrame() && pRoot
)
489 SwPageFrame
* pPage
= pFnd
->FindPageFrame();
492 // note: we have found the correct frame only if the footnote
493 // was actually removed; in case this is called from
494 // SwTextFrame::DestroyImpl(), then that frame isn't connected
495 // to SwPageFrame any more, and RemoveFootnote on any follow
496 // must not prevent the fall-back to the !bFrameFnd code.
497 bFrameFnd
= pPage
->RemoveFootnote(pFnd
, this);
501 //JP 13.05.97: if the layout is deleted before the footnotes are deleted,
502 // try to delete the footnote's frames by another way
503 if ( bFrameFnd
|| !m_oStartNode
)
506 SwNodeIndex
aIdx( *m_oStartNode
);
507 SwContentNode
* pCNd
= m_pTextNode
->GetNodes().GoNext( &aIdx
);
511 SwIterator
<SwContentFrame
, SwContentNode
, sw::IteratorMode::UnwrapMulti
> aIter(*pCNd
);
512 for( SwContentFrame
* pFnd
= aIter
.First(); pFnd
; pFnd
= aIter
.Next() )
514 if( pRoot
!= pFnd
->getRootFrame() && pRoot
)
516 SwPageFrame
* pPage
= pFnd
->FindPageFrame();
518 SwFrame
*pFrame
= pFnd
->GetUpper();
519 while ( pFrame
&& !pFrame
->IsFootnoteFrame() )
520 pFrame
= pFrame
->GetUpper();
522 SwFootnoteFrame
*pFootnote
= static_cast<SwFootnoteFrame
*>(pFrame
);
523 while ( pFootnote
&& pFootnote
->GetMaster() )
524 pFootnote
= pFootnote
->GetMaster();
525 OSL_ENSURE( pFootnote
->GetAttr() == this, "Footnote mismatch error." );
529 SwFootnoteFrame
*pFoll
= pFootnote
->GetFollow();
531 SwFrame::DestroyFrame(pFootnote
);
535 // #i20556# During hiding of a section, the connection
536 // to the layout is already lost. pPage may be 0:
538 pPage
->UpdateFootnoteNum();
542 /// Set the sequence number for the current footnote.
543 /// @returns The new sequence number or USHRT_MAX if invalid.
544 void SwTextFootnote::SetSeqRefNo()
549 SwDoc
& rDoc
= m_pTextNode
->GetDoc();
550 if( rDoc
.IsInReading() )
553 std::set
<sal_uInt16
> aUsedNums
;
554 std::vector
<SwTextFootnote
*> badRefNums
;
555 ::lcl_FillUsedFootnoteRefNumbers(rDoc
, this, aUsedNums
, badRefNums
);
556 if ( ::lcl_IsRefNumAvailable(aUsedNums
, m_nSeqNo
) )
558 std::vector
<sal_uInt16
> unused
;
559 ::lcl_FillUnusedSeqRefNums(unused
, aUsedNums
, 1);
560 m_nSeqNo
= unused
[0];
563 /// Set a unique sequential reference number for every footnote in the document.
564 /// @param[in] rDoc The document to be processed.
565 void SwTextFootnote::SetUniqueSeqRefNo( SwDoc
& rDoc
)
567 std::set
<sal_uInt16
> aUsedNums
;
568 std::vector
<SwTextFootnote
*> badRefNums
;
569 ::lcl_FillUsedFootnoteRefNumbers(rDoc
, nullptr, aUsedNums
, badRefNums
);
570 std::vector
<sal_uInt16
> aUnused
;
571 ::lcl_FillUnusedSeqRefNums(aUnused
, aUsedNums
, badRefNums
.size());
573 for (size_t i
= 0; i
< badRefNums
.size(); ++i
)
575 badRefNums
[i
]->m_nSeqNo
= aUnused
[i
];
579 void SwTextFootnote::CheckCondColl()
582 static_cast<SwStartNode
&>(GetStartNode()->GetNode()).CheckSectionCondColl();
585 void SwTextFootnote::dumpAsXml(xmlTextWriterPtr pWriter
) const
587 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwTextFootnote"));
588 SwTextAttr::dumpAsXml(pWriter
);
592 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_oStartNode"));
593 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("index"),
594 BAD_CAST(OString::number(sal_Int32(m_oStartNode
->GetIndex())).getStr()));
595 (void)xmlTextWriterEndElement(pWriter
);
599 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_pTextNode"));
600 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("index"),
601 BAD_CAST(OString::number(sal_Int32(m_pTextNode
->GetIndex())).getStr()));
602 (void)xmlTextWriterEndElement(pWriter
);
604 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_nSeqNo"));
605 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("value"),
606 BAD_CAST(OString::number(m_nSeqNo
).getStr()));
607 (void)xmlTextWriterEndElement(pWriter
);
609 (void)xmlTextWriterEndElement(pWriter
);
612 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */