[Libreoffice-commits] core.git: Branch 'feature/perfwork5' - sc/inc sc/Library_sc.mk sc/source

Kohei Yoshida kohei.yoshida at collabora.com
Fri Nov 14 13:47:19 PST 2014


 sc/Library_sc.mk                          |    2 
 sc/inc/bulkdatahint.hxx                   |   43 ++++
 sc/inc/document.hxx                       |    7 
 sc/inc/formulacell.hxx                    |   12 +
 sc/inc/grouparealistener.hxx              |   73 ++++++
 sc/inc/simplehintids.hxx                  |    9 
 sc/source/core/data/bcaslot.cxx           |  116 +++++++++-
 sc/source/core/data/colorscale.cxx        |    2 
 sc/source/core/data/documen2.cxx          |    4 
 sc/source/core/data/documen7.cxx          |   17 -
 sc/source/core/data/formulacell.cxx       |   52 ++++
 sc/source/core/data/table3.cxx            |    8 
 sc/source/core/inc/bcaslot.hxx            |   57 +++--
 sc/source/core/tool/bulkdatahint.cxx      |   49 ++++
 sc/source/core/tool/chartlis.cxx          |    4 
 sc/source/core/tool/grouparealistener.cxx |  321 ++++++++++++++++++++++++++++++
 sc/source/core/tool/sharedformula.cxx     |   78 +++++++
 sc/source/ui/docshell/macromgr.cxx        |    2 
 sc/source/ui/docshell/servobj.cxx         |    6 
 sc/source/ui/unoobj/cellsuno.cxx          |    4 
 sc/source/ui/unoobj/chart2uno.cxx         |    4 
 21 files changed, 787 insertions(+), 83 deletions(-)

New commits:
commit 12899ce686d302352645605f6e262784df77f0b2
Author: Kohei Yoshida <kohei.yoshida at collabora.com>
Date:   Wed Nov 12 22:18:49 2014 -0500

    Dedicated listener type tailored for formula groups.
    
    Right now, it's only used when loading an xlsx file. But eventually
    this one should be used everywhere.
    
    Change-Id: I216c3a9a33c4b8040e8284d59299e0637471fb50

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 3648720..4404263 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -197,6 +197,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/core/tool/adiasync \
     sc/source/core/tool/appoptio \
     sc/source/core/tool/autoform \
+    sc/source/core/tool/bulkdatahint \
     sc/source/core/tool/brdcst \
     sc/source/core/tool/calcconfig \
     sc/source/core/tool/callform \
@@ -227,6 +228,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/core/tool/formulaopt \
     sc/source/core/tool/formulaparserpool \
     sc/source/core/tool/formularesult \
+    sc/source/core/tool/grouparealistener \
     sc/source/core/tool/hints \
     sc/source/core/tool/inputopt \
     sc/source/core/tool/interpr1 \
diff --git a/sc/inc/bulkdatahint.hxx b/sc/inc/bulkdatahint.hxx
new file mode 100644
index 0000000..31c13fb
--- /dev/null
+++ b/sc/inc/bulkdatahint.hxx
@@ -0,0 +1,43 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_SC_BULKDATAHINT_HXX
+#define INCLUDED_SC_BULKDATAHINT_HXX
+
+#include <simplehintids.hxx>
+
+class ScDocument;
+
+namespace sc {
+
+class ColumnSpanSet;
+
+class BulkDataHint : public SfxSimpleHint
+{
+    struct Impl;
+    Impl* mpImpl;
+
+    BulkDataHint( const BulkDataHint& );
+    BulkDataHint& operator= ( const BulkDataHint& );
+
+public:
+    BulkDataHint( ScDocument& rDoc, const ColumnSpanSet* pSpans );
+    virtual ~BulkDataHint();
+
+    void setSpans( const ColumnSpanSet* pSpans );
+    const ColumnSpanSet* getSpans() const;
+
+    ScDocument& getDoc();
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 9f6ab2c..72b49a8 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -1852,10 +1852,9 @@ private:
     static ScRecursionHelper*   CreateRecursionHelperInstance();
 
 public:
-    void                StartListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener );
-    void                EndListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener );
+    void StartListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
+
+    void EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
                         /** Broadcast wrapper, calls
                             rHint.GetCell()->Broadcast() and AreaBroadcast()
                             and TrackFormulas() and conditional format list
diff --git a/sc/inc/formulacell.hxx b/sc/inc/formulacell.hxx
index b65504e..67381a0 100644
--- a/sc/inc/formulacell.hxx
+++ b/sc/inc/formulacell.hxx
@@ -23,6 +23,7 @@
 #include <set>
 
 #include <boost/noncopyable.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
 
 #include <formula/tokenarray.hxx>
 #include <osl/conditn.hxx>
@@ -47,16 +48,18 @@ struct RefUpdateInsertTabContext;
 struct RefUpdateDeleteTabContext;
 struct RefUpdateMoveTabContext;
 class CompileFormulaContext;
+class FormulaGroupAreaListener;
 
 }
 
 class ScFormulaCell;
 class ScProgress;
 class ScTokenArray;
-struct ScSimilarFormulaDelta;
 
 struct SC_DLLPUBLIC ScFormulaCellGroup : boost::noncopyable
 {
+    typedef boost::ptr_vector<sc::FormulaGroupAreaListener> AreaListenersType;
+
     mutable size_t mnRefCount;
 
     ScTokenArray* mpCode;
@@ -70,6 +73,8 @@ struct SC_DLLPUBLIC ScFormulaCellGroup : boost::noncopyable
     sal_uInt8 meCalcState;
     sal_uInt8 meKernelState;
 
+    AreaListenersType maAreaListeners;
+
     ScFormulaCellGroup();
     ~ScFormulaCellGroup();
 
@@ -81,6 +86,11 @@ struct SC_DLLPUBLIC ScFormulaCellGroup : boost::noncopyable
         ScDocument& rDoc, const ScAddress& rPos, formula::FormulaGrammar::Grammar eGram );
     void compileOpenCLKernel();
 
+    sc::FormulaGroupAreaListener* getAreaListener(
+        ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed );
+
+    void endAllGroupListening( ScDocument& rDoc );
+
 #if ENABLE_THREADED_OPENCL_KERNEL_COMPILATION
     static int snCount;
     static rtl::Reference<sc::CLBuildKernelThread> sxCompilationThread;
diff --git a/sc/inc/grouparealistener.hxx b/sc/inc/grouparealistener.hxx
new file mode 100644
index 0000000..6301023
--- /dev/null
+++ b/sc/inc/grouparealistener.hxx
@@ -0,0 +1,73 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_SC_GROUPAREALISTENER_HXX
+#define INCLUDED_SC_GROUPAREALISTENER_HXX
+
+#include <address.hxx>
+
+#include <svl/listener.hxx>
+
+class ScFormulaCell;
+
+namespace sc {
+
+class BulkDataHint;
+
+class FormulaGroupAreaListener : public SvtListener
+{
+    ScRange maRange;
+    ScFormulaCell** mppTopCell;
+    SCROW mnGroupLen;
+    bool mbStartFixed;
+    bool mbEndFixed;
+
+    FormulaGroupAreaListener(); // disabled
+
+public:
+    FormulaGroupAreaListener(
+        const ScRange& rRange, ScFormulaCell** ppTopCell, SCROW nGroupLen, bool bStartFixed, bool bEndFixed );
+
+    ScRange getListeningRange() const;
+
+    virtual void Notify( const SfxHint& rHint ) SAL_OVERRIDE;
+
+    /**
+     * Given the position of a changed cell, collect all formula cells that
+     * need to be notified of the change.
+     *
+     * @param rPos position of changed cell.
+     * @param rCells all formula cells that need to be notified are put into
+     *               this container.
+     */
+    void collectFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells ) const;
+
+    /**
+     * Given the row span of changed cells within a single column, collect all
+     * formula cells that need to be notified of the change.
+     *
+     * @param nTab sheet position of the changed cell span.
+     * @param nCol column position of the changed cell span.
+     * @param nRow1 top row position of the changed cell span.
+     * @param nRow2 bottom row position of the changed cell span.
+     * @param rCells all formula cells that need to be notified are put into
+     *               this container.
+     */
+    void collectFormulaCells( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const;
+
+private:
+    void notifyCellChange( const SfxHint& rHint, const ScAddress& rPos );
+    void notifyBulkChange( const BulkDataHint& rHint );
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/inc/simplehintids.hxx b/sc/inc/simplehintids.hxx
index 34900c0..64d2b08 100644
--- a/sc/inc/simplehintids.hxx
+++ b/sc/inc/simplehintids.hxx
@@ -12,10 +12,11 @@
 
 #include <svl/smplhint.hxx>
 
-#define SC_HINT_DATACHANGED     SFX_HINT_DATACHANGED
-#define SC_HINT_TABLEOPDIRTY    SFX_HINT_USER00
-#define SC_HINT_CALCALL         SFX_HINT_USER01
-#define SC_HINT_REFERENCE       SFX_HINT_USER02
+#define SC_HINT_DATACHANGED         SFX_HINT_DATACHANGED
+#define SC_HINT_TABLEOPDIRTY        SFX_HINT_USER00
+#define SC_HINT_CALCALL             SFX_HINT_USER01
+#define SC_HINT_REFERENCE           SFX_HINT_USER02
+#define SC_HINT_BULK_DATACHANGED    SFX_HINT_USER03
 
 #endif
 
diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx
index 8229d68..9af7c19 100644
--- a/sc/source/core/data/bcaslot.cxx
+++ b/sc/source/core/data/bcaslot.cxx
@@ -27,6 +27,7 @@
 #include "docoptio.hxx"
 #include "refupdat.hxx"
 #include "table.hxx"
+#include <bulkdatahint.hxx>
 
 #if DEBUG_AREA_BROADCASTER
 #include <formulacell.hxx>
@@ -107,6 +108,13 @@ static SCSIZE nBcaSlots = initSlotDistribution( aSlotDistribution, nBcaSlotsRow)
 // Ensure that all static variables are initialized with this one call.
 #endif
 
+ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) :
+    pUpdateChainNext(NULL),
+    aRange(rRange),
+    nRefCount(0),
+    mbInUpdateChain(false),
+    mbGroupListening(false) {}
+
 ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument,
         ScBroadcastAreaSlotMachine* pBASMa ) :
     aTmpSeekBroadcastArea( ScRange()),
@@ -155,8 +163,8 @@ bool ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const
     return false;
 }
 
-bool ScBroadcastAreaSlot::StartListeningArea( const ScRange& rRange,
-        SvtListener* pListener, ScBroadcastArea*& rpArea )
+bool ScBroadcastAreaSlot::StartListeningArea(
+    const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
 {
     bool bNewArea = false;
     OSL_ENSURE(pListener, "StartListeningArea: pListener Null");
@@ -168,12 +176,13 @@ bool ScBroadcastAreaSlot::StartListeningArea( const ScRange& rRange,
         // to new and insert it would save an attempt to find it, on mass
         // operations like identical large [HV]LOOKUP() areas the new/delete
         // would add quite some penalty for all but the first formula cell.
-        ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange));
+        ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
         if (aIter != aBroadcastAreaTbl.end())
             rpArea = (*aIter).mpArea;
         else
         {
             rpArea = new ScBroadcastArea( rRange);
+            rpArea->SetGroupListening(bGroupListening);
             if (aBroadcastAreaTbl.insert( rpArea).second)
             {
                 rpArea->IncRef();
@@ -208,13 +217,13 @@ void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea )
 
 // If rpArea != NULL then no listeners are stopped, only the area is removed
 // and the reference count decremented.
-void ScBroadcastAreaSlot::EndListeningArea( const ScRange& rRange,
-        SvtListener* pListener, ScBroadcastArea*& rpArea )
+void ScBroadcastAreaSlot::EndListeningArea(
+    const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
 {
     OSL_ENSURE(pListener, "EndListeningArea: pListener Null");
     if ( !rpArea )
     {
-        ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange));
+        ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
         if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
             return;
         rpArea = (*aIter).mpArea;
@@ -230,7 +239,7 @@ void ScBroadcastAreaSlot::EndListeningArea( const ScRange& rRange,
     {
         if (rpArea && !rpArea->GetBroadcaster().HasListeners())
         {
-            ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange));
+            ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
             if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
                 return;
             OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch");
@@ -242,9 +251,10 @@ void ScBroadcastAreaSlot::EndListeningArea( const ScRange& rRange,
 }
 
 ScBroadcastAreas::const_iterator ScBroadcastAreaSlot::FindBroadcastArea(
-        const ScRange& rRange ) const
+        const ScRange& rRange, bool bGroupListening ) const
 {
     aTmpSeekBroadcastArea.UpdateRange( rRange);
+    aTmpSeekBroadcastArea.SetGroupListening(bGroupListening);
     return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea);
 }
 
@@ -270,7 +280,19 @@ bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint)
         const ScRange& rAreaRange = pArea->GetRange();
         if (rAreaRange.In( rAddress))
         {
-            if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+            if (pArea->IsGroupListening())
+            {
+                if (pBASM->IsInBulkBroadcast())
+                {
+                    pBASM->InsertBulkGroupArea(pArea, rAddress);
+                }
+                else
+                {
+                    pArea->GetBroadcaster().Broadcast( rHint);
+                    bIsBroadcasted = true;
+                }
+            }
+            else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
             {
                 pArea->GetBroadcaster().Broadcast( rHint);
                 bIsBroadcasted = true;
@@ -296,27 +318,46 @@ bool ScBroadcastAreaSlot::AreaBroadcastInRange( const ScRange& rRange,
     bool bInBroadcast = mbInBroadcastIteration;
     mbInBroadcastIteration = true;
     bool bIsBroadcasted = false;
+
+    mbHasErasedArea = false;
+
     for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
             aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
     {
-        if (isMarkedErased( aIter))
+        if (mbHasErasedArea && isMarkedErased( aIter))
             continue;
+
         ScBroadcastArea* pArea = (*aIter).mpArea;
         const ScRange& rAreaRange = pArea->GetRange();
         if (rAreaRange.Intersects( rRange ))
         {
-            if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+            if (pArea->IsGroupListening())
+            {
+                if (pBASM->IsInBulkBroadcast())
+                {
+                    pBASM->InsertBulkGroupArea(pArea, rRange);
+                }
+                else
+                {
+                    pArea->GetBroadcaster().Broadcast( rHint);
+                    bIsBroadcasted = true;
+                }
+            }
+            else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
             {
                 pArea->GetBroadcaster().Broadcast( rHint);
                 bIsBroadcasted = true;
             }
         }
     }
+
     mbInBroadcastIteration = bInBroadcast;
+
     // A Notify() during broadcast may call EndListeningArea() and thus dispose
     // an area if it was the last listener, which would invalidate an iterator
     // pointing to it, hence the real erase is done afterwards.
     FinallyEraseAreas();
+
     return bIsBroadcasted;
 }
 
@@ -487,6 +528,7 @@ void ScBroadcastAreaSlot::GetAllListeners(
         {
             sc::AreaListener aEntry;
             aEntry.maArea = rAreaRange;
+            aEntry.mbGroupListening = pArea->IsGroupListening();
             aEntry.mpListener = *itLst;
             rListeners.push_back(aEntry);
         }
@@ -617,8 +659,8 @@ inline void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot
     }
 }
 
-void ScBroadcastAreaSlotMachine::StartListeningArea( const ScRange& rRange,
-        SvtListener* pListener )
+void ScBroadcastAreaSlotMachine::StartListeningArea(
+    const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
 {
     if ( rRange == BCA_LISTEN_ALWAYS  )
     {
@@ -653,7 +695,7 @@ void ScBroadcastAreaSlotMachine::StartListeningArea( const ScRange& rRange,
                     // ScBroadcastArea, listeners were added to an already
                     // existing identical area that doesn't need to be inserted
                     // to slots again.
-                    if (!(*pp)->StartListeningArea( rRange, pListener, pArea))
+                    if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea))
                         bDone = true;
                 }
                 else
@@ -664,8 +706,8 @@ void ScBroadcastAreaSlotMachine::StartListeningArea( const ScRange& rRange,
     }
 }
 
-void ScBroadcastAreaSlotMachine::EndListeningArea( const ScRange& rRange,
-        SvtListener* pListener )
+void ScBroadcastAreaSlotMachine::EndListeningArea(
+    const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
 {
     if ( rRange == BCA_LISTEN_ALWAYS  )
     {
@@ -700,7 +742,7 @@ void ScBroadcastAreaSlotMachine::EndListeningArea( const ScRange& rRange,
                 do
                 {
                     if ( *pp )
-                        (*pp)->EndListeningArea( rRange, pListener, pArea );
+                        (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
                 } while (++pp < pStop);
             }
             else
@@ -708,7 +750,7 @@ void ScBroadcastAreaSlotMachine::EndListeningArea( const ScRange& rRange,
                 while ( nOff <= nEnd )
                 {
                     if ( *pp )
-                        (*pp)->EndListeningArea( rRange, pListener, pArea );
+                        (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
                     ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
                 }
             }
@@ -988,7 +1030,10 @@ void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast()
     if (nInBulkBroadcast > 0)
     {
         if (--nInBulkBroadcast == 0)
+        {
             ScBroadcastAreasBulk().swap( aBulkBroadcastAreas);
+            BulkBroadcastGroupAreas();
+        }
     }
 }
 
@@ -997,6 +1042,41 @@ bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea )
     return aBulkBroadcastAreas.insert( pArea ).second;
 }
 
+void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange )
+{
+    BulkGroupAreasType::iterator it = maBulkGroupAreas.lower_bound(pArea);
+    if (it == maBulkGroupAreas.end() || maBulkGroupAreas.key_comp()(pArea, it->first))
+    {
+        // Insert a new one.
+        it = maBulkGroupAreas.insert(it, pArea, new sc::ColumnSpanSet(false));
+    }
+
+    sc::ColumnSpanSet* pSet = it->second;
+    assert(pSet);
+    pSet->set(rRange, true);
+}
+
+void ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas()
+{
+    if (maBulkGroupAreas.empty())
+        return;
+
+    sc::BulkDataHint aHint(*pDoc, NULL);
+
+    BulkGroupAreasType::iterator it = maBulkGroupAreas.begin(), itEnd = maBulkGroupAreas.end();
+    for (; it != itEnd; ++it)
+    {
+        ScBroadcastArea* pArea = it->first;
+        const sc::ColumnSpanSet* pSpans = it->second;
+        assert(pArea);
+        assert(pSpans);
+        aHint.setSpans(pSpans);
+        pArea->GetBroadcaster().Broadcast(aHint);
+    }
+
+    maBulkGroupAreas.clear();
+}
+
 size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea )
 {
     return aBulkBroadcastAreas.erase( pArea );
diff --git a/sc/source/core/data/colorscale.cxx b/sc/source/core/data/colorscale.cxx
index b915e25..9de8919 100644
--- a/sc/source/core/data/colorscale.cxx
+++ b/sc/source/core/data/colorscale.cxx
@@ -81,7 +81,7 @@ void ScFormulaListener::startListening(ScTokenArray* pArr, const ScAddress& rPos
                             aCell2.SetCol(MAXCOL);
                         }
                     }
-                    mpDoc->StartListeningArea(ScRange(aCell1, aCell2), this);
+                    mpDoc->StartListeningArea(ScRange(aCell1, aCell2), false, this);
                     maCells.push_back(ScRange(aCell1, aCell2));
                 }
             }
diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx
index 5a6af4b..6837a54 100644
--- a/sc/source/core/data/documen2.cxx
+++ b/sc/source/core/data/documen2.cxx
@@ -1187,7 +1187,7 @@ void ScDocument::AddLookupCache( ScLookupCache & rCache )
         OSL_FAIL( "ScDocument::AddLookupCache: couldn't add to hash map");
     }
     else
-        StartListeningArea( rCache.getRange(), &rCache);
+        StartListeningArea( rCache.getRange(), false, &rCache);
 }
 
 void ScDocument::RemoveLookupCache( ScLookupCache & rCache )
@@ -1202,7 +1202,7 @@ void ScDocument::RemoveLookupCache( ScLookupCache & rCache )
     {
         ScLookupCache* pCache = (*it).second;
         pLookupCacheMapImpl->aCacheMap.erase( it);
-        EndListeningArea( pCache->getRange(), &rCache);
+        EndListeningArea( pCache->getRange(), false, &rCache);
     }
 }
 
diff --git a/sc/source/core/data/documen7.cxx b/sc/source/core/data/documen7.cxx
index af09c83..5ea4ce5 100644
--- a/sc/source/core/data/documen7.cxx
+++ b/sc/source/core/data/documen7.cxx
@@ -45,20 +45,17 @@ extern const ScFormulaCell* pLastFormulaTreeTop;    // cellform.cxx Err527 WorkA
 
 // STATIC DATA -----------------------------------------------------------
 
-void ScDocument::StartListeningArea( const ScRange& rRange,
-        SvtListener* pListener
-    )
+void ScDocument::StartListeningArea(
+    const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
 {
     if ( pBASM )
-        pBASM->StartListeningArea( rRange, pListener );
+        pBASM->StartListeningArea(rRange, bGroupListening, pListener);
 }
 
-void ScDocument::EndListeningArea( const ScRange& rRange,
-        SvtListener* pListener
-    )
+void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
 {
     if ( pBASM )
-        pBASM->EndListeningArea( rRange, pListener );
+        pBASM->EndListeningArea(rRange, bGroupListening, pListener);
 }
 
 void ScDocument::Broadcast( const ScHint& rHint )
@@ -155,7 +152,7 @@ void ScDocument::BroadcastRefMoved( const sc::RefMovedHint& rHint )
         std::vector<sc::AreaListener>::iterator it = aAreaListeners.begin(), itEnd = aAreaListeners.end();
         for (; it != itEnd; ++it)
         {
-            pBASM->EndListeningArea(it->maArea, it->mpListener);
+            pBASM->EndListeningArea(it->maArea, it->mbGroupListening, it->mpListener);
             it->mpListener->Notify(rHint); // Adjust the references.
         }
     }
@@ -206,7 +203,7 @@ void ScDocument::BroadcastRefMoved( const sc::RefMovedHint& rHint )
         {
             ScRange aNewRange = it->maArea;
             aNewRange.Move(rDelta.Col(), rDelta.Row(), rDelta.Tab());
-            pBASM->StartListeningArea(aNewRange, it->mpListener);
+            pBASM->StartListeningArea(aNewRange, it->mbGroupListening, it->mpListener);
         }
     }
 }
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx
index 96b4339..0a2226f 100644
--- a/sc/source/core/data/formulacell.cxx
+++ b/sc/source/core/data/formulacell.cxx
@@ -58,6 +58,7 @@
 #include <refhint.hxx>
 #include <listenerquery.hxx>
 #include <listenerqueryids.hxx>
+#include <grouparealistener.hxx>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -508,6 +509,29 @@ void ScFormulaCellGroup::compileOpenCLKernel()
     meKernelState = sc::OpenCLKernelBinaryCreated;
 }
 
+sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener(
+    ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed )
+{
+    // TODO : Find existing one with the same criteria.
+    maAreaListeners.push_back(new sc::FormulaGroupAreaListener(rRange, ppTopCell, mnLength, bStartFixed, bEndFixed));
+    return &maAreaListeners.back();
+}
+
+void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc )
+{
+    AreaListenersType::iterator it = maAreaListeners.begin(), itEnd = maAreaListeners.end();
+    for (; it != itEnd; ++it)
+    {
+        sc::FormulaGroupAreaListener* pListener = &(*it);
+        ScRange aListenRange = pListener->getListeningRange();
+        // This "always listen" special range is never grouped.
+        bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS);
+        rDoc.EndListeningArea(aListenRange, bGroupListening, pListener);
+    }
+
+    maAreaListeners.clear();
+}
+
 ScFormulaCell::ScFormulaCell( ScDocument* pDoc, const ScAddress& rPos ) :
     eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT),
     pCode(new ScTokenArray),
@@ -1854,14 +1878,14 @@ void ScFormulaCell::InterpretTail( ScInterpretTailParameter eTailParam )
                 if (pCode->IsRecalcModeAlways())
                 {
                     // The formula was previously volatile, but no more.
-                    pDocument->EndListeningArea(BCA_LISTEN_ALWAYS, this);
+                    pDocument->EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
                     pCode->SetExclusiveRecalcModeNormal();
                 }
                 else
                 {
                     // non-volatile formula.  End listening to the area in case
                     // it's listening due to macro module change.
-                    pDocument->EndListeningArea(BCA_LISTEN_ALWAYS, this);
+                    pDocument->EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
                 }
                 pDocument->RemoveFromFormulaTree(this);
             break;
@@ -3793,7 +3817,7 @@ void startListeningArea(
                 aCell2.SetCol(MAXCOL);
             }
         }
-        rDoc.StartListeningArea(ScRange(aCell1, aCell2), pCell);
+        rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell);
     }
 }
 
@@ -3801,6 +3825,9 @@ void startListeningArea(
 
 void ScFormulaCell::StartListeningTo( ScDocument* pDoc )
 {
+    if (mxGroup)
+        mxGroup->endAllGroupListening(*pDoc);
+
     if (pDoc->IsClipOrUndo() || pDoc->GetNoListening() || IsInChangeTrack())
         return;
 
@@ -3809,7 +3836,7 @@ void ScFormulaCell::StartListeningTo( ScDocument* pDoc )
     ScTokenArray* pArr = GetCode();
     if( pArr->IsRecalcModeAlways() )
     {
-        pDoc->StartListeningArea(BCA_LISTEN_ALWAYS, this);
+        pDoc->StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
     }
 
     pArr->Reset();
@@ -3839,6 +3866,9 @@ void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
 {
     ScDocument& rDoc = rCxt.getDoc();
 
+    if (mxGroup)
+        mxGroup->endAllGroupListening(rDoc);
+
     if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
         return;
 
@@ -3847,7 +3877,7 @@ void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
     ScTokenArray* pArr = GetCode();
     if( pArr->IsRecalcModeAlways() )
     {
-        rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, this);
+        rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
     }
 
     pArr->Reset();
@@ -3896,7 +3926,7 @@ void endListeningArea(
             }
         }
 
-        rDoc.EndListeningArea(ScRange(aCell1, aCell2), pCell);
+        rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell);
     }
 }
 
@@ -3905,6 +3935,9 @@ void endListeningArea(
 void ScFormulaCell::EndListeningTo( ScDocument* pDoc, ScTokenArray* pArr,
         ScAddress aCellPos )
 {
+    if (mxGroup)
+        mxGroup->endAllGroupListening(*pDoc);
+
     if (pDoc->IsClipOrUndo() || IsInChangeTrack())
         return;
 
@@ -3912,7 +3945,7 @@ void ScFormulaCell::EndListeningTo( ScDocument* pDoc, ScTokenArray* pArr,
 
     if ( GetCode()->IsRecalcModeAlways() )
     {
-        pDoc->EndListeningArea( BCA_LISTEN_ALWAYS, this );
+        pDoc->EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
     }
 
     if (!pArr)
@@ -3944,6 +3977,9 @@ void ScFormulaCell::EndListeningTo( ScDocument* pDoc, ScTokenArray* pArr,
 
 void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
 {
+    if (mxGroup)
+        mxGroup->endAllGroupListening(rCxt.getDoc());
+
     if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack())
         return;
 
@@ -3957,7 +3993,7 @@ void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
 
     if (pArr->IsRecalcModeAlways())
     {
-        rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, this);
+        rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
     }
 
     pArr->Reset();
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index f7c6118..c42f777 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -739,7 +739,7 @@ void ScTable::SortReorderByColumn(
         std::vector<sc::AreaListener>::iterator it = aAreaListeners.begin(), itEnd = aAreaListeners.end();
         for (; it != itEnd; ++it)
         {
-            pDocument->EndListeningArea(it->maArea, it->mpListener);
+            pDocument->EndListeningArea(it->maArea, it->mbGroupListening, it->mpListener);
             aListeners.push_back( it->mpListener);
         }
     }
@@ -770,7 +770,7 @@ void ScTable::SortReorderByColumn(
                 aNewRange.aStart.SetCol( itCol->second);
                 aNewRange.aEnd.SetCol( itCol->second);
             }
-            pDocument->StartListeningArea(aNewRange, it->mpListener);
+            pDocument->StartListeningArea(aNewRange, it->mbGroupListening, it->mpListener);
         }
     }
 
@@ -1020,7 +1020,7 @@ void ScTable::SortReorderByRow(
         std::vector<sc::AreaListener>::iterator it = aAreaListeners.begin(), itEnd = aAreaListeners.end();
         for (; it != itEnd; ++it)
         {
-            pDocument->EndListeningArea(it->maArea, it->mpListener);
+            pDocument->EndListeningArea(it->maArea, it->mbGroupListening, it->mpListener);
             aListeners.push_back( it->mpListener);
         }
     }
@@ -1082,7 +1082,7 @@ void ScTable::SortReorderByRow(
                 aNewRange.aStart.SetRow( itRow->second);
                 aNewRange.aEnd.SetRow( itRow->second);
             }
-            pDocument->StartListeningArea(aNewRange, it->mpListener);
+            pDocument->StartListeningArea(aNewRange, it->mbGroupListening, it->mpListener);
         }
     }
 
diff --git a/sc/source/core/inc/bcaslot.hxx b/sc/source/core/inc/bcaslot.hxx
index 2f63207..bf98908 100644
--- a/sc/source/core/inc/bcaslot.hxx
+++ b/sc/source/core/inc/bcaslot.hxx
@@ -22,17 +22,23 @@
 
 #include <set>
 #include <boost/unordered_set.hpp>
+#include <boost/ptr_container/ptr_map.hpp>
 #include <functional>
+
 #include <svl/broadcast.hxx>
 
 #include "global.hxx"
 #include "brdcst.hxx"
+#include <columnspanset.hxx>
+
+class ScBroadcastArea;
 
 namespace sc {
 
 struct AreaListener
 {
     ScRange maArea;
+    bool mbGroupListening;
     SvtListener* mpListener;
 };
 
@@ -42,19 +48,20 @@ struct AreaListener
     Used in a Unique Associative Container.
  */
 
-class ScBroadcastArea
+class ScBroadcastArea : boost::noncopyable
 {
 private:
     ScBroadcastArea*    pUpdateChainNext;
     SvtBroadcaster      aBroadcaster;
     ScRange             aRange;
     sal_uLong               nRefCount;
-    bool                bInUpdateChain;
+
+    bool mbInUpdateChain:1;
+    bool mbGroupListening:1;
 
 public:
-            ScBroadcastArea( const ScRange& rRange )
-                : pUpdateChainNext( NULL ), aRange( rRange ),
-                nRefCount( 0 ), bInUpdateChain( false ) {}
+    ScBroadcastArea( const ScRange& rRange );
+
     inline SvtBroadcaster&       GetBroadcaster()       { return aBroadcaster; }
     inline const SvtBroadcaster& GetBroadcaster() const { return aBroadcaster; }
     inline void         UpdateRange( const ScRange& rNewRange )
@@ -67,8 +74,11 @@ public:
     inline sal_uLong        GetRef() { return nRefCount; }
     inline ScBroadcastArea* GetUpdateChainNext() const { return pUpdateChainNext; }
     inline void         SetUpdateChainNext( ScBroadcastArea* p ) { pUpdateChainNext = p; }
-    inline bool         IsInUpdateChain() const { return bInUpdateChain; }
-    inline void         SetInUpdateChain( bool b ) { bInUpdateChain = b; }
+    inline bool         IsInUpdateChain() const { return mbInUpdateChain; }
+    inline void         SetInUpdateChain( bool b ) { mbInUpdateChain = b; }
+
+    inline bool IsGroupListening() const { return mbGroupListening; }
+    void SetGroupListening( bool b ) { mbGroupListening = b; }
 
     /** Equalness of this or range. */
     inline  bool        operator==( const ScBroadcastArea & rArea ) const;
@@ -76,7 +86,7 @@ public:
 
 inline bool ScBroadcastArea::operator==( const ScBroadcastArea & rArea ) const
 {
-    return aRange == rArea.aRange;
+    return aRange == rArea.aRange && mbGroupListening == rArea.mbGroupListening;
 }
 
 struct ScBroadcastAreaEntry
@@ -91,7 +101,7 @@ struct ScBroadcastAreaHash
 {
     size_t operator()( const ScBroadcastAreaEntry& rEntry ) const
     {
-        return rEntry.mpArea->GetRange().hashArea();
+        return rEntry.mpArea->GetRange().hashArea() + rEntry.mpArea->IsGroupListening();
     }
 };
 
@@ -145,7 +155,7 @@ private:
      */
     bool mbHasErasedArea;
 
-    ScBroadcastAreas::const_iterator  FindBroadcastArea( const ScRange& rRange ) const;
+    ScBroadcastAreas::const_iterator FindBroadcastArea( const ScRange& rRange, bool bGroupListening ) const;
 
     /**
         More hypothetical (memory would probably be doomed anyway) check
@@ -189,9 +199,8 @@ public:
             true if rpArea passed was NULL and ScBroadcastArea is newly
             created.
      */
-    bool                StartListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener,
-                                            ScBroadcastArea*& rpArea );
+    bool StartListeningArea(
+        const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea );
 
     /**
         Insert a ScBroadcastArea obtained via StartListeningArea() to
@@ -199,9 +208,9 @@ public:
      */
     void                InsertListeningArea( ScBroadcastArea* pArea );
 
-    void                EndListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener,
-                                            ScBroadcastArea*& rpArea );
+    void EndListeningArea(
+        const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea );
+
     bool                AreaBroadcast( const ScHint& rHint );
     /// @return true if at least one broadcast occurred.
     bool                AreaBroadcastInRange( const ScRange& rRange,
@@ -238,6 +247,7 @@ public:
 class  ScBroadcastAreaSlotMachine
 {
 private:
+    typedef boost::ptr_map<ScBroadcastArea*, sc::ColumnSpanSet> BulkGroupAreasType;
 
     /**
         Slot offset arrangement of columns and rows, once per sheet.
@@ -279,6 +289,7 @@ private:
 
 private:
     ScBroadcastAreasBulk  aBulkBroadcastAreas;
+    BulkGroupAreasType maBulkGroupAreas;
     TableSlotsMap         aTableSlotsMap;
     AreasToBeErased       maAreasToBeErased;
     SvtBroadcaster       *pBCAlways;             // for the RC_ALWAYS special range
@@ -295,10 +306,12 @@ private:
 public:
                         ScBroadcastAreaSlotMachine( ScDocument* pDoc );
                         ~ScBroadcastAreaSlotMachine();
-    void                StartListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener );
-    void                EndListeningArea( const ScRange& rRange,
-                                            SvtListener* pListener );
+    void StartListeningArea(
+        const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
+
+    void EndListeningArea(
+        const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
+
     bool                AreaBroadcast( const ScHint& rHint ) const;
         // return: at least one broadcast occurred
     bool                AreaBroadcastInRange( const ScRange& rRange, const ScHint& rHint ) const;
@@ -309,6 +322,10 @@ public:
     void                EnterBulkBroadcast();
     void                LeaveBulkBroadcast();
     bool                InsertBulkArea( const ScBroadcastArea* p );
+
+    void InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange );
+    void BulkBroadcastGroupAreas();
+
     /// @return: how many removed
     size_t              RemoveBulkArea( const ScBroadcastArea* p );
     inline ScBroadcastArea* GetUpdateChain() const { return pUpdateChain; }
diff --git a/sc/source/core/tool/bulkdatahint.cxx b/sc/source/core/tool/bulkdatahint.cxx
new file mode 100644
index 0000000..78c2384
--- /dev/null
+++ b/sc/source/core/tool/bulkdatahint.cxx
@@ -0,0 +1,49 @@
+/* -*- 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 <bulkdatahint.hxx>
+
+namespace sc {
+
+struct BulkDataHint::Impl
+{
+    ScDocument& mrDoc;
+    const ColumnSpanSet* mpSpans;
+
+    Impl( ScDocument& rDoc, const ColumnSpanSet* pSpans ) :
+        mrDoc(rDoc),
+        mpSpans(pSpans) {}
+};
+
+BulkDataHint::BulkDataHint( ScDocument& rDoc, const ColumnSpanSet* pSpans ) :
+    SfxSimpleHint(SC_HINT_BULK_DATACHANGED), mpImpl(new Impl(rDoc, pSpans)) {}
+
+BulkDataHint::~BulkDataHint()
+{
+    delete mpImpl;
+}
+
+void BulkDataHint::setSpans( const ColumnSpanSet* pSpans )
+{
+    mpImpl->mpSpans = pSpans;
+}
+
+const ColumnSpanSet* BulkDataHint::getSpans() const
+{
+    return mpImpl->mpSpans;
+}
+
+ScDocument& BulkDataHint::getDoc()
+{
+    return mpImpl->mrDoc;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/tool/chartlis.cxx b/sc/source/core/tool/chartlis.cxx
index be56325..b9f5a3e 100644
--- a/sc/source/core/tool/chartlis.cxx
+++ b/sc/source/core/tool/chartlis.cxx
@@ -285,7 +285,7 @@ private:
         if (rRange.aStart == rRange.aEnd)
             mpDoc->StartListeningCell(rRange.aStart, &mrParent);
         else
-            mpDoc->StartListeningArea(rRange, &mrParent);
+            mpDoc->StartListeningArea(rRange, false, &mrParent);
     }
 
     void endListening(const ScRange& rRange)
@@ -293,7 +293,7 @@ private:
         if (rRange.aStart == rRange.aEnd)
             mpDoc->EndListeningCell(rRange.aStart, &mrParent);
         else
-            mpDoc->EndListeningArea(rRange, &mrParent);
+            mpDoc->EndListeningArea(rRange, false, &mrParent);
     }
 private:
     ScDocument* mpDoc;
diff --git a/sc/source/core/tool/grouparealistener.cxx b/sc/source/core/tool/grouparealistener.cxx
new file mode 100644
index 0000000..e682d61
--- /dev/null
+++ b/sc/source/core/tool/grouparealistener.cxx
@@ -0,0 +1,321 @@
+/* -*- 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 <grouparealistener.hxx>
+#include <brdcst.hxx>
+#include <formulacell.hxx>
+#include <bulkdatahint.hxx>
+#include <columnspanset.hxx>
+#include <column.hxx>
+
+namespace sc {
+
+namespace {
+
+class Notifier : std::unary_function<ScFormulaCell*, void>
+{
+    const SfxHint& mrHint;
+public:
+    Notifier( const SfxHint& rHint ) : mrHint(rHint) {}
+
+    void operator() ( ScFormulaCell* pCell )
+    {
+        pCell->Notify(mrHint);
+    }
+};
+
+class CollectCellAction : public sc::ColumnSpanSet::ColumnAction
+{
+    const FormulaGroupAreaListener& mrAreaListener;
+    ScAddress maPos;
+    std::vector<ScFormulaCell*> maCells;
+
+public:
+    CollectCellAction( const FormulaGroupAreaListener& rAreaListener ) :
+        mrAreaListener(rAreaListener) {}
+
+    virtual void startColumn( ScColumn* pCol )
+    {
+        maPos.SetTab(pCol->GetTab());
+        maPos.SetCol(pCol->GetCol());
+    }
+
+    virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal )
+    {
+        if (!bVal)
+            return;
+
+        mrAreaListener.collectFormulaCells(maPos.Tab(), maPos.Col(), nRow1, nRow2, maCells);
+    };
+
+    void swapCells( std::vector<ScFormulaCell*>& rCells )
+    {
+        // Remove duplicate before the swap.
+        std::sort(maCells.begin(), maCells.end());
+        std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end());
+        maCells.erase(it, maCells.end());
+
+        rCells.swap(maCells);
+    }
+};
+
+}
+
+FormulaGroupAreaListener::FormulaGroupAreaListener(
+    const ScRange& rRange, ScFormulaCell** ppTopCell, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) :
+    maRange(rRange),
+    mppTopCell(ppTopCell),
+    mnGroupLen(nGroupLen),
+    mbStartFixed(bStartFixed),
+    mbEndFixed(bEndFixed)
+{
+    assert(mppTopCell); // This can't be NULL.
+}
+
+ScRange FormulaGroupAreaListener::getListeningRange() const
+{
+    ScRange aRet = maRange;
+    if (!mbEndFixed)
+        aRet.aEnd.IncRow(mnGroupLen-1);
+    return aRet;
+}
+
+void FormulaGroupAreaListener::Notify( const SfxHint& rHint )
+{
+    const SfxSimpleHint* pSimpleHint = dynamic_cast<const SfxSimpleHint*>(&rHint);
+    if (!pSimpleHint)
+        return;
+
+    switch (pSimpleHint->GetId())
+    {
+        case SC_HINT_DATACHANGED:
+            notifyCellChange(rHint, static_cast<const ScHint*>(pSimpleHint)->GetAddress());
+        break;
+        case SC_HINT_BULK_DATACHANGED:
+        {
+            const BulkDataHint& rBulkHint = static_cast<const BulkDataHint&>(*pSimpleHint);
+            notifyBulkChange(rBulkHint);
+        }
+        break;
+        default:
+            ;
+    }
+}
+
+void FormulaGroupAreaListener::notifyBulkChange( const BulkDataHint& rHint )
+{
+    const ColumnSpanSet* pSpans = rHint.getSpans();
+    if (!pSpans)
+        return;
+
+    ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc();
+
+    CollectCellAction aAction(*this);
+    pSpans->executeColumnAction(rDoc, aAction);
+
+    std::vector<ScFormulaCell*> aCells;
+    aAction.swapCells(aCells);
+    ScHint aHint(SC_HINT_DATACHANGED, ScAddress());
+    std::for_each(aCells.begin(), aCells.end(), Notifier(aHint));
+}
+
+void FormulaGroupAreaListener::collectFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells ) const
+{
+    if (rPos.Tab() < maRange.aStart.Tab() || maRange.aEnd.Tab() < rPos.Tab())
+        // Wrong sheet.
+        return;
+
+    if (rPos.Col() < maRange.aStart.Col() || maRange.aEnd.Col() < rPos.Col())
+        // Outside the column range.
+        return;
+
+    ScFormulaCell** pp = mppTopCell;
+    ScFormulaCell** ppEnd = pp + mnGroupLen;
+
+    if (mbStartFixed)
+    {
+        if (mbEndFixed)
+        {
+            // Both top and bottom row positions are absolute.  Use the original range as-is.
+            SCROW nRow1 = maRange.aStart.Row();
+            SCROW nRow2 = maRange.aEnd.Row();
+            if (rPos.Row() < nRow1 || nRow2 < rPos.Row())
+                return;
+
+            for (; pp != ppEnd; ++pp)
+                rCells.push_back(*pp);
+        }
+        else
+        {
+            // Only the end row is relative.
+            SCROW nRow1 = maRange.aStart.Row();
+            SCROW nRow2 = maRange.aEnd.Row();
+            SCROW nMaxRow = nRow2 + mnGroupLen - 1;
+            if (rPos.Row() < nRow1 || nMaxRow < rPos.Row())
+                return;
+
+            if (nRow2 < rPos.Row())
+            {
+                // Skip ahead to the first hit.
+                SCROW nSkip = rPos.Row() - nRow2;
+                pp += nSkip;
+                nRow2 += nSkip;
+            }
+
+            // Notify the first hit cell and all subsequent ones.
+            for (; pp != ppEnd; ++pp)
+                rCells.push_back(*pp);
+        }
+    }
+    else if (mbEndFixed)
+    {
+        // Only the start row is relative.
+        SCROW nRow1 = maRange.aStart.Row();
+        SCROW nRow2 = maRange.aEnd.Row();
+
+        if (rPos.Row() < nRow1 || nRow2 < rPos.Row())
+            return;
+
+        for (; pp != ppEnd && nRow1 <= nRow2; ++pp, ++nRow1)
+            rCells.push_back(*pp);
+    }
+    else
+    {
+        // Both top and bottom row positions are relative.
+        SCROW nRow1 = maRange.aStart.Row();
+        SCROW nRow2 = maRange.aEnd.Row();
+        SCROW nMaxRow = nRow2 + mnGroupLen - 1;
+        if (nMaxRow < rPos.Row())
+            return;
+
+        if (nRow2 < rPos.Row())
+        {
+            // Skip ahead.
+            SCROW nSkip = rPos.Row() - nRow2;
+            pp += nSkip;
+            nRow1 += nSkip;
+            nRow2 += nSkip;
+        }
+
+        for (; pp != ppEnd; ++pp, ++nRow1, ++nRow2)
+        {
+            if (rPos.Row() < nRow1 || nRow2 < rPos.Row())
+                // Changed cell is outside the range.
+                continue;
+
+            rCells.push_back(*pp);
+        }
+    }
+}
+
+void FormulaGroupAreaListener::collectFormulaCells(
+    SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const
+{
+    PutInOrder(nRow1, nRow2);
+
+    if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
+        // Wrong sheet.
+        return;
+
+    if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
+        // Outside the column range.
+        return;
+
+    ScFormulaCell** pp = mppTopCell;
+    ScFormulaCell** ppEnd = pp + mnGroupLen;
+
+    if (mbStartFixed)
+    {
+        if (mbEndFixed)
+        {
+            // Both top and bottom row positions are absolute.  Use the original range as-is.
+            SCROW nRefRow1 = maRange.aStart.Row();
+            SCROW nRefRow2 = maRange.aEnd.Row();
+            if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
+                return;
+
+            for (; pp != ppEnd; ++pp)
+                rCells.push_back(*pp);
+        }
+        else
+        {
+            // Only the end row is relative.
+            SCROW nRefRow1 = maRange.aStart.Row();
+            SCROW nRefRow2 = maRange.aEnd.Row();
+            SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
+            if (nRow2 < nRefRow1 || nMaxRefRow < nRow1)
+                return;
+
+            if (nRefRow2 < nRow1)
+            {
+                // Skip ahead to the first hit.
+                SCROW nSkip = nRow1 - nRefRow2;
+                pp += nSkip;
+                nRefRow2 += nSkip;
+            }
+
+            assert(nRow1 <= nRefRow2);
+
+            // Notify the first hit cell and all subsequent ones.
+            for (; pp != ppEnd; ++pp)
+                rCells.push_back(*pp);
+        }
+    }
+    else if (mbEndFixed)
+    {
+        // Only the start row is relative.
+        SCROW nRefRow1 = maRange.aStart.Row();
+        SCROW nRefRow2 = maRange.aEnd.Row();
+
+        if (nRow2 < nRefRow1 || nRefRow2 < nRow1)
+            return;
+
+        for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1)
+            rCells.push_back(*pp);
+    }
+    else
+    {
+        // Both top and bottom row positions are relative.
+        SCROW nRefRow1 = maRange.aStart.Row();
+        SCROW nRefRow2 = maRange.aEnd.Row();
+        SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1;
+        if (nMaxRefRow < nRow1)
+            return;
+
+        if (nRefRow2 < nRow1)
+        {
+            // The ref row range is above the changed row span.  Skip ahead.
+            SCROW nSkip = nRow1 - nRefRow2;
+            pp += nSkip;
+            nRefRow1 += nSkip;
+            nRefRow2 += nSkip;
+        }
+
+        // At this point the initial ref row range should be overlapping the
+        // dirty cell range.
+        assert(nRow1 <= nRefRow2);
+
+        // Keep sliding down until the top ref row position is below the
+        // bottom row of the dirty cell range.
+        for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2)
+            rCells.push_back(*pp);
+    }
+}
+
+void FormulaGroupAreaListener::notifyCellChange( const SfxHint& rHint, const ScAddress& rPos )
+{
+    // Determine which formula cells within the group need to be notified of this change.
+    std::vector<ScFormulaCell*> aCells;
+    collectFormulaCells(rPos, aCells);
+    std::for_each(aCells.begin(), aCells.end(), Notifier(rHint));
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/tool/sharedformula.cxx b/sc/source/core/tool/sharedformula.cxx
index 65d91a73..ce00fc3 100644
--- a/sc/source/core/tool/sharedformula.cxx
+++ b/sc/source/core/tool/sharedformula.cxx
@@ -10,6 +10,11 @@
 #include "sharedformula.hxx"
 #include "calcmacros.hxx"
 #include "tokenarray.hxx"
+#include <listenercontext.hxx>
+#include <document.hxx>
+#include <grouparealistener.hxx>
+
+#define USE_FORMULA_GROUP_LISTENER 1
 
 namespace sc {
 
@@ -328,15 +333,86 @@ void SharedFormulaUtil::unshareFormulaCells(CellStoreType& rCells, std::vector<S
 
 void SharedFormulaUtil::startListeningAsGroup( sc::StartListeningContext& rCxt, ScFormulaCell** ppSharedTop )
 {
-    assert((**ppSharedTop).IsSharedTop());
+    ScFormulaCell& rTopCell = **ppSharedTop;
+    assert(rTopCell.IsSharedTop());
+
+#if USE_FORMULA_GROUP_LISTENER
+    ScDocument& rDoc = rCxt.getDoc();
+    rDoc.SetDetectiveDirty(true);
+
+    ScFormulaCellGroupRef xGroup = rTopCell.GetCellGroup();
+    const ScTokenArray* pCode = xGroup->mpCode;
+    assert(pCode == rTopCell.GetCode());
+    if (pCode->IsRecalcModeAlways())
+    {
+        rDoc.StartListeningArea(
+            BCA_LISTEN_ALWAYS, false,
+            xGroup->getAreaListener(ppSharedTop, BCA_LISTEN_ALWAYS, true, true));
+    }
+
+    formula::FormulaToken** p = pCode->GetCode();
+    formula::FormulaToken** pEnd = p + pCode->GetCodeLen();
+    for (; p != pEnd; ++p)
+    {
+        const formula::FormulaToken* t = *p;
+        switch (t->GetType())
+        {
+            case formula::svSingleRef:
+            {
+                ScAddress aPos = t->GetSingleRef()->toAbs(rTopCell.aPos);
+                ScFormulaCell** pp = ppSharedTop;
+                ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
+                for (; pp != ppEnd; ++pp, aPos.IncRow())
+                {
+                    if (!aPos.IsValid())
+                        break;
+
+                    rDoc.StartListeningCell(rCxt, aPos, **pp);
+                }
+            }
+            break;
+            case formula::svDoubleRef:
+            {
+                const ScSingleRefData& rRef1 = *t->GetSingleRef();
+                const ScSingleRefData& rRef2 = *t->GetSingleRef2();
+                ScAddress aPos1 = rRef1.toAbs(rTopCell.aPos);
+                ScAddress aPos2 = rRef2.toAbs(rTopCell.aPos);
+
+                ScRange aOrigRange = ScRange(aPos1, aPos2);
+                ScRange aListenedRange = aOrigRange;
+                if (rRef2.IsRowRel())
+                    aListenedRange.aEnd.IncRow(xGroup->mnLength-1);
+
+                if (aPos1.IsValid() && aPos2.IsValid())
+                {
+                    rDoc.StartListeningArea(
+                        aListenedRange, true,
+                        xGroup->getAreaListener(ppSharedTop, aOrigRange, !rRef1.IsRowRel(), !rRef2.IsRowRel()));
+                }
+            }
+            break;
+            default:
+                ;
+        }
+    }
 
     ScFormulaCell** pp = ppSharedTop;
+    ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength;
+    for (; pp != ppEnd; ++pp)
+    {
+        ScFormulaCell& rCell = **pp;
+        rCell.SetNeedsListening(false);
+    }
+
+#else
+    ScFormulaCell** pp = ppSharedTop;
     ScFormulaCell** ppEnd = ppSharedTop + (**ppSharedTop).GetSharedLength();
     for (; pp != ppEnd; ++pp)
     {
         ScFormulaCell& rFC = **pp;
         rFC.StartListeningTo(rCxt);
     }
+#endif
 }
 
 }
diff --git a/sc/source/ui/docshell/macromgr.cxx b/sc/source/ui/docshell/macromgr.cxx
index c17c0b7..542e04b 100644
--- a/sc/source/ui/docshell/macromgr.cxx
+++ b/sc/source/ui/docshell/macromgr.cxx
@@ -192,7 +192,7 @@ void ScMacroManager::BroadcastModuleUpdate(const OUString& aModuleName)
 
         // for recalc on cell value change.  If the cell is not volatile, the
         // cell stops listening right away after it gets re-interpreted.
-        mpDoc->StartListeningArea(BCA_LISTEN_ALWAYS, pCell);
+        mpDoc->StartListeningArea(BCA_LISTEN_ALWAYS, false, pCell);
     }
 }
 
diff --git a/sc/source/ui/docshell/servobj.cxx b/sc/source/ui/docshell/servobj.cxx
index 664887e..26208b2 100644
--- a/sc/source/ui/docshell/servobj.cxx
+++ b/sc/source/ui/docshell/servobj.cxx
@@ -100,7 +100,7 @@ ScServerObject::ScServerObject( ScDocShell* pShell, const OUString& rItem ) :
     }
 
     pDocSh->GetDocument().GetLinkManager()->InsertServer( this );
-    pDocSh->GetDocument().StartListeningArea( aRange, &aForwarder );
+    pDocSh->GetDocument().StartListeningArea( aRange, false, &aForwarder );
 
     StartListening(*pDocSh);        // um mitzubekommen, wenn die DocShell geloescht wird
     StartListening(*SfxGetpApp());     // for SC_HINT_AREAS_CHANGED
@@ -118,7 +118,7 @@ void ScServerObject::Clear()
         ScDocShell* pTemp = pDocSh;
         pDocSh = NULL;
 
-        pTemp->GetDocument().EndListeningArea( aRange, &aForwarder );
+        pTemp->GetDocument().EndListeningArea(aRange, false, &aForwarder);
         pTemp->GetDocument().GetLinkManager()->RemoveServer( this );
         EndListening(*pTemp);
         EndListening(*SfxGetpApp());
@@ -154,7 +154,7 @@ bool ScServerObject::GetData(
         //  refresh the listeners now (this is called from a timer)
 
         EndListeningAll();
-        pDocSh->GetDocument().StartListeningArea( aRange, &aForwarder );
+        pDocSh->GetDocument().StartListeningArea( aRange, false, &aForwarder );
         StartListening(*pDocSh);
         StartListening(*SfxGetpApp());
         bRefreshListener = false;
diff --git a/sc/source/ui/unoobj/cellsuno.cxx b/sc/source/ui/unoobj/cellsuno.cxx
index 7a50745..a2b58a8 100644
--- a/sc/source/ui/unoobj/cellsuno.cxx
+++ b/sc/source/ui/unoobj/cellsuno.cxx
@@ -1674,7 +1674,7 @@ void ScCellRangesBase::RefChanged()
 
         ScDocument& rDoc = pDocShell->GetDocument();
         for ( size_t i = 0, nCount = aRanges.size(); i < nCount; ++i )
-            rDoc.StartListeningArea( *aRanges[ i ], pValueListener );
+            rDoc.StartListeningArea( *aRanges[ i ], false, pValueListener );
     }
 
     ForgetCurrentAttrs();
@@ -3395,7 +3395,7 @@ void SAL_CALL ScCellRangesBase::addModifyListener(const uno::Reference<util::XMo
 
         ScDocument& rDoc = pDocShell->GetDocument();
         for ( size_t i = 0, nCount = aRanges.size(); i < nCount; i++)
-            rDoc.StartListeningArea( *aRanges[ i ], pValueListener );
+            rDoc.StartListeningArea( *aRanges[ i ], false, pValueListener );
 
         acquire();  // don't lose this object (one ref for all listeners)
     }
diff --git a/sc/source/ui/unoobj/chart2uno.cxx b/sc/source/ui/unoobj/chart2uno.cxx
index cad19ee..bc8f175 100644
--- a/sc/source/ui/unoobj/chart2uno.cxx
+++ b/sc/source/ui/unoobj/chart2uno.cxx
@@ -2547,7 +2547,7 @@ void ScChart2DataSequence::RefChanged()
                 if (!ScRefTokenHelper::getRangeFromToken(aRange, *itr, ScAddress()))
                     continue;
 
-                m_pDocument->StartListeningArea(aRange, m_pValueListener);
+                m_pDocument->StartListeningArea(aRange, false, m_pValueListener);
                 if (pCLC)
                     pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
             }
@@ -3399,7 +3399,7 @@ void SAL_CALL ScChart2DataSequence::addModifyListener( const uno::Reference< uti
                 if (!ScRefTokenHelper::getRangeFromToken(aRange, *itr, ScAddress()))
                     continue;
 
-                m_pDocument->StartListeningArea( aRange, m_pValueListener );
+                m_pDocument->StartListeningArea( aRange, false, m_pValueListener );
                 if (pCLC)
                     pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
             }


More information about the Libreoffice-commits mailing list