summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/chartlis.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/chartlis.cxx')
-rw-r--r--sc/source/core/tool/chartlis.cxx629
1 files changed, 629 insertions, 0 deletions
diff --git a/sc/source/core/tool/chartlis.cxx b/sc/source/core/tool/chartlis.cxx
new file mode 100644
index 000000000..70b60e826
--- /dev/null
+++ b/sc/source/core/tool/chartlis.cxx
@@ -0,0 +1,629 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <vcl/svapp.hxx>
+
+#include <chartlis.hxx>
+#include <brdcst.hxx>
+#include <document.hxx>
+#include <reftokenhelper.hxx>
+#include <formula/token.hxx>
+#include <com/sun/star/chart/XChartDataChangeEventListener.hpp>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::std::for_each;
+
+// Update chart listeners quickly, to get a similar behavior to loaded charts
+// which register UNO listeners.
+
+class ScChartUnoData
+{
+ uno::Reference< chart::XChartDataChangeEventListener > xListener;
+ uno::Reference< chart::XChartData > xSource;
+
+public:
+ ScChartUnoData( const uno::Reference< chart::XChartDataChangeEventListener >& rL,
+ const uno::Reference< chart::XChartData >& rS ) :
+ xListener( rL ), xSource( rS ) {}
+
+ const uno::Reference< chart::XChartDataChangeEventListener >& GetListener() const { return xListener; }
+ const uno::Reference< chart::XChartData >& GetSource() const { return xSource; }
+};
+
+// ScChartListener
+ScChartListener::ExternalRefListener::ExternalRefListener(ScChartListener& rParent, ScDocument* pDoc) :
+ mrParent(rParent), mpDoc(pDoc)
+{
+}
+
+ScChartListener::ExternalRefListener::~ExternalRefListener()
+{
+ if (!mpDoc || mpDoc->IsInDtorClear())
+ // The document is being destroyed. Do nothing.
+ return;
+
+ // Make sure to remove all pointers to this object.
+ mpDoc->GetExternalRefManager()->removeLinkListener(this);
+}
+
+void ScChartListener::ExternalRefListener::notify(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType)
+{
+ switch (eType)
+ {
+ case ScExternalRefManager::LINK_MODIFIED:
+ {
+ if (maFileIds.count(nFileId))
+ // We are listening to this external document. Send an update
+ // request to the chart.
+ mrParent.SetUpdateQueue();
+ }
+ break;
+ case ScExternalRefManager::LINK_BROKEN:
+ removeFileId(nFileId);
+ break;
+ }
+}
+
+void ScChartListener::ExternalRefListener::addFileId(sal_uInt16 nFileId)
+{
+ maFileIds.insert(nFileId);
+}
+
+void ScChartListener::ExternalRefListener::removeFileId(sal_uInt16 nFileId)
+{
+ maFileIds.erase(nFileId);
+}
+
+ScChartListener::ScChartListener( const OUString& rName, ScDocument* pDocP,
+ const ScRangeListRef& rRangeList ) :
+ SvtListener(),
+ mpTokens(new vector<ScTokenRef>),
+ maName(rName),
+ mpDoc( pDocP ),
+ bUsed( false ),
+ bDirty( false )
+{
+ ScRefTokenHelper::getTokensFromRangeList(pDocP, *mpTokens, *rRangeList);
+}
+
+ScChartListener::ScChartListener( const OUString& rName, ScDocument* pDocP, std::unique_ptr<vector<ScTokenRef>> pTokens ) :
+ SvtListener(),
+ mpTokens(std::move(pTokens)),
+ maName(rName),
+ mpDoc( pDocP ),
+ bUsed( false ),
+ bDirty( false )
+{
+}
+
+ScChartListener::~ScChartListener()
+{
+ if ( HasBroadcaster() )
+ EndListeningTo();
+ pUnoData.reset();
+
+ if (mpExtRefListener)
+ {
+ // Stop listening to all external files.
+ ScExternalRefManager* pRefMgr = mpDoc->GetExternalRefManager();
+ const std::unordered_set<sal_uInt16>& rFileIds = mpExtRefListener->getAllFileIds();
+ for (const auto& rFileId : rFileIds)
+ pRefMgr->removeLinkListener(rFileId, mpExtRefListener.get());
+ }
+}
+
+void ScChartListener::SetUno(
+ const uno::Reference< chart::XChartDataChangeEventListener >& rListener,
+ const uno::Reference< chart::XChartData >& rSource )
+{
+ pUnoData.reset( new ScChartUnoData( rListener, rSource ) );
+}
+
+uno::Reference< chart::XChartDataChangeEventListener > ScChartListener::GetUnoListener() const
+{
+ if ( pUnoData )
+ return pUnoData->GetListener();
+ return uno::Reference< chart::XChartDataChangeEventListener >();
+}
+
+uno::Reference< chart::XChartData > ScChartListener::GetUnoSource() const
+{
+ if ( pUnoData )
+ return pUnoData->GetSource();
+ return uno::Reference< chart::XChartData >();
+}
+
+void ScChartListener::Notify( const SfxHint& rHint )
+{
+ const ScHint* p = dynamic_cast<const ScHint*>(&rHint);
+ if (p && (p->GetId() == SfxHintId::ScDataChanged))
+ SetUpdateQueue();
+}
+
+void ScChartListener::Update()
+{
+ if ( mpDoc->IsInInterpreter() )
+ { // If interpreting do nothing and restart timer so we don't
+ // interfere with interpreter and don't produce an Err522 or similar.
+ // This may happen if we are rescheduled via Basic function.
+ mpDoc->GetChartListenerCollection()->StartTimer();
+ return ;
+ }
+ if ( pUnoData )
+ {
+ bDirty = false;
+ // recognize some day what has changed inside the Chart
+ chart::ChartDataChangeEvent aEvent( pUnoData->GetSource(),
+ chart::ChartDataChangeType_ALL,
+ 0, 0, 0, 0 );
+ pUnoData->GetListener()->chartDataChanged( aEvent );
+ }
+ else if ( mpDoc->GetAutoCalc() )
+ {
+ bDirty = false;
+ mpDoc->UpdateChart(GetName());
+ }
+}
+
+ScRangeListRef ScChartListener::GetRangeList() const
+{
+ ScRangeListRef aRLRef(new ScRangeList);
+ ScRefTokenHelper::getRangeListFromTokens(mpDoc, *aRLRef, *mpTokens, ScAddress());
+ return aRLRef;
+}
+
+void ScChartListener::SetRangeList( const ScRangeListRef& rNew )
+{
+ vector<ScTokenRef> aTokens;
+ ScRefTokenHelper::getTokensFromRangeList(mpDoc, aTokens, *rNew);
+ mpTokens->swap(aTokens);
+}
+
+namespace {
+
+class StartEndListening
+{
+public:
+ StartEndListening(ScDocument* pDoc, ScChartListener& rParent, bool bStart) :
+ mpDoc(pDoc), mrParent(rParent), mbStart(bStart) {}
+
+ void operator() (const ScTokenRef& pToken)
+ {
+ if (!ScRefTokenHelper::isRef(pToken))
+ return;
+
+ bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
+ if (bExternal)
+ {
+ sal_uInt16 nFileId = pToken->GetIndex();
+ ScExternalRefManager* pRefMgr = mpDoc->GetExternalRefManager();
+ ScChartListener::ExternalRefListener* pExtRefListener = mrParent.GetExtRefListener();
+ if (mbStart)
+ {
+ pRefMgr->addLinkListener(nFileId, pExtRefListener);
+ pExtRefListener->addFileId(nFileId);
+ }
+ else
+ {
+ pRefMgr->removeLinkListener(nFileId, pExtRefListener);
+ pExtRefListener->removeFileId(nFileId);
+ }
+ }
+ else
+ {
+ ScRange aRange;
+ ScRefTokenHelper::getRangeFromToken(mpDoc, aRange, pToken, ScAddress(), bExternal);
+ if (mbStart)
+ startListening(aRange);
+ else
+ endListening(aRange);
+ }
+ }
+private:
+ void startListening(const ScRange& rRange)
+ {
+ if (rRange.aStart == rRange.aEnd)
+ mpDoc->StartListeningCell(rRange.aStart, &mrParent);
+ else
+ mpDoc->StartListeningArea(rRange, false, &mrParent);
+ }
+
+ void endListening(const ScRange& rRange)
+ {
+ if (rRange.aStart == rRange.aEnd)
+ mpDoc->EndListeningCell(rRange.aStart, &mrParent);
+ else
+ mpDoc->EndListeningArea(rRange, false, &mrParent);
+ }
+private:
+ ScDocument* mpDoc;
+ ScChartListener& mrParent;
+ bool mbStart;
+};
+
+}
+
+void ScChartListener::StartListeningTo()
+{
+ if (!mpTokens || mpTokens->empty())
+ // no references to listen to.
+ return;
+
+ for_each(mpTokens->begin(), mpTokens->end(), StartEndListening(mpDoc, *this, true));
+}
+
+void ScChartListener::EndListeningTo()
+{
+ if (!mpTokens || mpTokens->empty())
+ // no references to listen to.
+ return;
+
+ for_each(mpTokens->begin(), mpTokens->end(), StartEndListening(mpDoc, *this, false));
+}
+
+void ScChartListener::ChangeListening( const ScRangeListRef& rRangeListRef,
+ bool bDirtyP )
+{
+ EndListeningTo();
+ SetRangeList( rRangeListRef );
+ StartListeningTo();
+ if ( bDirtyP )
+ SetDirty( true );
+}
+
+void ScChartListener::UpdateChartIntersecting( const ScRange& rRange )
+{
+ ScTokenRef pToken;
+ ScRefTokenHelper::getTokenFromRange(mpDoc, pToken, rRange);
+
+ if (ScRefTokenHelper::intersects(mpDoc, *mpTokens, pToken, ScAddress()))
+ {
+ // force update (chart has to be loaded), don't use ScChartListener::Update
+ mpDoc->UpdateChart(GetName());
+ }
+}
+
+ScChartListener::ExternalRefListener* ScChartListener::GetExtRefListener()
+{
+ if (!mpExtRefListener)
+ mpExtRefListener.reset(new ExternalRefListener(*this, mpDoc));
+
+ return mpExtRefListener.get();
+}
+
+void ScChartListener::SetUpdateQueue()
+{
+ bDirty = true;
+ mpDoc->GetChartListenerCollection()->StartTimer();
+}
+
+bool ScChartListener::operator==( const ScChartListener& r ) const
+{
+ bool b1 = (mpTokens && !mpTokens->empty());
+ bool b2 = (r.mpTokens && !r.mpTokens->empty());
+
+ if (mpDoc != r.mpDoc || bUsed != r.bUsed || bDirty != r.bDirty ||
+ GetName() != r.GetName() || b1 != b2)
+ return false;
+
+ if (!b1 && !b2)
+ // both token list instances are empty.
+ return true;
+
+ return *mpTokens == *r.mpTokens;
+}
+
+bool ScChartListener::operator!=( const ScChartListener& r ) const
+{
+ return !operator==(r);
+}
+
+ScChartHiddenRangeListener::ScChartHiddenRangeListener()
+{
+}
+
+ScChartHiddenRangeListener::~ScChartHiddenRangeListener()
+{
+ // empty d'tor
+}
+
+void ScChartListenerCollection::Init()
+{
+ aIdle.SetInvokeHandler( LINK( this, ScChartListenerCollection, TimerHdl ) );
+ aIdle.SetPriority( TaskPriority::REPAINT );
+ aIdle.SetDebugName( "sc::ScChartListenerCollection aIdle" );
+}
+
+ScChartListenerCollection::ScChartListenerCollection( ScDocument* pDocP ) :
+ meModifiedDuringUpdate( SC_CLCUPDATE_NONE ),
+ pDoc( pDocP )
+{
+ Init();
+}
+
+ScChartListenerCollection::ScChartListenerCollection(
+ const ScChartListenerCollection& rColl ) :
+ meModifiedDuringUpdate( SC_CLCUPDATE_NONE ),
+ pDoc( rColl.pDoc )
+{
+ Init();
+}
+
+ScChartListenerCollection::~ScChartListenerCollection()
+{
+ // remove ChartListener objects before aIdle dtor is called, because
+ // ScChartListener::EndListeningTo may cause ScChartListenerCollection::StartTimer
+ // to be called if an empty ScNoteCell is deleted
+
+ m_Listeners.clear();
+}
+
+void ScChartListenerCollection::StartAllListeners()
+{
+ for (auto const& it : m_Listeners)
+ {
+ it.second->StartListeningTo();
+ }
+}
+
+void ScChartListenerCollection::insert(ScChartListener* pListener)
+{
+ if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
+ meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
+ OUString aName = pListener->GetName();
+ m_Listeners.insert(std::make_pair(aName, std::unique_ptr<ScChartListener>(pListener)));
+}
+
+void ScChartListenerCollection::removeByName(const OUString& rName)
+{
+ if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
+ meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
+ m_Listeners.erase(rName);
+}
+
+ScChartListener* ScChartListenerCollection::findByName(const OUString& rName)
+{
+ ListenersType::iterator const it = m_Listeners.find(rName);
+ return it == m_Listeners.end() ? nullptr : it->second.get();
+}
+
+const ScChartListener* ScChartListenerCollection::findByName(const OUString& rName) const
+{
+ ListenersType::const_iterator const it = m_Listeners.find(rName);
+ return it == m_Listeners.end() ? nullptr : it->second.get();
+}
+
+bool ScChartListenerCollection::hasListeners() const
+{
+ return !m_Listeners.empty();
+}
+
+OUString ScChartListenerCollection::getUniqueName(const OUString& rPrefix) const
+{
+ for (sal_Int32 nNum = 1; nNum < 10000; ++nNum) // arbitrary limit to prevent infinite loop.
+ {
+ OUString aTestName = rPrefix + OUString::number(nNum);
+ if (m_Listeners.find(aTestName) == m_Listeners.end())
+ return aTestName;
+ }
+ return OUString();
+}
+
+void ScChartListenerCollection::ChangeListening( const OUString& rName,
+ const ScRangeListRef& rRangeListRef )
+{
+ ScChartListener* pCL = findByName(rName);
+ if (pCL)
+ {
+ pCL->EndListeningTo();
+ pCL->SetRangeList( rRangeListRef );
+ }
+ else
+ {
+ pCL = new ScChartListener(rName, pDoc, rRangeListRef);
+ insert(pCL);
+ }
+ pCL->StartListeningTo();
+}
+
+void ScChartListenerCollection::FreeUnused()
+{
+ if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
+ meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
+
+ ListenersType aUsed;
+
+ for (auto & pair : m_Listeners)
+ {
+ ScChartListener* p = pair.second.get();
+ if (p->IsUno())
+ {
+ // We don't delete UNO charts; they are to be deleted separately via FreeUno().
+ aUsed.insert(std::make_pair(pair.first, std::move(pair.second)));
+ continue;
+ }
+
+ if (p->IsUsed())
+ {
+ p->SetUsed(false);
+ aUsed.insert(std::make_pair(pair.first, std::move(pair.second)));
+ }
+ }
+
+ m_Listeners = std::move(aUsed);
+}
+
+void ScChartListenerCollection::FreeUno( const uno::Reference< chart::XChartDataChangeEventListener >& rListener,
+ const uno::Reference< chart::XChartData >& rSource )
+{
+ if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
+ meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
+
+ for (auto it = m_Listeners.begin(); it != m_Listeners.end(); )
+ {
+ ScChartListener *const p = it->second.get();
+ if (p->IsUno() && p->GetUnoListener() == rListener && p->GetUnoSource() == rSource)
+ it = m_Listeners.erase(it);
+ else
+ ++it;
+ }
+}
+
+void ScChartListenerCollection::StartTimer()
+{
+ aIdle.Start();
+}
+
+IMPL_LINK_NOARG(ScChartListenerCollection, TimerHdl, Timer *, void)
+{
+ if ( Application::AnyInput( VclInputFlags::KEYBOARD ) )
+ {
+ aIdle.Start();
+ return;
+ }
+ UpdateDirtyCharts();
+}
+
+void ScChartListenerCollection::UpdateDirtyCharts()
+{
+ // During ScChartListener::Update() the most nasty things can happen due to
+ // UNO listeners, e.g. reentrant calls via BASIC to insert() and FreeUno()
+ // and similar that modify m_Listeners and invalidate iterators.
+ meModifiedDuringUpdate = SC_CLCUPDATE_RUNNING;
+
+ for (auto const& it : m_Listeners)
+ {
+ ScChartListener *const p = it.second.get();
+ if (p->IsDirty())
+ p->Update();
+
+ if (meModifiedDuringUpdate == SC_CLCUPDATE_MODIFIED)
+ break; // iterator is invalid
+
+ if (aIdle.IsActive() && !pDoc->IsImportingXML())
+ break; // one interfered
+ }
+ meModifiedDuringUpdate = SC_CLCUPDATE_NONE;
+}
+
+void ScChartListenerCollection::SetDirty()
+{
+ for (auto const& it : m_Listeners)
+ {
+ it.second->SetDirty(true);
+ }
+
+ StartTimer();
+}
+
+void ScChartListenerCollection::SetDiffDirty(
+ const ScChartListenerCollection& rCmp, bool bSetChartRangeLists )
+{
+ bool bDirty = false;
+ for (auto const& it : m_Listeners)
+ {
+ ScChartListener *const pCL = it.second.get();
+ assert(pCL);
+ const ScChartListener* pCLCmp = rCmp.findByName(pCL->GetName());
+ if (!pCLCmp || *pCL != *pCLCmp)
+ {
+ if ( bSetChartRangeLists )
+ {
+ if (pCLCmp)
+ {
+ const ScRangeListRef& rList1 = pCL->GetRangeList();
+ const ScRangeListRef& rList2 = pCLCmp->GetRangeList();
+ bool b1 = rList1.is();
+ bool b2 = rList2.is();
+ if ( b1 != b2 || (b1 && b2 && (*rList1 != *rList2)) )
+ pDoc->SetChartRangeList( pCL->GetName(), rList1 );
+ }
+ else
+ pDoc->SetChartRangeList( pCL->GetName(), pCL->GetRangeList() );
+ }
+ bDirty = true;
+ pCL->SetDirty( true );
+ }
+ }
+ if ( bDirty )
+ StartTimer();
+}
+
+void ScChartListenerCollection::SetRangeDirty( const ScRange& rRange )
+{
+ bool bDirty = false;
+ for (auto const& it : m_Listeners)
+ {
+ ScChartListener *const pCL = it.second.get();
+ const ScRangeListRef& rList = pCL->GetRangeList();
+ if ( rList.is() && rList->Intersects( rRange ) )
+ {
+ bDirty = true;
+ pCL->SetDirty( true );
+ }
+ }
+ if ( bDirty )
+ StartTimer();
+
+ // New hidden range listener implementation
+ for (auto& [pListener, rHiddenRange] : maHiddenListeners)
+ {
+ if (rHiddenRange.Intersects(rRange))
+ {
+ pListener->notify();
+ }
+ }
+}
+
+void ScChartListenerCollection::UpdateChartsContainingTab( SCTAB nTab )
+{
+ ScRange aRange( 0, 0, nTab, pDoc->MaxCol(), pDoc->MaxRow(), nTab );
+ for (auto const& it : m_Listeners)
+ {
+ it.second->UpdateChartIntersecting(aRange);
+ }
+}
+
+bool ScChartListenerCollection::operator==( const ScChartListenerCollection& r ) const
+{
+ // Do not use ScStrCollection::operator==() here that uses IsEqual and Compare.
+ // Use ScChartListener::operator==() instead.
+ if (pDoc != r.pDoc)
+ return false;
+
+ return std::equal(m_Listeners.begin(), m_Listeners.end(), r.m_Listeners.begin(), r.m_Listeners.end(),
+ [](const ListenersType::value_type& lhs, const ListenersType::value_type& rhs) {
+ return (lhs.first == rhs.first) && (*lhs.second == *rhs.second);
+ });
+}
+
+void ScChartListenerCollection::StartListeningHiddenRange( const ScRange& rRange, ScChartHiddenRangeListener* pListener )
+{
+ maHiddenListeners.insert(std::make_pair<>(pListener, rRange));
+}
+
+void ScChartListenerCollection::EndListeningHiddenRange( ScChartHiddenRangeListener* pListener )
+{
+ auto range = maHiddenListeners.equal_range(pListener);
+ maHiddenListeners.erase(range.first, range.second);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */