Avoid potential negative array index access to cached text.
[LibreOffice.git] / sc / source / core / tool / chartlis.cxx
blob024ff205e1f613ee2b9e5678129beae20f963ae4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <memory>
21 #include <utility>
22 #include <vcl/svapp.hxx>
24 #include <chartlis.hxx>
25 #include <brdcst.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;
32 using ::std::vector;
33 using ::std::for_each;
35 // Update chart listeners quickly, to get a similar behavior to loaded charts
36 // which register UNO listeners.
38 class ScChartUnoData
40 uno::Reference< chart::XChartDataChangeEventListener > xListener;
41 uno::Reference< chart::XChartData > xSource;
43 public:
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; }
52 // ScChartListener
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.
62 return;
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)
70 switch (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();
79 break;
80 case ScExternalRefManager::LINK_BROKEN:
81 removeFileId(nFileId);
82 break;
83 case ScExternalRefManager::OH_NO_WE_ARE_GOING_TO_DIE:
84 m_pDoc = nullptr;
85 break;
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)),
102 mrDoc( rDocP ),
103 bUsed( false ),
104 bDirty( false )
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)),
112 mrDoc( rDocP ),
113 bUsed( false ),
114 bDirty( false )
118 ScChartListener::~ScChartListener()
120 if ( HasBroadcaster() )
121 EndListeningTo();
122 pUnoData.reset();
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
143 if ( pUnoData )
144 return pUnoData->GetListener();
145 return uno::Reference< chart::XChartDataChangeEventListener >();
148 uno::Reference< chart::XChartData > ScChartListener::GetUnoSource() const
150 if ( pUnoData )
151 return pUnoData->GetSource();
152 return uno::Reference< chart::XChartData >();
155 void ScChartListener::Notify( const SfxHint& rHint )
157 if (rHint.GetId() == SfxHintId::ScDataChanged)
158 SetUpdateQueue();
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();
168 return ;
170 if ( pUnoData )
172 bDirty = false;
173 // recognize some day what has changed inside the Chart
174 chart::ChartDataChangeEvent aEvent( pUnoData->GetSource(),
175 chart::ChartDataChangeType_ALL,
176 0, 0, 0, 0 );
177 pUnoData->GetListener()->chartDataChanged( aEvent );
179 else if ( mrDoc.GetAutoCalc() )
181 bDirty = false;
182 mrDoc.UpdateChart(GetName());
186 ScRangeListRef ScChartListener::GetRangeList() const
188 ScRangeListRef aRLRef(new ScRangeList);
189 ScRefTokenHelper::getRangeListFromTokens(&mrDoc, *aRLRef, maTokens, ScAddress());
190 return aRLRef;
193 void ScChartListener::SetRangeList( const ScRangeListRef& rNew )
195 vector<ScTokenRef> aTokens;
196 ScRefTokenHelper::getTokensFromRangeList(&mrDoc, aTokens, *rNew);
197 maTokens.swap(aTokens);
200 namespace {
202 class StartEndListening
204 public:
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))
211 return;
213 bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
214 if (bExternal)
216 sal_uInt16 nFileId = pToken->GetIndex();
217 ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager();
218 ScChartListener::ExternalRefListener* pExtRefListener = mrParent.GetExtRefListener();
219 if (mbStart)
221 pRefMgr->addLinkListener(nFileId, pExtRefListener);
222 pExtRefListener->addFileId(nFileId);
224 else
226 pRefMgr->removeLinkListener(nFileId, pExtRefListener);
227 pExtRefListener->removeFileId(nFileId);
230 else
232 ScRange aRange;
233 ScRefTokenHelper::getRangeFromToken(&mrDoc, aRange, pToken, ScAddress(), bExternal);
234 if (mbStart)
235 startListening(aRange);
236 else
237 endListening(aRange);
240 private:
241 void startListening(const ScRange& rRange)
243 if (rRange.aStart == rRange.aEnd)
244 mrDoc.StartListeningCell(rRange.aStart, &mrParent);
245 else
246 mrDoc.StartListeningArea(rRange, false, &mrParent);
249 void endListening(const ScRange& rRange)
251 if (rRange.aStart == rRange.aEnd)
252 mrDoc.EndListeningCell(rRange.aStart, &mrParent);
253 else
254 mrDoc.EndListeningArea(rRange, false, &mrParent);
256 private:
257 ScDocument& mrDoc;
258 ScChartListener& mrParent;
259 bool mbStart;
264 void ScChartListener::StartListeningTo()
266 if (maTokens.empty())
267 // no references to listen to.
268 return;
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.
277 return;
279 for_each(maTokens.begin(), maTokens.end(), StartEndListening(mrDoc, *this, false));
282 void ScChartListener::ChangeListening( const ScRangeListRef& rRangeListRef,
283 bool bDirtyP )
285 EndListeningTo();
286 SetRangeList( rRangeListRef );
287 StartListeningTo();
288 if ( bDirtyP )
289 SetDirty( true );
292 void ScChartListener::UpdateChartIntersecting( const ScRange& rRange )
294 ScTokenRef pToken;
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()
314 bDirty = true;
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)
325 return false;
327 if (!b1 && !b2)
328 // both token list instances are empty.
329 return true;
331 return maTokens == r.maTokens;
334 bool ScChartListener::operator!=( const ScChartListener& r ) const
336 return !operator==(r);
339 ScChartHiddenRangeListener::ScChartHiddenRangeListener()
343 ScChartHiddenRangeListener::~ScChartHiddenRangeListener()
345 // empty d'tor
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" ),
357 rDoc( rDocP )
359 Init();
362 ScChartListenerCollection::ScChartListenerCollection(
363 const ScChartListenerCollection& rColl ) :
364 meModifiedDuringUpdate( SC_CLCUPDATE_NONE ),
365 aIdle( "sc::ScChartListenerCollection aIdle" ),
366 rDoc( rColl.rDoc )
368 Init();
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
377 m_Listeners.clear();
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())
426 return aTestName;
428 return OUString();
431 void ScChartListenerCollection::ChangeListening( const OUString& rName,
432 const ScRangeListRef& rRangeListRef )
434 ScChartListener* pCL = findByName(rName);
435 if (pCL)
437 pCL->EndListeningTo();
438 pCL->SetRangeList( rRangeListRef );
440 else
442 pCL = new ScChartListener(rName, rDoc, rRangeListRef);
443 insert(pCL);
445 pCL->StartListeningTo();
448 void ScChartListenerCollection::FreeUnused()
450 if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
451 meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
453 ListenersType aUsed;
455 for (auto & pair : m_Listeners)
457 ScChartListener* p = pair.second.get();
458 if (p->IsUno())
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)));
462 continue;
465 if (p->IsUsed())
467 p->SetUsed(false);
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);
486 else
487 ++it;
491 void ScChartListenerCollection::StartTimer()
493 aIdle.Start();
496 IMPL_LINK_NOARG(ScChartListenerCollection, TimerHdl, Timer *, void)
498 if ( Application::AnyInput( VclInputFlags::KEYBOARD ) )
500 aIdle.Start();
501 return;
503 UpdateDirtyCharts();
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();
516 if (p->IsDirty())
517 p->Update();
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);
535 StartTimer();
538 void ScChartListenerCollection::SetDiffDirty(
539 const ScChartListenerCollection& rCmp, bool bSetChartRangeLists )
541 bool bDirty = false;
542 for (auto const& it : m_Listeners)
544 ScChartListener *const pCL = it.second.get();
545 assert(pCL);
546 const ScChartListener* pCLCmp = rCmp.findByName(pCL->GetName());
547 if (!pCLCmp || *pCL != *pCLCmp)
549 if ( bSetChartRangeLists )
551 if (pCLCmp)
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 );
560 else
561 rDoc.SetChartRangeList( pCL->GetName(), pCL->GetRangeList() );
563 bDirty = true;
564 pCL->SetDirty( true );
567 if ( bDirty )
568 StartTimer();
571 void ScChartListenerCollection::SetRangeDirty( const ScRange& rRange )
573 bool bDirty = false;
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 ) )
580 bDirty = true;
581 pCL->SetDirty( true );
584 if ( bDirty )
585 StartTimer();
587 // New hidden range listener implementation
588 for (auto& [pListener, rHiddenRange] : maHiddenListeners)
590 if (rHiddenRange.Intersects(rRange))
592 pListener->notify();
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)
611 return false;
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: */