sc/Library_sc.mk                   |    1 
 sc/inc/broadcast.hxx               |   86 +++++++++++++++++++
 sc/inc/calcmacros.hxx              |    4 
 sc/inc/column.hxx                  |    3 
 sc/inc/document.hxx                |    7 -
 sc/inc/grouparealistener.hxx       |    3 
 sc/inc/table.hxx                   |    3 
 sc/qa/unit/ucalc_formula.cxx       |   85 +++++++++++++------
 sc/source/core/data/bcaslot.cxx    |   59 ++++---------
 sc/source/core/data/broadcast.cxx  |  164 +++++++++++++++++++++++++++++++++++++
 sc/source/core/data/column4.cxx    |   31 ++++++
 sc/source/core/data/document.cxx   |    8 -
 sc/source/core/data/document10.cxx |   15 +++
 sc/source/core/data/table7.cxx     |    6 +
 sc/source/core/inc/bcaslot.hxx     |   15 +--
 15 files changed, 400 insertions(+), 90 deletions(-)

New commits:
commit 687b950702c49c90cff9a43655ea97a0343799a0
Author:     Kohei Yoshida <ko...@libreoffice.org>
AuthorDate: Tue Feb 21 22:16:30 2023 -0500
Commit:     Kohei Yoshida <ko...@libreoffice.org>
CommitDate: Thu Mar 2 23:35:56 2023 +0000

    Add a means to query the internal broadcaster state ...
    
    ... and use it in one unit test case.  Also, remove the code inside
    DEBUG_AREA_BROADCASTER macro since it no longer builds & has been
    superceded by this new state query method.
    
    Change-Id: I38691a76df5c63034ff488522936dd566bf8b4e3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148079
    Tested-by: Kohei Yoshida <ko...@libreoffice.org>
    Reviewed-by: Kohei Yoshida <ko...@libreoffice.org>

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 4d3c7398b43d..49c36cf27d3c 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -104,6 +104,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/core/data/attrib \
     sc/source/core/data/autonamecache \
     sc/source/core/data/bcaslot \
+    sc/source/core/data/broadcast \
     sc/source/core/data/bigrange \
     sc/source/core/data/celltextattr \
     sc/source/core/data/cellvalue \
diff --git a/sc/inc/broadcast.hxx b/sc/inc/broadcast.hxx
new file mode 100644
index 000000000000..b095f819acb7
--- /dev/null
+++ b/sc/inc/broadcast.hxx
@@ -0,0 +1,86 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "address.hxx"
+
+#include <map>
+#include <variant>
+#include <ostream>
+
+class ScFormulaCell;
+class SvtListener;
+
+namespace sc
+{
+class FormulaGroupAreaListener;
+
+struct BroadcasterState
+{
+    enum class CellListenerType
+    {
+        FormulaCell,
+        Generic,
+    };
+
+    enum class AreaListenerType
+    {
+        FormulaCell,
+        FormulaGroup,
+        Generic,
+    };
+
+    struct CellListener
+    {
+        using DataType = std::variant<const ScFormulaCell*, const 
SvtListener*>;
+
+        CellListenerType eType;
+        DataType pData;
+
+        CellListener(const ScFormulaCell* p);
+        CellListener(const SvtListener* p);
+    };
+
+    struct AreaListener
+    {
+        using DataType = std::variant<const ScFormulaCell*, const 
sc::FormulaGroupAreaListener*,
+                                      const SvtListener*>;
+
+        AreaListenerType eType;
+        DataType pData;
+
+        AreaListener(const ScFormulaCell* p);
+        AreaListener(const sc::FormulaGroupAreaListener* p);
+        AreaListener(const SvtListener* p);
+    };
+
+    std::map<ScAddress, std::vector<CellListener>> aCellListenerStore;
+    std::map<ScRange, std::vector<AreaListener>> aAreaListenerStore;
+
+    /**
+     * Check if a formula cell listens on a single cell.
+     */
+    bool hasFormulaCellListener(const ScAddress& rBroadcasterPos,
+                                const ScAddress& rFormulaPos) const;
+
+    /**
+     * Check if a formula cell listens on a single range.
+     */
+    bool hasFormulaCellListener(const ScRange& rBroadcasterRange,
+                                const ScAddress& rFormulaPos) const;
+
+    /**
+     * Dump all broadcaster state in YAML format.
+     */
+    void dump(std::ostream& rStrm, const ScDocument* pDoc = nullptr) const;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/calcmacros.hxx b/sc/inc/calcmacros.hxx
index c0c68d4487cf..ebe96654804a 100644
--- a/sc/inc/calcmacros.hxx
+++ b/sc/inc/calcmacros.hxx
@@ -12,7 +12,6 @@
 #define DEBUG_COLUMN_STORAGE 0
 #define DEBUG_PIVOT_TABLE 0
 #define DEBUG_FORMULA_COMPILER 0
-#define DEBUG_AREA_BROADCASTER 0
 
 #define DUMP_COLUMN_STORAGE 0
 #define DUMP_PIVOT_TABLE 0
@@ -26,8 +25,7 @@
 
 #if DUMP_PIVOT_TABLE || DEBUG_PIVOT_TABLE || \
     DUMP_COLUMN_STORAGE || DEBUG_COLUMN_STORAGE || \
-    DEBUG_FORMULA_COMPILER || \
-    DEBUG_AREA_BROADCASTER
+    DEBUG_FORMULA_COMPILER
 #include <iostream>
 using std::cout;
 using std::cerr;
diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx
index 949ca30dd137..5c42f02794ab 100644
--- a/sc/inc/column.hxx
+++ b/sc/inc/column.hxx
@@ -42,6 +42,7 @@ namespace formula { struct VectorRefArray; }
 
 namespace sc {
 
+struct BroadcasterState;
 struct FormulaGroupEntry;
 class StartListeningContext;
 class EndListeningContext;
@@ -864,6 +865,8 @@ private:
 
     void EndListeningGroup( sc::EndListeningContext& rCxt, SCROW nRow );
     void SetNeedsListeningGroup( SCROW nRow );
+
+    void CollectBroadcasterState(sc::BroadcasterState& rState) const;
 };
 
 inline bool ScColumn::IsEmptyAttr() const
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 092d9ba0d9a9..09fcc126ed06 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -77,6 +77,7 @@ namespace tools { class Guid; }
 
 namespace sc {
 
+struct BroadcasterState;
 struct FormulaGroupContext;
 class StartListeningContext;
 class EndListeningContext;
@@ -2368,6 +2369,8 @@ public:
 
     void EndListeningFormulaCells( std::vector<ScFormulaCell*>& rCells );
 
+    sc::BroadcasterState GetBroadcasterState() const;
+
     void                PutInFormulaTree( ScFormulaCell* pCell );
     void                RemoveFromFormulaTree( ScFormulaCell* pCell );
 
@@ -2611,10 +2614,6 @@ public:
     SC_DLLPUBLIC void DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const;
 #endif
 
-#if DEBUG_AREA_BROADCASTER
-    SC_DLLPUBLIC void DumpAreaBroadcasters() const;
-#endif
-
     SC_DLLPUBLIC void   SetCalcConfig( const ScCalcConfig& rConfig );
     const ScCalcConfig& GetCalcConfig() const { return maCalcConfig; }
     void                ConvertFormulaToValue( const ScRange& rRange, 
sc::TableValues* pUndo );
diff --git a/sc/inc/grouparealistener.hxx b/sc/inc/grouparealistener.hxx
index d823fc987627..a52dc2fb8a74 100644
--- a/sc/inc/grouparealistener.hxx
+++ b/sc/inc/grouparealistener.hxx
@@ -60,10 +60,11 @@ public:
     void collectFormulaCells( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW 
nRow2, std::vector<ScFormulaCell*>& rCells ) const;
     void collectFormulaCells( SCROW nRow1, SCROW nRow2, 
std::vector<ScFormulaCell*>& rCells ) const;
 
+    const ScFormulaCell* getTopCell() const;
+
 private:
     void notifyCellChange( const SfxHint& rHint, const ScAddress& rPos, SCROW 
nNumRows );
     void notifyBulkChange( const BulkDataHint& rHint );
-    const ScFormulaCell* getTopCell() const;
 };
 
 }
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index f81e3925964e..b5fa4e9ec334 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -57,6 +57,7 @@ namespace com::sun::star {
 namespace formula { struct VectorRefArray; }
 namespace sc {
 
+struct BroadcasterState;
 class StartListeningContext;
 class EndListeningContext;
 class CopyFromClipContext;
@@ -1156,6 +1157,8 @@ public:
 
     void CheckIntegrity() const;
 
+    void CollectBroadcasterState(sc::BroadcasterState& rState) const;
+
 private:
 
     void FillFormulaVertical(
diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx
index 059be9bbef3d..d6453cf0815d 100644
--- a/sc/qa/unit/ucalc_formula.cxx
+++ b/sc/qa/unit/ucalc_formula.cxx
@@ -25,6 +25,7 @@
 #include <externalrefmgr.hxx>
 #include <scmod.hxx>
 #include <undomanager.hxx>
+#include <broadcast.hxx>
 
 #include <formula/vectortoken.hxx>
 #include <svl/broadcast.hxx>
@@ -6235,66 +6236,98 @@ CPPUNIT_TEST_FIXTURE(TestFormula, 
testFormulaDepTracking)
 
     sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calculation.
 
+    const ScAddress aA5(0, 4, 0);
+    const ScAddress aB2(1, 1, 0);
+    const ScAddress aB5(1, 4, 0);
+    const ScAddress aC5(2, 4, 0);
+    const ScAddress aD2(3, 1, 0);
+    const ScAddress aD5(3, 4, 0);
+    const ScAddress aD6(3, 5, 0);
+    const ScAddress aE2(4, 1, 0);
+    const ScAddress aE3(4, 2, 0);
+    const ScAddress aE6(4, 5, 0);
+
     // B2 listens on D2.
-    m_pDoc->SetString(1, 1, 0, "=D2");
-    double val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetString(aB2, "=D2");
+    double val = m_pDoc->GetValue(aB2);
     ASSERT_DOUBLES_EQUAL_MESSAGE("Referencing an empty cell should yield 
zero.", 0.0, val);
 
+    {
+        // Check the internal broadcaster state.
+        auto aState = m_pDoc->GetBroadcasterState();
+        aState.dump(std::cout, m_pDoc);
+        CPPUNIT_ASSERT(aState.hasFormulaCellListener(aD2, aB2));
+    }
+
     // Changing the value of D2 should trigger recalculation of B2.
-    m_pDoc->SetValue(3, 1, 0, 1.1);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aD2, 1.1);
+    val = m_pDoc->GetValue(aB2);
     ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 
1.1, val);
 
     // And again.
-    m_pDoc->SetValue(3, 1, 0, 2.2);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aD2, 2.2);
+    val = m_pDoc->GetValue(aB2);
     ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on value change.", 
2.2, val);
 
     clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0));
 
+    {
+        // Make sure nobody is listening on anything.
+        auto aState = m_pDoc->GetBroadcasterState();
+        aState.dump(std::cout, m_pDoc);
+        CPPUNIT_ASSERT(aState.aCellListenerStore.empty());
+    }
+
     // Now, let's test the range dependency tracking.
 
     // B2 listens on D2:E6.
-    m_pDoc->SetString(1, 1, 0, "=SUM(D2:E6)");
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetString(aB2, "=SUM(D2:E6)");
+    val = m_pDoc->GetValue(aB2);
     ASSERT_DOUBLES_EQUAL_MESSAGE("Summing an empty range should yield zero.", 
0.0, val);
 
+    {
+        // Check the internal state to make sure it matches.
+        auto aState = m_pDoc->GetBroadcasterState();
+        aState.dump(std::cout, m_pDoc);
+        CPPUNIT_ASSERT(aState.hasFormulaCellListener({aD2, aE6}, aB2));
+    }
+
     // Set value to E3. This should trigger recalc on B2.
-    m_pDoc->SetValue(4, 2, 0, 2.4);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aE3, 2.4);
+    val = m_pDoc->GetValue(aB2);
     ASSERT_DOUBLES_EQUAL_MESSAGE("Failed to recalculate on single value 
change.", 2.4, val);
 
     // Set value to D5 to trigger recalc again.  Note that this causes an
     // addition of 1.2 + 2.4 which is subject to binary floating point
     // rounding error.  We need to use approxEqual to assess its value.
 
-    m_pDoc->SetValue(3, 4, 0, 1.2);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aD5, 1.2);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 3.6));
 
     // Change the value of D2 (boundary case).
-    m_pDoc->SetValue(3, 1, 0, 1.0);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aD2, 1.0);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 4.6));
 
     // Change the value of E6 (another boundary case).
-    m_pDoc->SetValue(4, 5, 0, 2.0);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aE6, 2.0);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 6.6));
 
     // Change the value of D6 (another boundary case).
-    m_pDoc->SetValue(3, 5, 0, 3.0);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aD6, 3.0);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 9.6));
 
     // Change the value of E2 (another boundary case).
-    m_pDoc->SetValue(4, 1, 0, 0.4);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aE2, 0.4);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 10.0));
 
     // Change the existing non-empty value cell (E2).
-    m_pDoc->SetValue(4, 1, 0, 2.4);
-    val = m_pDoc->GetValue(1, 1, 0);
+    m_pDoc->SetValue(aE2, 2.4);
+    val = m_pDoc->GetValue(aB2);
     CPPUNIT_ASSERT_MESSAGE("Failed to recalculate on single value change.", 
rtl::math::approxEqual(val, 12.0));
 
     clearRange(m_pDoc, ScRange(0, 0, 0, 10, 10, 0));
@@ -6328,10 +6361,10 @@ CPPUNIT_TEST_FIXTURE(TestFormula, 
testFormulaDepTracking)
 
     // Intentionally insert a formula in column 1. This will break column 1's
     // uniformity of consisting only of static value cells.
-    m_pDoc->SetString(0, 4, 0, "=R2C3");
-    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, 
m_pDoc->GetValue(0, 4, 0));
-    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, 
m_pDoc->GetValue(1, 4, 0));
-    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 4.0, 
m_pDoc->GetValue(2, 4, 0));
+    m_pDoc->SetString(aA5, "=R2C3");
+    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, 
m_pDoc->GetValue(aA5));
+    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 2.0, 
m_pDoc->GetValue(aB5));
+    ASSERT_DOUBLES_EQUAL_MESSAGE("Unexpected formula value.", 4.0, 
m_pDoc->GetValue(aC5));
 
     m_pDoc->DeleteTab(0);
 }
diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx
index a39d5b4812b4..910eb46082e6 100644
--- a/sc/source/core/data/bcaslot.cxx
+++ b/sc/source/core/data/bcaslot.cxx
@@ -29,11 +29,9 @@
 #include <refupdat.hxx>
 #include <bulkdatahint.hxx>
 #include <columnspanset.hxx>
-
-#if DEBUG_AREA_BROADCASTER
 #include <formulacell.hxx>
 #include <grouparealistener.hxx>
-#endif
+#include <broadcast.hxx>
 
 ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) :
     pUpdateChainNext(nullptr),
@@ -500,46 +498,32 @@ void ScBroadcastAreaSlot::GetAllListeners(
     }
 }
 
-#if DEBUG_AREA_BROADCASTER
-void ScBroadcastAreaSlot::Dump() const
+void ScBroadcastAreaSlot::CollectBroadcasterState(sc::BroadcasterState& 
rState) const
 {
     for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl)
     {
-        const ScBroadcastArea* pArea = rEntry.mpArea;
-        const SvtBroadcaster& rBC = pArea->GetBroadcaster();
-        const SvtBroadcaster::ListenersType& rListeners = 
rBC.GetAllListeners();
-        size_t n = rListeners.size();
+        const ScRange& rRange = rEntry.mpArea->GetRange();
+        auto aRes = rState.aAreaListenerStore.try_emplace(rRange);
+        auto& rLisStore = aRes.first->second;
 
-        cout << "  * range: " << 
OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D,
 pDoc), RTL_TEXTENCODING_UTF8).getStr()
-            << ", group: " << pArea->IsGroupListening()
-            << ", listener count: " << n << endl;
-
-        for (size_t i = 0; i < n; ++i)
+        for (const SvtListener* pLis : 
rEntry.mpArea->GetBroadcaster().GetAllListeners())
         {
-            const ScFormulaCell* pFC = dynamic_cast<const 
ScFormulaCell*>(rListeners[i]);
-            if (pFC)
+            if (auto pFC = dynamic_cast<const ScFormulaCell*>(pLis); pFC)
             {
-                cout << "    * listener: formula cell: "
-                     << 
OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), 
RTL_TEXTENCODING_UTF8).getStr()
-                     << endl;
+                rLisStore.emplace_back(pFC);
                 continue;
             }
 
-            const sc::FormulaGroupAreaListener* pFGListener = 
dynamic_cast<const sc::FormulaGroupAreaListener*>(rListeners[i]);
-            if (pFGListener)
+            if (auto pFGL = dynamic_cast<const 
sc::FormulaGroupAreaListener*>(pLis); pFGL)
             {
-                cout << "    * listener: formula group: (pos: "
-                     << 
OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | 
ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
-                     << ", length: " << pFGListener->getGroupLength()
-                     << ")" << endl;
+                rLisStore.emplace_back(pFGL);
                 continue;
             }
 
-            cout << "    * listener: unknown" << endl;
+            rLisStore.emplace_back(pLis);
         }
     }
 }
-#endif
 
 void ScBroadcastAreaSlot::FinallyEraseAreas()
 {
@@ -1283,27 +1267,20 @@ std::vector<sc::AreaListener> 
ScBroadcastAreaSlotMachine::GetAllListeners(
     return aRet;
 }
 
-#if DEBUG_AREA_BROADCASTER
-void ScBroadcastAreaSlotMachine::Dump() const
+void ScBroadcastAreaSlotMachine::CollectBroadcasterState(sc::BroadcasterState& 
rState) const
 {
-    cout << "slot distribution count: " << nBcaSlots << endl;
-    for (const auto& [rIndex, pTabSlots] : aTableSlotsMap)
+    for (const auto& [rTab, rTabSlots] : aTableSlotsMap)
     {
-        cout << "-- sheet (index: " << rIndex << ")" << endl;
+        (void)rTab;
 
-        assert(pTabSlots);
-        ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots();
-        for (SCSIZE i = 0; i < nBcaSlots; ++i)
+        ScBroadcastAreaSlot** pp = rTabSlots.getSlots();
+        for (SCSIZE i = 0; i < mnBcaSlots; ++i)
         {
-            const ScBroadcastAreaSlot* pSlot = ppSlots[i];
+            const ScBroadcastAreaSlot* pSlot = pp[i];
             if (pSlot)
-            {
-                cout << "* slot " << i << endl;
-                pSlot->Dump();
-            }
+                pSlot->CollectBroadcasterState(rState);
         }
     }
 }
-#endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/broadcast.cxx 
b/sc/source/core/data/broadcast.cxx
new file mode 100644
index 000000000000..c0cd0c29edca
--- /dev/null
+++ b/sc/source/core/data/broadcast.cxx
@@ -0,0 +1,164 @@
+/* -*- 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/.
+ */
+
+#include <broadcast.hxx>
+#include <address.hxx>
+#include <formulacell.hxx>
+
+namespace sc
+{
+BroadcasterState::CellListener::CellListener(const ScFormulaCell* p)
+    : eType(CellListenerType::FormulaCell)
+    , pData(p)
+{
+}
+
+BroadcasterState::CellListener::CellListener(const SvtListener* p)
+    : eType(CellListenerType::Generic)
+    , pData(p)
+{
+}
+
+BroadcasterState::AreaListener::AreaListener(const ScFormulaCell* p)
+    : eType(AreaListenerType::FormulaCell)
+    , pData(p)
+{
+}
+
+BroadcasterState::AreaListener::AreaListener(const 
sc::FormulaGroupAreaListener* p)
+    : eType(AreaListenerType::FormulaGroup)
+    , pData(p)
+{
+}
+
+BroadcasterState::AreaListener::AreaListener(const SvtListener* p)
+    : eType(AreaListenerType::Generic)
+    , pData(p)
+{
+}
+
+bool BroadcasterState::hasFormulaCellListener(const ScAddress& rBroadcasterPos,
+                                              const ScAddress& rFormulaPos) 
const
+{
+    auto it = aCellListenerStore.find(rBroadcasterPos);
+    if (it == aCellListenerStore.end())
+        return false;
+
+    for (const auto& rLis : it->second)
+    {
+        if (rLis.eType == CellListenerType::FormulaCell)
+        {
+            auto pFC = std::get<const ScFormulaCell*>(rLis.pData);
+            if (pFC->aPos == rFormulaPos)
+                return true;
+        }
+    }
+
+    return false;
+}
+
+bool BroadcasterState::hasFormulaCellListener(const ScRange& rBroadcasterRange,
+                                              const ScAddress& rFormulaPos) 
const
+{
+    auto it = aAreaListenerStore.find(rBroadcasterRange);
+    if (it == aAreaListenerStore.end())
+        return false;
+
+    for (const auto& rLis : it->second)
+    {
+        if (rLis.eType == AreaListenerType::FormulaCell)
+        {
+            auto pFC = std::get<const ScFormulaCell*>(rLis.pData);
+            if (pFC->aPos == rFormulaPos)
+                return true;
+        }
+    }
+
+    return false;
+}
+
+void BroadcasterState::dump(std::ostream& rStrm, const ScDocument* pDoc) const
+{
+    constexpr ScRefFlags nPosFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D;
+
+    rStrm << "---" << std::endl;
+
+    for (const auto & [ rPos, rListeners ] : aCellListenerStore)
+    {
+        rStrm << "- type: cell-broadcaster\n";
+        rStrm << "  position: " << rPos.Format(nPosFlags, pDoc) << std::endl;
+
+        if (!rListeners.empty())
+            rStrm << "  listeners:\n";
+
+        for (const auto& rLis : rListeners)
+        {
+            switch (rLis.eType)
+            {
+                case BroadcasterState::CellListenerType::FormulaCell:
+                {
+                    auto* pFC = std::get<const ScFormulaCell*>(rLis.pData);
+                    rStrm << "  - type: formula-cell\n";
+                    rStrm << "    position: " << pFC->aPos.Format(nPosFlags, 
pDoc) << std::endl;
+                    break;
+                }
+                case BroadcasterState::CellListenerType::Generic:
+                {
+                    rStrm << "  - type: unknown" << std::endl;
+                    break;
+                }
+            }
+        }
+    }
+
+    for (const auto & [ rRange, rListeners ] : aAreaListenerStore)
+    {
+        rStrm << "- type: area-broadcaster\n";
+        rStrm << "  range: " << rRange.Format(*pDoc, nPosFlags) << std::endl;
+
+        if (!rListeners.empty())
+            rStrm << "  listeners:\n";
+
+        for (const auto& rLis : rListeners)
+        {
+            switch (rLis.eType)
+            {
+                case BroadcasterState::AreaListenerType::FormulaCell:
+                {
+                    auto* pFC = std::get<const ScFormulaCell*>(rLis.pData);
+                    rStrm << "  - type: formula-cell\n";
+                    rStrm << "    position: " << pFC->aPos.Format(nPosFlags, 
pDoc) << std::endl;
+                    break;
+                }
+                case BroadcasterState::AreaListenerType::FormulaGroup:
+                {
+                    auto* pFGL = std::get<const 
sc::FormulaGroupAreaListener*>(rLis.pData);
+
+                    auto pTopCell = pFGL->getTopCell();
+                    if (auto xFG = pTopCell->GetCellGroup(); xFG)
+                    {
+                        ScRange aGR(pTopCell->aPos);
+                        aGR.aEnd.IncRow(xFG->mnLength - 1);
+                        rStrm << "  - type: formula-group\n";
+                        rStrm << "    range: " << aGR.Format(*pDoc, nPosFlags) 
<< std::endl;
+                    }
+                    break;
+                }
+                case BroadcasterState::AreaListenerType::Generic:
+                {
+                    rStrm << "  - type: unknown" << std::endl;
+                    break;
+                }
+            }
+        }
+    }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx
index 49420642bf5f..a9b1b91857f8 100644
--- a/sc/source/core/data/column4.cxx
+++ b/sc/source/core/data/column4.cxx
@@ -31,6 +31,7 @@
 #include <recursionhelper.hxx>
 #include <docsh.hxx>
 #include <editutil.hxx>
+#include <broadcast.hxx>
 
 #include <SparklineGroup.hxx>
 
@@ -2233,4 +2234,34 @@ void ScColumn::CheckIntegrity() const
     }
 }
 
+void ScColumn::CollectBroadcasterState(sc::BroadcasterState& rState) const
+{
+    for (const auto& block : maBroadcasters)
+    {
+        if (block.type != sc::element_type_broadcaster)
+            continue;
+
+        auto itBeg = sc::broadcaster_block::begin(*block.data);
+        auto itEnd = sc::broadcaster_block::end(*block.data);
+
+        for (auto it = itBeg; it != itEnd; ++it)
+        {
+            ScAddress aBCPos(nCol, block.position + std::distance(itBeg, it), 
nTab);
+
+            auto aRes = rState.aCellListenerStore.try_emplace(aBCPos);
+            auto& rLisStore = aRes.first->second;
+
+            const SvtBroadcaster& rBC = **it;
+            for (const SvtListener* pLis : rBC.GetAllListeners())
+            {
+                const auto* pFC = dynamic_cast<const ScFormulaCell*>(pLis);
+                if (pFC)
+                    rLisStore.emplace_back(pFC);
+                else
+                    rLisStore.emplace_back(pLis);
+            }
+        }
+    }
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
index e204acd3cd24..b53de471e776 100644
--- a/sc/source/core/data/document.cxx
+++ b/sc/source/core/data/document.cxx
@@ -2529,14 +2529,6 @@ void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL 
nCol ) const
 }
 #endif
 
-#if DEBUG_AREA_BROADCASTER
-void ScDocument::DumpAreaBroadcasters() const
-{
-    if (pBASM)
-        pBASM->Dump();
-}
-#endif
-
 bool ScDocument::TableExists( SCTAB nTab ) const
 {
     return ValidTab(nTab) && o3tl::make_unsigned(nTab) < maTabs.size() && 
maTabs[nTab];
diff --git a/sc/source/core/data/document10.cxx 
b/sc/source/core/data/document10.cxx
index e0eb8d46bd28..baee3603fd1f 100644
--- a/sc/source/core/data/document10.cxx
+++ b/sc/source/core/data/document10.cxx
@@ -26,6 +26,8 @@
 #include <scitems.hxx>
 #include <datamapper.hxx>
 #include <docsh.hxx>
+#include <bcaslot.hxx>
+#include <broadcast.hxx>
 
 // Add totally brand-new methods to this source file.
 
@@ -1101,4 +1103,17 @@ void ScDocument::CheckIntegrity( SCTAB nTab ) const
     pTab->CheckIntegrity();
 }
 
+sc::BroadcasterState ScDocument::GetBroadcasterState() const
+{
+    sc::BroadcasterState aState;
+
+    for (const auto& xTab : maTabs)
+        xTab->CollectBroadcasterState(aState);
+
+    if (pBASM)
+        pBASM->CollectBroadcasterState(aState);
+
+    return aState;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx
index 9af01cba748e..7755bc6176de 100644
--- a/sc/source/core/data/table7.cxx
+++ b/sc/source/core/data/table7.cxx
@@ -649,4 +649,10 @@ void ScTable::CheckIntegrity() const
         pCol->CheckIntegrity();
 }
 
+void ScTable::CollectBroadcasterState(sc::BroadcasterState& rState) const
+{
+    for (const auto& pCol : aCol)
+        pCol->CollectBroadcasterState(rState);
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/bcaslot.hxx b/sc/source/core/inc/bcaslot.hxx
index 4e1173596840..6e891e8aab5a 100644
--- a/sc/source/core/inc/bcaslot.hxx
+++ b/sc/source/core/inc/bcaslot.hxx
@@ -30,7 +30,12 @@
 #include <document.hxx>
 #include <global.hxx>
 
-namespace sc { class ColumnSpanSet; }
+namespace sc {
+
+struct BroadcasterState;
+class ColumnSpanSet;
+
+}
 class ScHint;
 
 namespace sc {
@@ -233,9 +238,7 @@ public:
         const ScRange& rRange, std::vector<sc::AreaListener>& rListeners,
         sc::AreaOverlapType eType, sc::ListenerGroupType eGroup );
 
-#if DEBUG_AREA_BROADCASTER
-    void Dump() const;
-#endif
+    void CollectBroadcasterState(sc::BroadcasterState& rState) const;
 };
 
 /**
@@ -364,9 +367,7 @@ public:
         const ScRange& rRange, sc::AreaOverlapType eType,
         sc::ListenerGroupType eGroup = sc::ListenerGroupType::Both );
 
-#if DEBUG_AREA_BROADCASTER
-    void Dump() const;
-#endif
+    void CollectBroadcasterState(sc::BroadcasterState& rState) const;
 };
 
 class ScBulkBroadcast

Reply via email to