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 .
22 #include <vcl/svapp.hxx>
24 #include <chartlis.hxx>
26 #include <document.hxx>
27 #include <reftokenhelper.hxx>
28 #include <formula/token.hxx>
29 #include <com/sun/star/chart/XChartDataChangeEventListener.hpp>
31 using namespace com::sun::star
;
33 using ::std::for_each
;
35 // Update chart listeners quickly, to get a similar behavior to loaded charts
36 // which register UNO listeners.
40 uno::Reference
< chart::XChartDataChangeEventListener
> xListener
;
41 uno::Reference
< chart::XChartData
> xSource
;
44 ScChartUnoData( uno::Reference
< chart::XChartDataChangeEventListener
> xL
,
45 uno::Reference
< chart::XChartData
> xS
) :
46 xListener(std::move( xL
)), xSource(std::move( xS
)) {}
48 const uno::Reference
< chart::XChartDataChangeEventListener
>& GetListener() const { return xListener
; }
49 const uno::Reference
< chart::XChartData
>& GetSource() const { return xSource
; }
53 ScChartListener::ExternalRefListener::ExternalRefListener(ScChartListener
& rParent
, ScDocument
& rDoc
) :
54 mrParent(rParent
), m_pDoc(&rDoc
)
58 ScChartListener::ExternalRefListener::~ExternalRefListener()
60 if (!m_pDoc
|| m_pDoc
->IsInDtorClear())
61 // The document is being destroyed. Do nothing.
64 // Make sure to remove all pointers to this object.
65 m_pDoc
->GetExternalRefManager()->removeLinkListener(this);
68 void ScChartListener::ExternalRefListener::notify(sal_uInt16 nFileId
, ScExternalRefManager::LinkUpdateType eType
)
72 case ScExternalRefManager::LINK_MODIFIED
:
74 if (maFileIds
.count(nFileId
))
75 // We are listening to this external document. Send an update
76 // request to the chart.
77 mrParent
.SetUpdateQueue();
80 case ScExternalRefManager::LINK_BROKEN
:
81 removeFileId(nFileId
);
83 case ScExternalRefManager::OH_NO_WE_ARE_GOING_TO_DIE
:
89 void ScChartListener::ExternalRefListener::addFileId(sal_uInt16 nFileId
)
91 maFileIds
.insert(nFileId
);
94 void ScChartListener::ExternalRefListener::removeFileId(sal_uInt16 nFileId
)
96 maFileIds
.erase(nFileId
);
99 ScChartListener::ScChartListener( OUString aName
, ScDocument
& rDocP
,
100 const ScRangeListRef
& rRangeList
) :
101 maName(std::move(aName
)),
106 ScRefTokenHelper::getTokensFromRangeList(&rDocP
, maTokens
, *rRangeList
);
109 ScChartListener::ScChartListener( OUString aName
, ScDocument
& rDocP
, vector
<ScTokenRef
> aTokens
) :
110 maTokens(std::move(aTokens
)),
111 maName(std::move(aName
)),
118 ScChartListener::~ScChartListener()
120 if ( HasBroadcaster() )
124 if (mpExtRefListener
)
126 // Stop listening to all external files.
127 ScExternalRefManager
* pRefMgr
= mrDoc
.GetExternalRefManager();
128 const std::unordered_set
<sal_uInt16
>& rFileIds
= mpExtRefListener
->getAllFileIds();
129 for (const auto& rFileId
: rFileIds
)
130 pRefMgr
->removeLinkListener(rFileId
, mpExtRefListener
.get());
134 void ScChartListener::SetUno(
135 const uno::Reference
< chart::XChartDataChangeEventListener
>& rListener
,
136 const uno::Reference
< chart::XChartData
>& rSource
)
138 pUnoData
.reset( new ScChartUnoData( rListener
, rSource
) );
141 uno::Reference
< chart::XChartDataChangeEventListener
> ScChartListener::GetUnoListener() const
144 return pUnoData
->GetListener();
145 return uno::Reference
< chart::XChartDataChangeEventListener
>();
148 uno::Reference
< chart::XChartData
> ScChartListener::GetUnoSource() const
151 return pUnoData
->GetSource();
152 return uno::Reference
< chart::XChartData
>();
155 void ScChartListener::Notify( const SfxHint
& rHint
)
157 if (rHint
.GetId() == SfxHintId::ScDataChanged
)
161 void ScChartListener::Update()
163 if ( mrDoc
.IsInInterpreter() )
164 { // If interpreting do nothing and restart timer so we don't
165 // interfere with interpreter and don't produce an Err522 or similar.
166 // This may happen if we are rescheduled via Basic function.
167 mrDoc
.GetChartListenerCollection()->StartTimer();
173 // recognize some day what has changed inside the Chart
174 chart::ChartDataChangeEvent
aEvent( pUnoData
->GetSource(),
175 chart::ChartDataChangeType_ALL
,
177 pUnoData
->GetListener()->chartDataChanged( aEvent
);
179 else if ( mrDoc
.GetAutoCalc() )
182 mrDoc
.UpdateChart(GetName());
186 ScRangeListRef
ScChartListener::GetRangeList() const
188 ScRangeListRef
aRLRef(new ScRangeList
);
189 ScRefTokenHelper::getRangeListFromTokens(&mrDoc
, *aRLRef
, maTokens
, ScAddress());
193 void ScChartListener::SetRangeList( const ScRangeListRef
& rNew
)
195 vector
<ScTokenRef
> aTokens
;
196 ScRefTokenHelper::getTokensFromRangeList(&mrDoc
, aTokens
, *rNew
);
197 maTokens
.swap(aTokens
);
202 class StartEndListening
205 StartEndListening(ScDocument
& rDoc
, ScChartListener
& rParent
, bool bStart
) :
206 mrDoc(rDoc
), mrParent(rParent
), mbStart(bStart
) {}
208 void operator() (const ScTokenRef
& pToken
)
210 if (!ScRefTokenHelper::isRef(pToken
))
213 bool bExternal
= ScRefTokenHelper::isExternalRef(pToken
);
216 sal_uInt16 nFileId
= pToken
->GetIndex();
217 ScExternalRefManager
* pRefMgr
= mrDoc
.GetExternalRefManager();
218 ScChartListener::ExternalRefListener
* pExtRefListener
= mrParent
.GetExtRefListener();
221 pRefMgr
->addLinkListener(nFileId
, pExtRefListener
);
222 pExtRefListener
->addFileId(nFileId
);
226 pRefMgr
->removeLinkListener(nFileId
, pExtRefListener
);
227 pExtRefListener
->removeFileId(nFileId
);
233 ScRefTokenHelper::getRangeFromToken(&mrDoc
, aRange
, pToken
, ScAddress(), bExternal
);
235 startListening(aRange
);
237 endListening(aRange
);
241 void startListening(const ScRange
& rRange
)
243 if (rRange
.aStart
== rRange
.aEnd
)
244 mrDoc
.StartListeningCell(rRange
.aStart
, &mrParent
);
246 mrDoc
.StartListeningArea(rRange
, false, &mrParent
);
249 void endListening(const ScRange
& rRange
)
251 if (rRange
.aStart
== rRange
.aEnd
)
252 mrDoc
.EndListeningCell(rRange
.aStart
, &mrParent
);
254 mrDoc
.EndListeningArea(rRange
, false, &mrParent
);
258 ScChartListener
& mrParent
;
264 void ScChartListener::StartListeningTo()
266 if (maTokens
.empty())
267 // no references to listen to.
270 for_each(maTokens
.begin(), maTokens
.end(), StartEndListening(mrDoc
, *this, true));
273 void ScChartListener::EndListeningTo()
275 if (maTokens
.empty())
276 // no references to listen to.
279 for_each(maTokens
.begin(), maTokens
.end(), StartEndListening(mrDoc
, *this, false));
282 void ScChartListener::ChangeListening( const ScRangeListRef
& rRangeListRef
,
286 SetRangeList( rRangeListRef
);
292 void ScChartListener::UpdateChartIntersecting( const ScRange
& rRange
)
295 ScRefTokenHelper::getTokenFromRange(&mrDoc
, pToken
, rRange
);
297 if (ScRefTokenHelper::intersects(&mrDoc
, maTokens
, pToken
, ScAddress()))
299 // force update (chart has to be loaded), don't use ScChartListener::Update
300 mrDoc
.UpdateChart(GetName());
304 ScChartListener::ExternalRefListener
* ScChartListener::GetExtRefListener()
306 if (!mpExtRefListener
)
307 mpExtRefListener
.reset(new ExternalRefListener(*this, mrDoc
));
309 return mpExtRefListener
.get();
312 void ScChartListener::SetUpdateQueue()
315 mrDoc
.GetChartListenerCollection()->StartTimer();
318 bool ScChartListener::operator==( const ScChartListener
& r
) const
320 bool b1
= !maTokens
.empty();
321 bool b2
= !r
.maTokens
.empty();
323 if (&mrDoc
!= &r
.mrDoc
|| bUsed
!= r
.bUsed
|| bDirty
!= r
.bDirty
||
324 GetName() != r
.GetName() || b1
!= b2
)
328 // both token list instances are empty.
331 return maTokens
== r
.maTokens
;
334 bool ScChartListener::operator!=( const ScChartListener
& r
) const
336 return !operator==(r
);
339 ScChartHiddenRangeListener::ScChartHiddenRangeListener()
343 ScChartHiddenRangeListener::~ScChartHiddenRangeListener()
348 void ScChartListenerCollection::Init()
350 aIdle
.SetInvokeHandler( LINK( this, ScChartListenerCollection
, TimerHdl
) );
351 aIdle
.SetPriority( TaskPriority::REPAINT
);
354 ScChartListenerCollection::ScChartListenerCollection( ScDocument
& rDocP
) :
355 meModifiedDuringUpdate( SC_CLCUPDATE_NONE
),
356 aIdle( "sc::ScChartListenerCollection aIdle" ),
362 ScChartListenerCollection::ScChartListenerCollection(
363 const ScChartListenerCollection
& rColl
) :
364 meModifiedDuringUpdate( SC_CLCUPDATE_NONE
),
365 aIdle( "sc::ScChartListenerCollection aIdle" ),
371 ScChartListenerCollection::~ScChartListenerCollection()
373 // remove ChartListener objects before aIdle dtor is called, because
374 // ScChartListener::EndListeningTo may cause ScChartListenerCollection::StartTimer
375 // to be called if an empty ScNoteCell is deleted
380 void ScChartListenerCollection::StartAllListeners()
382 for (auto const& it
: m_Listeners
)
384 it
.second
->StartListeningTo();
388 bool ScChartListenerCollection::insert(ScChartListener
* pListener
)
390 if (meModifiedDuringUpdate
== SC_CLCUPDATE_RUNNING
)
391 meModifiedDuringUpdate
= SC_CLCUPDATE_MODIFIED
;
392 OUString aName
= pListener
->GetName();
393 return m_Listeners
.insert(std::make_pair(aName
, std::unique_ptr
<ScChartListener
>(pListener
))).second
;
396 void ScChartListenerCollection::removeByName(const OUString
& rName
)
398 if (meModifiedDuringUpdate
== SC_CLCUPDATE_RUNNING
)
399 meModifiedDuringUpdate
= SC_CLCUPDATE_MODIFIED
;
400 m_Listeners
.erase(rName
);
403 ScChartListener
* ScChartListenerCollection::findByName(const OUString
& rName
)
405 ListenersType::iterator
const it
= m_Listeners
.find(rName
);
406 return it
== m_Listeners
.end() ? nullptr : it
->second
.get();
409 const ScChartListener
* ScChartListenerCollection::findByName(const OUString
& rName
) const
411 ListenersType::const_iterator
const it
= m_Listeners
.find(rName
);
412 return it
== m_Listeners
.end() ? nullptr : it
->second
.get();
415 bool ScChartListenerCollection::hasListeners() const
417 return !m_Listeners
.empty();
420 OUString
ScChartListenerCollection::getUniqueName(std::u16string_view rPrefix
) const
422 for (sal_Int32 nNum
= 1; nNum
< 10000; ++nNum
) // arbitrary limit to prevent infinite loop.
424 OUString aTestName
= rPrefix
+ OUString::number(nNum
);
425 if (m_Listeners
.find(aTestName
) == m_Listeners
.end())
431 void ScChartListenerCollection::ChangeListening( const OUString
& rName
,
432 const ScRangeListRef
& rRangeListRef
)
434 ScChartListener
* pCL
= findByName(rName
);
437 pCL
->EndListeningTo();
438 pCL
->SetRangeList( rRangeListRef
);
442 pCL
= new ScChartListener(rName
, rDoc
, rRangeListRef
);
445 pCL
->StartListeningTo();
448 void ScChartListenerCollection::FreeUnused()
450 if (meModifiedDuringUpdate
== SC_CLCUPDATE_RUNNING
)
451 meModifiedDuringUpdate
= SC_CLCUPDATE_MODIFIED
;
455 for (auto & pair
: m_Listeners
)
457 ScChartListener
* p
= pair
.second
.get();
460 // We don't delete UNO charts; they are to be deleted separately via FreeUno().
461 aUsed
.insert(std::make_pair(pair
.first
, std::move(pair
.second
)));
468 aUsed
.insert(std::make_pair(pair
.first
, std::move(pair
.second
)));
472 m_Listeners
= std::move(aUsed
);
475 void ScChartListenerCollection::FreeUno( const uno::Reference
< chart::XChartDataChangeEventListener
>& rListener
,
476 const uno::Reference
< chart::XChartData
>& rSource
)
478 if (meModifiedDuringUpdate
== SC_CLCUPDATE_RUNNING
)
479 meModifiedDuringUpdate
= SC_CLCUPDATE_MODIFIED
;
481 for (auto it
= m_Listeners
.begin(); it
!= m_Listeners
.end(); )
483 ScChartListener
*const p
= it
->second
.get();
484 if (p
->IsUno() && p
->GetUnoListener() == rListener
&& p
->GetUnoSource() == rSource
)
485 it
= m_Listeners
.erase(it
);
491 void ScChartListenerCollection::StartTimer()
496 IMPL_LINK_NOARG(ScChartListenerCollection
, TimerHdl
, Timer
*, void)
498 if ( Application::AnyInput( VclInputFlags::KEYBOARD
) )
506 void ScChartListenerCollection::UpdateDirtyCharts()
508 // During ScChartListener::Update() the most nasty things can happen due to
509 // UNO listeners, e.g. reentrant calls via BASIC to insert() and FreeUno()
510 // and similar that modify m_Listeners and invalidate iterators.
511 meModifiedDuringUpdate
= SC_CLCUPDATE_RUNNING
;
513 for (auto const& it
: m_Listeners
)
515 ScChartListener
*const p
= it
.second
.get();
519 if (meModifiedDuringUpdate
== SC_CLCUPDATE_MODIFIED
)
520 break; // iterator is invalid
522 if (aIdle
.IsActive() && !rDoc
.IsImportingXML())
523 break; // one interfered
525 meModifiedDuringUpdate
= SC_CLCUPDATE_NONE
;
528 void ScChartListenerCollection::SetDirty()
530 for (auto const& it
: m_Listeners
)
532 it
.second
->SetDirty(true);
538 void ScChartListenerCollection::SetDiffDirty(
539 const ScChartListenerCollection
& rCmp
, bool bSetChartRangeLists
)
542 for (auto const& it
: m_Listeners
)
544 ScChartListener
*const pCL
= it
.second
.get();
546 const ScChartListener
* pCLCmp
= rCmp
.findByName(pCL
->GetName());
547 if (!pCLCmp
|| *pCL
!= *pCLCmp
)
549 if ( bSetChartRangeLists
)
553 const ScRangeListRef
& rList1
= pCL
->GetRangeList();
554 const ScRangeListRef
& rList2
= pCLCmp
->GetRangeList();
555 bool b1
= rList1
.is();
556 bool b2
= rList2
.is();
557 if ( b1
!= b2
|| (b1
&& b2
&& (*rList1
!= *rList2
)) )
558 rDoc
.SetChartRangeList( pCL
->GetName(), rList1
);
561 rDoc
.SetChartRangeList( pCL
->GetName(), pCL
->GetRangeList() );
564 pCL
->SetDirty( true );
571 void ScChartListenerCollection::SetRangeDirty( const ScRange
& rRange
)
574 for (auto const& it
: m_Listeners
)
576 ScChartListener
*const pCL
= it
.second
.get();
577 const ScRangeListRef
& rList
= pCL
->GetRangeList();
578 if ( rList
.is() && rList
->Intersects( rRange
) )
581 pCL
->SetDirty( true );
587 // New hidden range listener implementation
588 for (auto& [pListener
, rHiddenRange
] : maHiddenListeners
)
590 if (rHiddenRange
.Intersects(rRange
))
597 void ScChartListenerCollection::UpdateChartsContainingTab( SCTAB nTab
)
599 ScRange
aRange( 0, 0, nTab
, rDoc
.MaxCol(), rDoc
.MaxRow(), nTab
);
600 for (auto const& it
: m_Listeners
)
602 it
.second
->UpdateChartIntersecting(aRange
);
606 bool ScChartListenerCollection::operator==( const ScChartListenerCollection
& r
) const
608 // Do not use ScStrCollection::operator==() here that uses IsEqual and Compare.
609 // Use ScChartListener::operator==() instead.
610 if (&rDoc
!= &r
.rDoc
)
613 return std::equal(m_Listeners
.begin(), m_Listeners
.end(), r
.m_Listeners
.begin(), r
.m_Listeners
.end(),
614 [](const ListenersType::value_type
& lhs
, const ListenersType::value_type
& rhs
) {
615 return (lhs
.first
== rhs
.first
) && (*lhs
.second
== *rhs
.second
);
619 void ScChartListenerCollection::StartListeningHiddenRange( const ScRange
& rRange
, ScChartHiddenRangeListener
* pListener
)
621 maHiddenListeners
.insert(std::make_pair
<>(pListener
, rRange
));
624 void ScChartListenerCollection::EndListeningHiddenRange( ScChartHiddenRangeListener
* pListener
)
626 auto range
= maHiddenListeners
.equal_range(pListener
);
627 maHiddenListeners
.erase(range
.first
, range
.second
);
630 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */