1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <memory>
21 #include <sal/config.h>
22 #include <sal/log.hxx>
23 #include <osl/diagnose.h>
24
25 #include <cstddef>
26
27 #include <hintids.hxx>
28 #include <hints.hxx>
29
30 #include <svl/cintitem.hxx>
31 #include <svl/stritem.hxx>
32 #include <fmtanchr.hxx>
33 #include <fmtfld.hxx>
34 #include <redline.hxx>
35 #include <pam.hxx>
36 #include <doc.hxx>
37 #include <IDocumentFieldsAccess.hxx>
38 #include <IDocumentRedlineAccess.hxx>
39 #include <IDocumentLayoutAccess.hxx>
40 #include <IDocumentMarkAccess.hxx>
41 #include <ndtxt.hxx>
42 #include <fldbas.hxx>
43 #include <docufld.hxx>
44 #include <txtfld.hxx>
45 #include <tox.hxx>
46 #include <expfld.hxx>
47 #include <bookmrk.hxx>
48 #include <fltshell.hxx>
49 #include <rdfhelper.hxx>
50
51 using namespace com::sun::star;
52
GetContentNode(SwDoc & rDoc,SwNodeIndex & rIdx,bool bNext)53 static SwContentNode* GetContentNode(SwDoc& rDoc, SwNodeIndex& rIdx, bool bNext)
54 {
55 SwContentNode * pCNd = rIdx.GetNode().GetContentNode();
56 if(!pCNd && nullptr == (pCNd = bNext ? rDoc.GetNodes().GoNext(&rIdx)
57 : SwNodes::GoPrevious(&rIdx)))
58 {
59 pCNd = bNext ? SwNodes::GoPrevious(&rIdx)
60 : rDoc.GetNodes().GoNext(&rIdx);
61 OSL_ENSURE(pCNd, "no ContentNode found");
62 }
63 return pCNd;
64 }
65
lcl_getTypePath(OUString & rType)66 static OUString lcl_getTypePath(OUString& rType)
67 {
68 OUString aRet;
69 if (rType.startsWith("urn:bails"))
70 {
71 rType = "urn:bails";
72 aRet = "tscp/bails.rdf";
73 }
74 return aRet;
75 }
76
77 // Stack entry for all text attributes
SwFltStackEntry(const SwPosition & rStartPos,std::unique_ptr<SfxPoolItem> pHt)78 SwFltStackEntry::SwFltStackEntry(const SwPosition& rStartPos, std::unique_ptr<SfxPoolItem> pHt)
79 : m_aMkPos(rStartPos)
80 , m_aPtPos(rStartPos)
81 , m_pAttr( std::move(pHt) )
82 , m_isAnnotationOnEnd(false)
83 , mnStartCP(-1)
84 , mnEndCP(-1)
85 , m_bIsParaEnd(false)
86 {
87 m_bOld = false; // used for marking Attributes *before* skipping field results
88 m_bOpen = true; // lock the attribute --> may first
89 m_bConsumedByField = false;
90 }
91
~SwFltStackEntry()92 SwFltStackEntry::~SwFltStackEntry()
93 {
94 // Although attribute got passed as pointer, it gets deleted here
95 }
96
SetEndPos(const SwPosition & rEndPos)97 void SwFltStackEntry::SetEndPos(const SwPosition& rEndPos)
98 {
99 // Release attribute and keep track of end
100 // Everything with sal_uInt16s, lest the inserting of new text at
101 // the cursor position moves the attribute's range
102 // That's not the desired behavior!
103 m_bOpen = false; // release and remember END
104 m_aPtPos.FromSwPosition(rEndPos);
105 }
106
MakeRegion(SwDoc & rDoc,SwPaM & rRegion,RegionMode const eCheck,const SwFltPosition & rMkPos,const SwFltPosition & rPtPos,bool bIsParaEnd,sal_uInt16 nWhich)107 bool SwFltStackEntry::MakeRegion(SwDoc& rDoc, SwPaM& rRegion, RegionMode const eCheck,
108 const SwFltPosition &rMkPos, const SwFltPosition &rPtPos, bool bIsParaEnd,
109 sal_uInt16 nWhich)
110 {
111 // does this range actually contain something?
112 // empty range is allowed if at start of empty paragraph
113 // fields are special: never have range, so leave them
114
115 // The only position of 0x0D will not be able to make region in the old logic
116 // because it is beyond the length of para...need special consideration here.
117 sal_uLong nMk = rMkPos.m_nNode.GetIndex() + 1;
118 const SwNodes& rMkNodes = rMkPos.m_nNode.GetNodes();
119 if (nMk >= rMkNodes.Count())
120 return false;
121 SwContentNode *const pContentNode(rMkNodes[nMk]->GetContentNode());
122 if (rMkPos == rPtPos &&
123 ((0 != rPtPos.m_nContent) || (pContentNode && (0 != pContentNode->Len())))
124 && ( RES_TXTATR_FIELD != nWhich
125 && RES_TXTATR_ANNOTATION != nWhich
126 && RES_TXTATR_INPUTFIELD != nWhich )
127 && !(bIsParaEnd && pContentNode && pContentNode->IsTextNode() && 0 != pContentNode->Len() ))
128 {
129 return false;
130 }
131 // The content indices always apply to the node!
132 rRegion.GetPoint()->nNode = rMkPos.m_nNode.GetIndex() + 1;
133 SwContentNode* pCNd = GetContentNode(rDoc, rRegion.GetPoint()->nNode, true);
134
135 SAL_WARN_IF(pCNd->Len() < rMkPos.m_nContent, "sw.ww8",
136 "invalid content index " << rMkPos.m_nContent << " but text node has only " << pCNd->Len());
137 rRegion.GetPoint()->nContent.Assign(pCNd,
138 std::min<sal_Int32>(rMkPos.m_nContent, pCNd->Len()));
139 rRegion.SetMark();
140 if (rMkPos.m_nNode != rPtPos.m_nNode)
141 {
142 sal_uLong n = rPtPos.m_nNode.GetIndex() + 1;
143 SwNodes& rNodes = rRegion.GetPoint()->nNode.GetNodes();
144 if (n >= rNodes.Count())
145 return false;
146 rRegion.GetPoint()->nNode = n;
147 pCNd = GetContentNode(rDoc, rRegion.GetPoint()->nNode, false);
148 }
149 SAL_WARN_IF(pCNd->Len() < rPtPos.m_nContent, "sw.ww8",
150 "invalid content index " << rPtPos.m_nContent << " but text node has only " << pCNd->Len());
151 rRegion.GetPoint()->nContent.Assign(pCNd,
152 std::min<sal_Int32>(rPtPos.m_nContent, pCNd->Len()));
153 OSL_ENSURE( CheckNodesRange( rRegion.Start()->nNode,
154 rRegion.End()->nNode, true ),
155 "attribute or similar crosses section-boundaries" );
156 bool bRet = true;
157 if (eCheck & RegionMode::CheckNodes)
158 {
159 bRet &= CheckNodesRange(rRegion.Start()->nNode,
160 rRegion.End()->nNode, true);
161 }
162 if (eCheck & RegionMode::CheckFieldmark)
163 {
164 bRet &= !sw::mark::IsFieldmarkOverlap(rRegion);
165 }
166 return bRet;
167 }
168
MakeRegion(SwDoc & rDoc,SwPaM & rRegion,RegionMode eCheck) const169 bool SwFltStackEntry::MakeRegion(SwDoc& rDoc, SwPaM& rRegion, RegionMode eCheck) const
170 {
171 return MakeRegion(rDoc, rRegion, eCheck, m_aMkPos, m_aPtPos, m_bIsParaEnd,
172 m_pAttr->Which());
173 }
174
SwFltControlStack(SwDoc & rDo,sal_uLong nFieldFl)175 SwFltControlStack::SwFltControlStack(SwDoc& rDo, sal_uLong nFieldFl)
176 : m_nFieldFlags(nFieldFl),m_bHasSdOD(true), m_bSdODChecked(false), m_rDoc(rDo), m_bIsEndStack(false)
177 {
178 }
179
~SwFltControlStack()180 SwFltControlStack::~SwFltControlStack()
181 {
182 OSL_ENSURE(m_Entries.empty(), "There are still Attributes on the stack");
183 }
184
185 // MoveAttrs() is meant to address the following problem:
186 // When a field like "set variable" is set through the stack, the text
187 // is shifted by one \xff character, which makes all subsequent
188 // attribute positions invalid.
189 // After setting the attribute in the doc, MoveAttrs() needs to be
190 // called in order to push all attribute positions to the right in the
191 // same paragraph further out by one character.
MoveAttrs(const SwPosition & rPos,MoveAttrsMode eMode)192 void SwFltControlStack::MoveAttrs(const SwPosition& rPos, MoveAttrsMode eMode)
193 {
194 sal_uLong nPosNd = rPos.nNode.GetIndex();
195 sal_uInt16 nPosCt = rPos.nContent.GetIndex() - 1;
196
197 for (size_t i = 0, nCnt = m_Entries.size(); i < nCnt; ++i)
198 {
199 SwFltStackEntry& rEntry = *m_Entries[i];
200 if (
201 (rEntry.m_aMkPos.m_nNode.GetIndex()+1 == nPosNd) &&
202 (rEntry.m_aMkPos.m_nContent >= nPosCt)
203 )
204 {
205 rEntry.m_aMkPos.m_nContent++;
206 OSL_ENSURE( rEntry.m_aMkPos.m_nContent
207 <= m_rDoc.GetNodes()[nPosNd]->GetContentNode()->Len(),
208 "Attribute ends after end of line" );
209 }
210 if (
211 (rEntry.m_aPtPos.m_nNode.GetIndex()+1 == nPosNd) &&
212 (rEntry.m_aPtPos.m_nContent >= nPosCt)
213 )
214 {
215 if ( !rEntry.m_isAnnotationOnEnd
216 || rEntry.m_aPtPos.m_nContent > nPosCt)
217 {
218 assert(!(rEntry.m_isAnnotationOnEnd && rEntry.m_aPtPos.m_nContent > nPosCt));
219 if ( eMode == MoveAttrsMode::POSTIT_INSERTED
220 && rEntry.m_aPtPos.m_nContent == nPosCt
221 && rEntry.m_pAttr->Which() == RES_FLTR_ANNOTATIONMARK)
222 {
223 rEntry.m_isAnnotationOnEnd = true;
224 eMode = MoveAttrsMode::DEFAULT; // only set 1 flag
225 }
226 rEntry.m_aPtPos.m_nContent++;
227 OSL_ENSURE( rEntry.m_aPtPos.m_nContent
228 <= m_rDoc.GetNodes()[nPosNd]->GetContentNode()->Len(),
229 "Attribute ends after end of line" );
230 }
231 }
232 }
233 }
234
MarkAllAttrsOld()235 void SwFltControlStack::MarkAllAttrsOld()
236 {
237 size_t nCnt = m_Entries.size();
238 for (size_t i=0; i < nCnt; ++i)
239 m_Entries[i]->m_bOld = true;
240 }
241
242 namespace
243 {
couldExtendEntry(const SwFltStackEntry * pExtendCandidate,const SfxPoolItem & rAttr)244 bool couldExtendEntry(const SwFltStackEntry *pExtendCandidate,
245 const SfxPoolItem& rAttr)
246 {
247 return (pExtendCandidate &&
248 !pExtendCandidate->m_bConsumedByField &&
249 //if we bring character attributes into the fold we need to both
250 //a) consider RES_CHRATR_FONTSIZE and RES_CHRATR_FONT wrt Word's CJK/CTL variants
251 //b) consider crossing table cell boundaries (tdf#102334)
252 isPARATR_LIST(rAttr.Which()) &&
253 *(pExtendCandidate->m_pAttr) == rAttr);
254 }
255 }
256
NewAttr(const SwPosition & rPos,const SfxPoolItem & rAttr)257 void SwFltControlStack::NewAttr(const SwPosition& rPos, const SfxPoolItem& rAttr)
258 {
259 sal_uInt16 nWhich = rAttr.Which();
260 // Set end position of potentially equal attributes on stack, so
261 // as to avoid having them accumulate
262 SwFltStackEntry *pExtendCandidate = SetAttr(rPos, nWhich);
263 if (couldExtendEntry(pExtendCandidate, rAttr))
264 {
265 //Here we optimize by seeing if there is an attribute uncommitted
266 //to the document which
267
268 //a) has the same value as this attribute
269 //b) is already open, or ends at the same place as where we're starting
270 //from. If so we merge it with this one and elide adding another
271 //to the stack
272 pExtendCandidate->SetEndPos(rPos);
273 pExtendCandidate->m_bOpen=true;
274 }
275 else
276 {
277 SwFltStackEntry *pTmp = new SwFltStackEntry(rPos, std::unique_ptr<SfxPoolItem>(rAttr.Clone()) );
278 pTmp->SetStartCP(GetCurrAttrCP());
279 m_Entries.push_back(std::unique_ptr<SwFltStackEntry>(pTmp));
280 }
281 }
282
DeleteAndDestroy(Entries::size_type nCnt)283 void SwFltControlStack::DeleteAndDestroy(Entries::size_type nCnt)
284 {
285 OSL_ENSURE(nCnt < m_Entries.size(), "Out of range!");
286 if (nCnt < m_Entries.size())
287 {
288 auto aElement = m_Entries.begin() + nCnt;
289 m_Entries.erase(aElement);
290 }
291 //Clear the para end position recorded in reader intermittently for the least impact on loading performance
292 //Because the attributes handled based on the unit of para
293 if ( empty() )
294 {
295 ClearParaEndPosition();
296 m_bHasSdOD = true;
297 m_bSdODChecked = false;
298 }
299 }
300
301 // SwFltControlStack::StealAttr() removes attributes of the given type
302 // from the stack. Allowed as nAttrId: 0 meaning any, or a specific
303 // type. This makes them disappear from the doc structure. Only
304 // attributes from the same paragraph as rPos are removed. Used for
305 // graphic apos -> images.
StealAttr(const SwNodeIndex & rNode)306 void SwFltControlStack::StealAttr(const SwNodeIndex& rNode)
307 {
308 size_t nCnt = m_Entries.size();
309
310 while (nCnt)
311 {
312 nCnt --;
313 SwFltStackEntry& rEntry = *m_Entries[nCnt];
314 if (rEntry.m_aPtPos.m_nNode.GetIndex()+1 == rNode.GetIndex())
315 {
316 DeleteAndDestroy(nCnt); // delete from the stack
317 }
318 }
319 }
320
321 // SwFltControlStack::KillUnlockedAttr() removes all attributes from
322 // the stack, which are assigned to an rPos. This makes them disappear
323 // from the doc structure. Used in WW import for ignoring attributes
324 // assigned to the 0x0c section break symbol.
KillUnlockedAttrs(const SwPosition & rPos)325 void SwFltControlStack::KillUnlockedAttrs(const SwPosition& rPos)
326 {
327 SwFltPosition aFltPos(rPos);
328
329 size_t nCnt = m_Entries.size();
330 while( nCnt )
331 {
332 nCnt --;
333 SwFltStackEntry& rEntry = *m_Entries[nCnt];
334 if( !rEntry.m_bOld
335 && !rEntry.m_bOpen
336 && (rEntry.m_aMkPos == aFltPos)
337 && (rEntry.m_aPtPos == aFltPos))
338 {
339 DeleteAndDestroy( nCnt ); // remove from stack
340 }
341 }
342 }
343
344 // Unlock all locked attributes and move to the end, all others will
345 // be applied to the document and removed from the stack.
346 // Returns if there were any selected attributes on the stack
SetAttr(const SwPosition & rPos,sal_uInt16 nAttrId,bool bTstEnd,tools::Long nHand,bool consumedByField)347 SwFltStackEntry* SwFltControlStack::SetAttr(const SwPosition& rPos,
348 sal_uInt16 nAttrId, bool bTstEnd, tools::Long nHand,
349 bool consumedByField)
350 {
351 SwFltStackEntry *pRet = nullptr;
352
353 SwFltPosition aFltPos(rPos);
354
355 OSL_ENSURE(!nAttrId ||
356 (POOLATTR_BEGIN <= nAttrId && POOLATTR_END > nAttrId) ||
357 (RES_FLTRATTR_BEGIN <= nAttrId && RES_FLTRATTR_END > nAttrId),
358 "Wrong id for attribute");
359
360 auto aI = m_Entries.begin();
361 while (aI != m_Entries.end())
362 {
363 bool bLastEntry = aI == m_Entries.end() - 1;
364
365 SwFltStackEntry& rEntry = **aI;
366 if (rEntry.m_bOpen)
367 {
368 // set end of attribute
369 bool bF = false;
370 if (!nAttrId )
371 {
372 bF = true;
373 }
374 else if (nAttrId == rEntry.m_pAttr->Which())
375 {
376 if( nAttrId != RES_FLTR_BOOKMARK && nAttrId != RES_FLTR_ANNOTATIONMARK && nAttrId != RES_FLTR_RDFMARK )
377 {
378 // query handle
379 bF = true;
380 }
381 else if (nAttrId == RES_FLTR_BOOKMARK && nHand == static_cast<SwFltBookmark*>(rEntry.m_pAttr.get())->GetHandle())
382 {
383 bF = true;
384 }
385 else if (nAttrId == RES_FLTR_ANNOTATIONMARK && nHand == static_cast<CntUInt16Item*>(rEntry.m_pAttr.get())->GetValue())
386 {
387 bF = true;
388 }
389 else if (nAttrId == RES_FLTR_RDFMARK && nHand == static_cast<SwFltRDFMark*>(rEntry.m_pAttr.get())->GetHandle())
390 {
391 bF = true;
392 }
393 }
394 if (bF)
395 {
396 rEntry.m_bConsumedByField = consumedByField;
397 rEntry.SetEndPos(rPos);
398 rEntry.SetEndCP(GetCurrAttrCP());
399 if (bLastEntry && nAttrId == rEntry.m_pAttr->Which())
400 {
401 //potential candidate for merging with an identical
402 //property beginning at rPos
403 pRet = &rEntry;
404 }
405 }
406 ++aI;
407 continue;
408 }
409
410 // if the end position is equal to the cursor position, then
411 // refrain from applying it; there needs to be following text,
412 // except at the very end. (attribute expansion !!)
413 // Never apply end stack except at document ending
414 if (bTstEnd)
415 {
416 if (m_bIsEndStack)
417 {
418 ++aI;
419 continue;
420 }
421
422 //defer inserting this attribute into the document until
423 //we advance to the next node, or finish processing the document
424 if (rEntry.m_aPtPos.m_nNode.GetIndex() == aFltPos.m_nNode.GetIndex())
425 {
426 if (bLastEntry && nAttrId == rEntry.m_pAttr->Which() &&
427 rEntry.m_aPtPos.m_nContent == aFltPos.m_nContent)
428 {
429 //potential candidate for merging with an identical
430 //property beginning at rPos
431 pRet = &rEntry;
432 }
433
434 ++aI;
435 continue;
436 }
437 }
438 SetAttrInDoc(rPos, rEntry);
439 aI = m_Entries.erase(aI);
440 }
441
442 return pRet;
443 }
444
MakePoint(const SwFltStackEntry & rEntry,SwDoc & rDoc,SwPaM & rRegion)445 static bool MakePoint(const SwFltStackEntry& rEntry, SwDoc& rDoc,
446 SwPaM& rRegion)
447 {
448 // the anchor is the Pam's Point. It's modified when inserting
449 // text, etc.; therefore it is kept on the stack. Only the
450 // attribute's format needs to be set.
451 rRegion.DeleteMark();
452
453 sal_uLong nMk = rEntry.m_aMkPos.m_nNode.GetIndex() + 1;
454 const SwNodes& rMkNodes = rEntry.m_aMkPos.m_nNode.GetNodes();
455 if (nMk >= rMkNodes.Count())
456 return false;
457
458 rRegion.GetPoint()->nNode = nMk;
459 SwContentNode* pCNd = GetContentNode(rDoc, rRegion.GetPoint()->nNode, true);
460 rRegion.GetPoint()->nContent.Assign(pCNd, rEntry.m_aMkPos.m_nContent);
461 return true;
462 }
463
464 // MakeBookRegionOrPoint() behaves like MakeRegionOrPoint, except that
465 // it adheres to certain restrictions on bookmarks in tables (cannot
466 // span more than one cell)
MakeBookRegionOrPoint(const SwFltStackEntry & rEntry,SwDoc & rDoc,SwPaM & rRegion)467 static bool MakeBookRegionOrPoint(const SwFltStackEntry& rEntry, SwDoc& rDoc,
468 SwPaM& rRegion )
469 {
470 if (rEntry.MakeRegion(rDoc, rRegion, SwFltStackEntry::RegionMode::CheckNodes))
471 {
472 if (rRegion.GetPoint()->nNode.GetNode().FindTableBoxStartNode()
473 != rRegion.GetMark()->nNode.GetNode().FindTableBoxStartNode())
474 {
475 rRegion.Exchange(); // invalid range
476 rRegion.DeleteMark(); // -> both to mark
477 }
478 return true;
479 }
480 return MakePoint(rEntry, rDoc, rRegion);
481 }
482
483 // IterateNumrulePiece() looks for the first range valid for Numrules
484 // between rTmpStart and rEnd.
485
486 // rNds denotes the doc nodes
487 // rEnd denotes the range end,
488 // rTmpStart is an in/out parameter: in: start of range to be searched,
489 // out: start of valid range
490 // rTmpEnd is an out parameter
491 // Returns true for valid range
IterateNumrulePiece(const SwNodeIndex & rEnd,SwNodeIndex & rTmpStart,SwNodeIndex & rTmpEnd)492 static bool IterateNumrulePiece( const SwNodeIndex& rEnd,
493 SwNodeIndex& rTmpStart, SwNodeIndex& rTmpEnd )
494 {
495 while( ( rTmpStart <= rEnd )
496 && !( rTmpStart.GetNode().IsTextNode() ) ) // look for valid start
497 ++rTmpStart;
498
499 rTmpEnd = rTmpStart;
500 while( ( rTmpEnd <= rEnd )
501 && ( rTmpEnd.GetNode().IsTextNode() ) ) // look for valid end + 1
502 ++rTmpEnd;
503
504 --rTmpEnd; // valid end
505
506 return rTmpStart <= rTmpEnd; // valid ?
507 }
508
509 //***This function will check whether there is existing individual attribute position for 0x0D***/
510 //The check will happen only once for a paragraph during loading
HasSdOD()511 bool SwFltControlStack::HasSdOD()
512 {
513 bool bRet = false;
514
515 for (auto const& it : m_Entries)
516 {
517 SwFltStackEntry& rEntry = *it;
518 if ( rEntry.mnStartCP == rEntry.mnEndCP )
519 {
520 if ( CheckSdOD(rEntry.mnStartCP,rEntry.mnEndCP) )
521 {
522 bRet = true;
523 break;
524 }
525 }
526 }
527
528 return bRet;
529 }
530
SetAttrInDoc(const SwPosition & rTmpPos,SwFltStackEntry & rEntry)531 void SwFltControlStack::SetAttrInDoc(const SwPosition& rTmpPos,
532 SwFltStackEntry& rEntry)
533 {
534 SwPaM aRegion( rTmpPos );
535
536 switch(rEntry.m_pAttr->Which())
537 {
538 case RES_FLTR_ANCHOR:
539 {
540 SwFrameFormat* pFormat = static_cast<SwFltAnchor*>(rEntry.m_pAttr.get())->GetFrameFormat();
541 if (pFormat != nullptr)
542 {
543 MakePoint(rEntry, m_rDoc, aRegion);
544 SwFormatAnchor aAnchor(pFormat->GetAnchor());
545 aAnchor.SetAnchor(aRegion.GetPoint());
546 pFormat->SetFormatAttr(aAnchor);
547 // So the frames will be created when inserting into
548 // existing doc (after setting the anchor!):
549 if (m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()
550 && (RndStdIds::FLY_AT_PARA == pFormat->GetAnchor().GetAnchorId()))
551 {
552 pFormat->MakeFrames();
553 }
554 }
555 }
556 break;
557
558 case RES_TXTATR_FIELD:
559 case RES_TXTATR_ANNOTATION:
560 case RES_TXTATR_INPUTFIELD:
561 break;
562
563 case RES_TXTATR_TOXMARK:
564 break;
565
566 case RES_FLTR_NUMRULE: // insert Numrule
567 {
568 const OUString& rNumNm = static_cast<SfxStringItem*>(rEntry.m_pAttr.get())->GetValue();
569 SwNumRule* pNumRule = m_rDoc.FindNumRulePtr( rNumNm );
570 if( pNumRule )
571 {
572 if (rEntry.MakeRegion(m_rDoc, aRegion, SwFltStackEntry::RegionMode::CheckNodes))
573 {
574 SwNodeIndex aTmpStart( aRegion.Start()->nNode );
575 SwNodeIndex aTmpEnd( aTmpStart );
576 SwNodeIndex& rRegEndNd = aRegion.End()->nNode;
577 while( IterateNumrulePiece( rRegEndNd,
578 aTmpStart, aTmpEnd ) )
579 {
580 SwPaM aTmpPam( aTmpStart, aTmpEnd );
581 // no start of a new list
582 m_rDoc.SetNumRule( aTmpPam, *pNumRule, false );
583
584 aTmpStart = aTmpEnd; // here starts the next range
585 ++aTmpStart;
586 }
587 }
588 else
589 m_rDoc.DelNumRule( rNumNm );
590 }
591 }
592 break;
593
594 case RES_FLTR_BOOKMARK:
595 {
596 SwFltBookmark* pB = static_cast<SwFltBookmark*>(rEntry.m_pAttr.get());
597 const OUString& rName = static_cast<SwFltBookmark*>(rEntry.m_pAttr.get())->GetName();
598
599 if (IsFlagSet(BOOK_TO_VAR_REF))
600 {
601 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, rName, false);
602 if (!pFT)
603 {
604 SwSetExpFieldType aS(&m_rDoc, rName, nsSwGetSetExpType::GSE_STRING);
605 pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType(aS);
606 }
607 SwSetExpField aField(static_cast<SwSetExpFieldType*>(pFT), pB->GetValSys());
608 aField.SetSubType( nsSwExtendedSubType::SUB_INVISIBLE );
609 MakePoint(rEntry, m_rDoc, aRegion);
610 m_rDoc.getIDocumentContentOperations().InsertPoolItem(aRegion, SwFormatField(aField));
611 MoveAttrs( *(aRegion.GetPoint()) );
612 }
613 if ( ( !IsFlagSet(HYPO) || IsFlagSet(BOOK_AND_REF) ) &&
614 !rEntry.m_bConsumedByField )
615 {
616 MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion);
617 // #i120879# - create a cross reference heading bookmark if appropriate.
618 const IDocumentMarkAccess::MarkType eBookmarkType =
619 ( pB->IsTOCBookmark() &&
620 IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark( aRegion ) )
621 ? IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK
622 : IDocumentMarkAccess::MarkType::BOOKMARK;
623 m_rDoc.getIDocumentMarkAccess()->makeMark(aRegion, rName, eBookmarkType, sw::mark::InsertMode::New);
624 }
625 }
626 break;
627 case RES_FLTR_ANNOTATIONMARK:
628 {
629 if (MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion))
630 {
631 SwTextNode const*const pTextNode(
632 aRegion.End()->nNode.GetNode().GetTextNode());
633 SwTextField const*const pField = pTextNode ? pTextNode->GetFieldTextAttrAt(
634 aRegion.End()->nContent.GetIndex() - 1, true) : nullptr;
635 if (pField)
636 {
637 SwPostItField const*const pPostIt(
638 dynamic_cast<SwPostItField const*>(pField->GetFormatField().GetField()));
639 if (pPostIt)
640 {
641 assert(pPostIt->GetName().isEmpty());
642
643 if (!aRegion.HasMark())
644 {
645 // Annotation range was found in the file, but start/end is the same,
646 // pointing after the postit placeholder (see assert above).
647 // Adjust the start of the range to actually cover the comment, similar
648 // to what the UI and the UNO API does.
649 aRegion.SetMark();
650 --aRegion.Start()->nContent;
651 }
652
653 m_rDoc.getIDocumentMarkAccess()->makeAnnotationMark(aRegion, OUString());
654 }
655 else
656 {
657 SAL_WARN("sw", "RES_FLTR_ANNOTATIONMARK: unexpected field");
658 }
659 }
660 else
661 {
662 SAL_WARN("sw", "RES_FLTR_ANNOTATIONMARK: missing field");
663 }
664 }
665 else
666 SAL_WARN("sw", "failed to make book region or point");
667 }
668 break;
669 case RES_FLTR_RDFMARK:
670 {
671 if (MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion))
672 {
673 SwFltRDFMark* pMark = static_cast<SwFltRDFMark*>(rEntry.m_pAttr.get());
674 if (aRegion.GetNode().IsTextNode())
675 {
676 SwTextNode& rTextNode = *aRegion.GetNode().GetTextNode();
677
678 for (const std::pair<OUString, OUString>& rAttribute : pMark->GetAttributes())
679 {
680 OUString aTypeNS = rAttribute.first;
681 OUString aMetadataFilePath = lcl_getTypePath(aTypeNS);
682 if (aMetadataFilePath.isEmpty())
683 continue;
684
685 SwRDFHelper::addTextNodeStatement(aTypeNS, aMetadataFilePath, rTextNode, rAttribute.first, rAttribute.second);
686 }
687 }
688 }
689 else
690 SAL_WARN("sw", "failed to make book region or point");
691 }
692 break;
693 case RES_FLTR_TOX:
694 {
695 MakePoint(rEntry, m_rDoc, aRegion);
696
697 SwPosition* pPoint = aRegion.GetPoint();
698
699 SwFltTOX* pTOXAttr = static_cast<SwFltTOX*>(rEntry.m_pAttr.get());
700
701 // test if on this node there had been a pagebreak BEFORE the
702 // tox attribute was put on the stack
703 SfxItemSet aBkSet( m_rDoc.GetAttrPool(), svl::Items<RES_PAGEDESC, RES_BREAK>{} );
704 SwContentNode* pNd = nullptr;
705 if( !pTOXAttr->HadBreakItem() || !pTOXAttr->HadPageDescItem() )
706 {
707 pNd = pPoint->nNode.GetNode().GetContentNode();
708 if( pNd )
709 {
710 const SfxItemSet* pSet = pNd->GetpSwAttrSet();
711 const SfxPoolItem* pItem;
712 if( pSet )
713 {
714 if( !pTOXAttr->HadBreakItem()
715 && SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pItem ) )
716 {
717 aBkSet.Put( *pItem );
718 pNd->ResetAttr( RES_BREAK );
719 }
720 if( !pTOXAttr->HadPageDescItem()
721 && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pItem ) )
722 {
723 aBkSet.Put( *pItem );
724 pNd->ResetAttr( RES_PAGEDESC );
725 }
726 }
727 }
728 }
729
730 // set (above saved and removed) the break item at the node following the TOX
731 if (pNd && aBkSet.Count())
732 pNd->SetAttr(aBkSet);
733 }
734 break;
735 case RES_FLTR_REDLINE:
736 {
737 if (rEntry.MakeRegion(m_rDoc, aRegion,
738 SwFltStackEntry::RegionMode::CheckNodes|SwFltStackEntry::RegionMode::CheckFieldmark))
739 {
740 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On
741 | RedlineFlags::ShowInsert
742 | RedlineFlags::ShowDelete );
743 SwFltRedline& rFltRedline = *static_cast<SwFltRedline*>(rEntry.m_pAttr.get());
744
745 SwRedlineData aData(rFltRedline.m_eType,
746 rFltRedline.m_nAutorNo,
747 rFltRedline.m_aStamp,
748 OUString(),
749 nullptr
750 );
751 m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline(aData, aRegion), true );
752 m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::NONE
753 | RedlineFlags::ShowInsert
754 | RedlineFlags::ShowDelete );
755 }
756 }
757 break;
758 default:
759 {
760 // Revised for more complex situations should be considered
761 if ( !m_bSdODChecked )
762 {
763 m_bHasSdOD = HasSdOD();
764 m_bSdODChecked = true;
765 }
766 sal_Int32 nStart = rEntry.GetStartCP();
767 sal_Int32 nEnd = rEntry.GetEndCP();
768 if (nStart != -1 && nEnd != -1 && nEnd >= nStart )
769 {
770 rEntry.SetIsParaEnd( IsParaEndInCPs(nStart,nEnd,m_bHasSdOD) );
771 }
772 if (rEntry.MakeRegion(m_rDoc, aRegion, SwFltStackEntry::RegionMode::NoCheck))
773 {
774 if (rEntry.IsParaEnd())
775 {
776 m_rDoc.getIDocumentContentOperations().InsertPoolItem(aRegion, *rEntry.m_pAttr, SetAttrMode::DEFAULT, nullptr, true);
777 }
778 else
779 {
780 m_rDoc.getIDocumentContentOperations().InsertPoolItem(aRegion, *rEntry.m_pAttr);
781 }
782 }
783 }
784 break;
785 }
786 }
787
IsParaEndInCPs(sal_Int32,sal_Int32,bool) const788 bool SwFltControlStack::IsParaEndInCPs(sal_Int32 /*nStart*/, sal_Int32 /*nEnd*/,bool /*bSdOD*/) const
789 {
790 return false;
791 }
792
CheckSdOD(sal_Int32,sal_Int32)793 bool SwFltControlStack::CheckSdOD(sal_Int32 /*nStart*/, sal_Int32 /*nEnd*/)
794 {
795 return false;
796 }
797
GetFormatStackAttr(sal_uInt16 nWhich,sal_uInt16 * pPos)798 SfxPoolItem* SwFltControlStack::GetFormatStackAttr(sal_uInt16 nWhich, sal_uInt16 * pPos)
799 {
800 size_t nSize = m_Entries.size();
801
802 while (nSize)
803 {
804 // is it the looked-for attribute ? (only applies to locked, meaning
805 // currently set attributes!!)
806 SwFltStackEntry &rEntry = *m_Entries[--nSize];
807 if (rEntry.m_bOpen && rEntry.m_pAttr->Which() == nWhich)
808 {
809 if (pPos)
810 *pPos = nSize;
811 return rEntry.m_pAttr.get(); // Ok, so stop
812 }
813 }
814 return nullptr;
815 }
816
GetOpenStackAttr(const SwPosition & rPos,sal_uInt16 nWhich)817 const SfxPoolItem* SwFltControlStack::GetOpenStackAttr(const SwPosition& rPos, sal_uInt16 nWhich)
818 {
819 SwFltPosition aFltPos(rPos);
820
821 size_t nSize = m_Entries.size();
822
823 while (nSize)
824 {
825 SwFltStackEntry &rEntry = *m_Entries[--nSize];
826 if (rEntry.m_bOpen && rEntry.m_pAttr->Which() == nWhich && rEntry.m_aMkPos == aFltPos)
827 {
828 return rEntry.m_pAttr.get();
829 }
830 }
831 return nullptr;
832 }
833
Delete(const SwPaM & rPam)834 void SwFltControlStack::Delete(const SwPaM &rPam)
835 {
836 const SwPosition *pStt = rPam.Start(), *pEnd = rPam.End();
837
838 if( !rPam.HasMark() || *pStt >= *pEnd )
839 return;
840
841 SwNodeIndex aStartNode(pStt->nNode, -1);
842 const sal_Int32 nStartIdx = pStt->nContent.GetIndex();
843 SwNodeIndex aEndNode(pEnd->nNode, -1);
844 const sal_Int32 nEndIdx = pEnd->nContent.GetIndex();
845
846 // We don't support deleting content that is over one node, or removing a node.
847 OSL_ENSURE(aEndNode == aStartNode, "nodes must be the same, or this method extended");
848 if (aEndNode != aStartNode)
849 return;
850
851 for (size_t nSize = m_Entries.size(); nSize > 0;)
852 {
853 SwFltStackEntry& rEntry = *m_Entries[--nSize];
854
855 bool bEntryStartAfterSelStart =
856 (rEntry.m_aMkPos.m_nNode == aStartNode &&
857 rEntry.m_aMkPos.m_nContent >= nStartIdx);
858
859 bool bEntryStartBeforeSelEnd =
860 (rEntry.m_aMkPos.m_nNode == aEndNode &&
861 rEntry.m_aMkPos.m_nContent <= nEndIdx);
862
863 bool bEntryEndAfterSelStart = false;
864 bool bEntryEndBeforeSelEnd = false;
865 if (!rEntry.m_bOpen)
866 {
867 bEntryEndAfterSelStart =
868 (rEntry.m_aPtPos.m_nNode == aStartNode &&
869 rEntry.m_aPtPos.m_nContent >= nStartIdx);
870
871 bEntryEndBeforeSelEnd =
872 (rEntry.m_aPtPos.m_nNode == aEndNode &&
873 rEntry.m_aPtPos.m_nContent <= nEndIdx);
874 }
875
876 bool bTotallyContained = false;
877 if (
878 bEntryStartAfterSelStart && bEntryStartBeforeSelEnd &&
879 bEntryEndAfterSelStart && bEntryEndBeforeSelEnd
880 )
881 {
882 bTotallyContained = true;
883 }
884
885 if (bTotallyContained)
886 {
887 // after start, before end, delete
888 DeleteAndDestroy(nSize);
889 continue;
890 }
891
892 const sal_Int32 nContentDiff = nEndIdx - nStartIdx;
893
894 // to be adjusted
895 if (bEntryStartAfterSelStart)
896 {
897 if (bEntryStartBeforeSelEnd)
898 {
899 // move start to new start
900 rEntry.m_aMkPos.SetPos(aStartNode, nStartIdx);
901 }
902 else
903 rEntry.m_aMkPos.m_nContent -= nContentDiff;
904 }
905
906 if (bEntryEndAfterSelStart)
907 {
908 if (bEntryEndBeforeSelEnd)
909 rEntry.m_aPtPos.SetPos(aStartNode, nStartIdx);
910 else
911 rEntry.m_aPtPos.m_nContent -= nContentDiff;
912 }
913
914 //That's what Open is, end equal to start, and nPtContent is invalid
915 if (rEntry.m_bOpen)
916 rEntry.m_aPtPos = rEntry.m_aMkPos;
917 }
918 }
919
920 // methods of SwFltAnchor follow
SwFltAnchor(SwFrameFormat * pFormat)921 SwFltAnchor::SwFltAnchor(SwFrameFormat* pFormat) :
922 SfxPoolItem(RES_FLTR_ANCHOR), m_pFrameFormat(pFormat)
923 {
924 m_pListener.reset(new SwFltAnchorListener(this));
925 m_pListener->StartListening(m_pFrameFormat->GetNotifier());
926 }
927
SwFltAnchor(const SwFltAnchor & rCpy)928 SwFltAnchor::SwFltAnchor(const SwFltAnchor& rCpy) :
929 SfxPoolItem(RES_FLTR_ANCHOR), m_pFrameFormat(rCpy.m_pFrameFormat)
930 {
931 m_pListener.reset(new SwFltAnchorListener(this));
932 m_pListener->StartListening(m_pFrameFormat->GetNotifier());
933 }
934
~SwFltAnchor()935 SwFltAnchor::~SwFltAnchor()
936 {
937 }
938
SetFrameFormat(SwFrameFormat * _pFrameFormat)939 void SwFltAnchor::SetFrameFormat(SwFrameFormat * _pFrameFormat)
940 {
941 m_pFrameFormat = _pFrameFormat;
942 }
943
944
operator ==(const SfxPoolItem & rItem) const945 bool SwFltAnchor::operator==(const SfxPoolItem& rItem) const
946 {
947 return SfxPoolItem::operator==(rItem) &&
948 m_pFrameFormat == static_cast<const SwFltAnchor&>(rItem).m_pFrameFormat;
949 }
950
Clone(SfxItemPool *) const951 SwFltAnchor* SwFltAnchor::Clone(SfxItemPool*) const
952 {
953 return new SwFltAnchor(*this);
954 }
955
SwFltAnchorListener(SwFltAnchor * pFltAnchor)956 SwFltAnchorListener::SwFltAnchorListener(SwFltAnchor* pFltAnchor)
957 : m_pFltAnchor(pFltAnchor)
958 { }
959
Notify(const SfxHint & rHint)960 void SwFltAnchorListener::Notify(const SfxHint& rHint)
961 {
962 if (rHint.GetId() == SfxHintId::Dying)
963 m_pFltAnchor->SetFrameFormat(nullptr);
964 else if (auto pDrawFrameFormatHint = dynamic_cast<const sw::DrawFrameFormatHint*>(&rHint))
965 {
966 if (pDrawFrameFormatHint->m_eId != sw::DrawFrameFormatHintId::DYING)
967 return;
968 m_pFltAnchor->SetFrameFormat(nullptr);
969 }
970 else if (rHint.GetId() == SfxHintId::SwLegacyModify)
971 {
972 auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
973 if(pLegacyHint->m_pNew->Which() != RES_FMT_CHG)
974 return;
975 auto pFormatChg = dynamic_cast<const SwFormatChg*>(pLegacyHint->m_pNew);
976 auto pFrameFormat = pFormatChg ? dynamic_cast<SwFrameFormat*>(pFormatChg->pChangedFormat) : nullptr;
977 if(pFrameFormat)
978 m_pFltAnchor->SetFrameFormat(pFrameFormat);
979 }
980 }
981
982 // methods of SwFltRedline follow
operator ==(const SfxPoolItem & rItem) const983 bool SwFltRedline::operator==(const SfxPoolItem& rItem) const
984 {
985 return SfxPoolItem::operator==(rItem) &&
986 this == &rItem;
987 }
988
Clone(SfxItemPool *) const989 SwFltRedline* SwFltRedline::Clone( SfxItemPool* ) const
990 {
991 return new SwFltRedline(*this);
992 }
993
994 // methods of SwFltBookmark follow
SwFltBookmark(const OUString & rNa,const OUString & rVa,tools::Long nHand,const bool bIsTOCBookmark)995 SwFltBookmark::SwFltBookmark( const OUString& rNa, const OUString& rVa,
996 tools::Long nHand, const bool bIsTOCBookmark )
997 : SfxPoolItem( RES_FLTR_BOOKMARK )
998 , mnHandle( nHand )
999 , maName( rNa )
1000 , maVal( rVa )
1001 , mbIsTOCBookmark( bIsTOCBookmark )
1002 {
1003 // eSrc: CHARSET_DONTKNOW for no transform at operator <<
1004 // Upcase is always done.
1005 // Transform is never done at XXXStack.NewAttr(...).
1006 // otherwise: Src Charset from argument for aName
1007 // Src Charset from filter for aVal ( Text )
1008
1009 if ( IsTOCBookmark() && ! rNa.startsWith(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()) )
1010 {
1011 maName = IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix();
1012 maName += rNa;
1013 }
1014 }
1015
operator ==(const SfxPoolItem & rItem) const1016 bool SwFltBookmark::operator==(const SfxPoolItem& rItem) const
1017 {
1018 return SfxPoolItem::operator==(rItem)
1019 && maName == static_cast<const SwFltBookmark&>(rItem).maName
1020 && mnHandle == static_cast<const SwFltBookmark&>(rItem).mnHandle;
1021 }
1022
Clone(SfxItemPool *) const1023 SwFltBookmark* SwFltBookmark::Clone(SfxItemPool*) const
1024 {
1025 return new SwFltBookmark(*this);
1026 }
1027
SwFltRDFMark()1028 SwFltRDFMark::SwFltRDFMark()
1029 : SfxPoolItem(RES_FLTR_RDFMARK),
1030 m_nHandle(0)
1031 {
1032 }
1033
operator ==(const SfxPoolItem & rItem) const1034 bool SwFltRDFMark::operator==(const SfxPoolItem& rItem) const
1035 {
1036 if (!SfxPoolItem::operator==(rItem))
1037 return false;
1038
1039 const SwFltRDFMark& rMark = static_cast<const SwFltRDFMark&>(rItem);
1040
1041 return m_nHandle == rMark.m_nHandle && m_aAttributes == rMark.m_aAttributes;
1042 }
1043
Clone(SfxItemPool *) const1044 SwFltRDFMark* SwFltRDFMark::Clone(SfxItemPool*) const
1045 {
1046 return new SwFltRDFMark(*this);
1047 }
1048
SetHandle(tools::Long nHandle)1049 void SwFltRDFMark::SetHandle(tools::Long nHandle)
1050 {
1051 m_nHandle = nHandle;
1052 }
1053
GetHandle() const1054 tools::Long SwFltRDFMark::GetHandle() const
1055 {
1056 return m_nHandle;
1057 }
1058
SetAttributes(const std::vector<std::pair<OUString,OUString>> & rAttributes)1059 void SwFltRDFMark::SetAttributes(const std::vector< std::pair<OUString, OUString> >& rAttributes)
1060 {
1061 m_aAttributes = rAttributes;
1062 }
1063
GetAttributes() const1064 const std::vector< std::pair<OUString, OUString> >& SwFltRDFMark::GetAttributes() const
1065 {
1066 return m_aAttributes;
1067 }
1068
1069 // methods of SwFltTOX follow
SwFltTOX(std::shared_ptr<SwTOXBase> xBase)1070 SwFltTOX::SwFltTOX(std::shared_ptr<SwTOXBase> xBase)
1071 : SfxPoolItem(RES_FLTR_TOX), m_xTOXBase(std::move(xBase)),
1072 m_bHadBreakItem( false ), m_bHadPageDescItem( false )
1073 {
1074 }
1075
operator ==(const SfxPoolItem & rItem) const1076 bool SwFltTOX::operator==(const SfxPoolItem& rItem) const
1077 {
1078 return SfxPoolItem::operator==(rItem) &&
1079 m_xTOXBase.get() == static_cast<const SwFltTOX&>(rItem).m_xTOXBase.get();
1080 }
1081
Clone(SfxItemPool *) const1082 SwFltTOX* SwFltTOX::Clone(SfxItemPool*) const
1083 {
1084 return new SwFltTOX(*this);
1085 }
1086
1087 // UpdatePageDescs needs to be called at end of parsing to make Writer actually
1088 // accept Pagedescs contents
UpdatePageDescs(SwDoc & rDoc,size_t nInPageDescOffset)1089 void UpdatePageDescs(SwDoc &rDoc, size_t nInPageDescOffset)
1090 {
1091 // Update document page descriptors (only this way also left pages
1092 // get adjusted)
1093
1094 // PageDesc "Standard"
1095 rDoc.ChgPageDesc(0, rDoc.GetPageDesc(0));
1096
1097 // PageDescs "Convert..."
1098 for (size_t i = nInPageDescOffset; i < rDoc.GetPageDescCnt(); ++i)
1099 rDoc.ChgPageDesc(i, rDoc.GetPageDesc(i));
1100 }
1101
FrameDeleteWatch(SwFrameFormat * pFormat)1102 FrameDeleteWatch::FrameDeleteWatch(SwFrameFormat* pFormat)
1103 : m_pFormat(pFormat)
1104 {
1105 if(m_pFormat)
1106 StartListening(pFormat->GetNotifier());
1107 }
1108
Notify(const SfxHint & rHint)1109 void FrameDeleteWatch::Notify(const SfxHint& rHint)
1110 {
1111 bool bDying = false;
1112 if (rHint.GetId() == SfxHintId::Dying)
1113 bDying = true;
1114 else if (auto pDrawFrameFormatHint = dynamic_cast<const sw::DrawFrameFormatHint*>(&rHint))
1115 bDying = pDrawFrameFormatHint->m_eId == sw::DrawFrameFormatHintId::DYING;
1116 if (bDying)
1117 {
1118 m_pFormat = nullptr;
1119 EndListeningAll();
1120 }
1121 }
1122
~FrameDeleteWatch()1123 FrameDeleteWatch::~FrameDeleteWatch()
1124 {
1125 m_pFormat = nullptr;
1126 EndListeningAll();
1127 }
1128
1129 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1130