Add flush mechanism to buffered Primitives

There are the classes BufferedDecompositionPrimitive2D
and BufferedDecompositionGroupPrimitive2D that offer
a unified mechanism to preserve/buffer the decomposition
of a Primitive, independent of the complexity. This may
include e.g. the pixel representation of a 3D Scene,
for Charts/CustomShapes and others.

Until now these were refreshed when the objects change,
or when the buffered decomposition decides that it is
necessary, but there was no general mechanism in place
to clean these up while the Primitive owning them was
alive/used. This is not a big thing in Draw/Impress
since this would only be relevant when zooming deep into
a SdrPage, so the non-vislible parts would still hold
data that would not be needed. But for Calc and Writer
this is more relevant: A 3D Chart on the 1st page of a
Writer document would be held buffered all the time a
user would work on pages below that. This is good for
guaranteeing fast re-visualization when scrolling up
again, but until then would just block memory.

So I added this general mechanism that allows activating
flushing timer-based, on Seconds. The default is null
seconds, thus deactivated. It should only be used for
Primitives that can get expensive, not for all.
NOTE: I checked activating for all Primitives to be on
the safe side, works.

It is now (initially) activated for:
- GlowPrimitive2D
- GraphicPrimitive2D
- MetafilePrimitive2D
- ScenePrimitive2D
- ShadowPrimitive2D (softShadow)
- SoftEdgePrimitive2D
- SdrCustomShapePrimitive2D
- SdrGrafPrimitive2D

These are the usual suspects that may use some memory
to buffer their decomposition (for good reasons, each
repaint uses it). Other Primitives which may show need
to be treated this way can easily be added, just by
calling setCallbackSeconds(s) in their constructor. This
is true for all Primitives derived from the two classes
mentioned above.

NOTE: Too short buffering times are *not* useful - e.g.
for a expensive-to-be-recreated 3D chart representation
this may not pay out, so I chose times in a way that try
to respect this.

NOTE: This change needs
7397fa7cdfc33f5a079df42e4d6cfa59ae9e062d to work
correctly (thanks to sberg for this). Without this the
office hangs/does not terminate regularly in trying to
destroy the 'TimerManager'.

Change-Id: Id4802afcb6d12480bb2935cc1ef67fe443b3b788
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160926
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx
index 67f4162..4b5f52b 100644
--- a/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx
+++ b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx
@@ -22,11 +22,77 @@
#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>

namespace
{
class LocalCallbackTimer : public salhelper::Timer
{
protected:
    drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D* pCustomer;

public:
    explicit LocalCallbackTimer(
        drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D& rCustomer)
        : pCustomer(&rCustomer)
    {
    }

protected:
    virtual void SAL_CALL onShot() override;
};

void SAL_CALL LocalCallbackTimer::onShot() { flushBufferedDecomposition(*pCustomer); }
}

namespace drawinglayer::primitive2d
{
void flushBufferedDecomposition(BufferedDecompositionGroupPrimitive2D& rTarget)
{
    rTarget.setBuffered2DDecomposition(Primitive2DContainer());
}

const Primitive2DContainer&
BufferedDecompositionGroupPrimitive2D::getBuffered2DDecomposition() const
{
    if (0 != maCallbackSeconds && maCallbackTimer.is())
    {
        // decomposition was used, touch
        maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
    }

    return maBuffered2DDecomposition;
}

void BufferedDecompositionGroupPrimitive2D::setBuffered2DDecomposition(Primitive2DContainer&& rNew)
{
    if (0 != maCallbackSeconds)
    {
        if (rNew.empty())
        {
            // no more decomposition, end callback
            maCallbackTimer.clear();
        }
        else if (maCallbackTimer.is())
        {
            // decomposition changed, touch
            maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
        }
        else
        {
            // decomposition changed, start callback
            maCallbackTimer.set(new LocalCallbackTimer(*this));
            maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
            maCallbackTimer->start();
        }
    }

    maBuffered2DDecomposition = std::move(rNew);
}

BufferedDecompositionGroupPrimitive2D::BufferedDecompositionGroupPrimitive2D(
    Primitive2DContainer&& aChildren)
    : GroupPrimitive2D(std::move(aChildren))
    , maCallbackTimer()
    , maCallbackSeconds(0)
{
}

diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx
index 76fa1a9..53b6f87 100644
--- a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx
+++ b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx
@@ -22,9 +22,80 @@
#include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>

namespace
{
class LocalCallbackTimer : public salhelper::Timer
{
protected:
    drawinglayer::primitive2d::BufferedDecompositionPrimitive2D* pCustomer;

public:
    explicit LocalCallbackTimer(
        drawinglayer::primitive2d::BufferedDecompositionPrimitive2D& rCustomer)
        : pCustomer(&rCustomer)
    {
    }

protected:
    virtual void SAL_CALL onShot() override;
};

void SAL_CALL LocalCallbackTimer::onShot() { flushBufferedDecomposition(*pCustomer); }
}

namespace drawinglayer::primitive2d
{
BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() {}
void flushBufferedDecomposition(BufferedDecompositionPrimitive2D& rTarget)
{
    rTarget.setBuffered2DDecomposition(Primitive2DContainer());
}

const Primitive2DContainer& BufferedDecompositionPrimitive2D::getBuffered2DDecomposition() const
{
    if (0 != maCallbackSeconds && maCallbackTimer.is())
    {
        // decomposition was used, touch
        maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
    }

    return maBuffered2DDecomposition;
}

void BufferedDecompositionPrimitive2D::setBuffered2DDecomposition(Primitive2DContainer&& rNew)
{
    if (0 != maCallbackSeconds)
    {
        if (rNew.empty())
        {
            // no more decomposition, end callback
            maCallbackTimer.clear();
        }
        else if (maCallbackTimer.is())
        {
            // decomposition changed, touch
            maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
        }
        else
        {
            // decomposition changed, start callback
            maCallbackTimer.set(new LocalCallbackTimer(*this));
            maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
            maCallbackTimer->start();
        }
    }

    maBuffered2DDecomposition = std::move(rNew);
}

BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D()
    : maBuffered2DDecomposition()
    , maCallbackTimer()
    , maCallbackSeconds(0)
    , mnTransparenceForShadow(0)
{
}

BufferedDecompositionPrimitive2D::~BufferedDecompositionPrimitive2D() {}

void BufferedDecompositionPrimitive2D::get2DDecomposition(
    Primitive2DDecompositionVisitor& rVisitor,
diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
index fb1a12f..c843129 100644
--- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
@@ -43,6 +43,8 @@ GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius,
    , mfLastDiscreteGlowRadius(0.0)
    , maLastClippedRange()
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(15);
}

bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx
index c143c8a..c26c76f 100644
--- a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx
@@ -174,6 +174,8 @@ GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform,
    , maGraphicObject(rGraphicObject)
    , maGraphicAttr(rGraphicAttr)
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(20);
}

GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform,
@@ -181,6 +183,8 @@ GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform,
    : maTransform(std::move(aTransform))
    , maGraphicObject(rGraphicObject)
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(20);
}

bool GraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx
index f229aed..d8c20c1 100644
--- a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx
@@ -92,6 +92,8 @@ namespace drawinglayer::primitive2d
        :   maMetaFileTransform(std::move(aMetaFileTransform)),
            maMetaFile(rMetaFile)
        {
            // activate callback to flush buffered decomposition content
            setCallbackSeconds(20);
        }

        bool MetafilePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx
index b7f7e72..64a18bb 100644
--- a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx
@@ -618,7 +618,8 @@ namespace drawinglayer::primitive2d
            attribute::SdrLightingAttribute aSdrLightingAttribute,
            basegfx::B2DHomMatrix aObjectTransformation,
            geometry::ViewInformation3D aViewInformation3D)
        :   mxChildren3D(std::move(aChildren3D)),
        :   BufferedDecompositionPrimitive2D(),
            mxChildren3D(std::move(aChildren3D)),
            maSdrSceneAttribute(std::move(aSdrSceneAttribute)),
            maSdrLightingAttribute(std::move(aSdrLightingAttribute)),
            maObjectTransformation(std::move(aObjectTransformation)),
@@ -627,6 +628,8 @@ namespace drawinglayer::primitive2d
            mfOldDiscreteSizeX(0.0),
            mfOldDiscreteSizeY(0.0)
        {
            // activate callback to flush buffered decomposition content
            setCallbackSeconds(45);
        }

        bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
index 8fb2a31..5de34c5 100644
--- a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
@@ -50,6 +50,8 @@ ShadowPrimitive2D::ShadowPrimitive2D(basegfx::B2DHomMatrix aShadowTransform,
    , mfLastDiscreteBlurRadius(0.0)
    , maLastClippedRange()
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(15);
}

bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
index 87e6046..e6f92f3 100644
--- a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
@@ -39,6 +39,8 @@ SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& 
    , mfLastDiscreteSoftRadius(0.0)
    , maLastClippedRange()
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(15);
}

bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/include/drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx b/include/drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx
index a1cfee3..c3bc54e 100644
--- a/include/drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx
+++ b/include/drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx
@@ -21,6 +21,7 @@

#include <drawinglayer/drawinglayerdllapi.h>
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <salhelper/timer.hxx>

namespace drawinglayer::primitive2d
{
@@ -35,25 +36,32 @@ namespace drawinglayer::primitive2d
class DRAWINGLAYER_DLLPUBLIC BufferedDecompositionGroupPrimitive2D : public GroupPrimitive2D
{
private:
    // exclusive helper for Primitive2DFlusher
    friend void flushBufferedDecomposition(BufferedDecompositionGroupPrimitive2D&);

    /// a sequence used for buffering the last create2DDecomposition() result
    Primitive2DContainer maBuffered2DDecomposition;

    /// offer callback mechanism to flush buffered content timer-based
    ::rtl::Reference<::salhelper::Timer> maCallbackTimer;
    sal_uInt16 maCallbackSeconds;

protected:
    /// identical to BufferedDecompositionPrimitive2D, see there please
    const Primitive2DContainer& getBuffered2DDecomposition() const
    {
        return maBuffered2DDecomposition;
    }
    void setBuffered2DDecomposition(Primitive2DContainer&& rNew)
    {
        maBuffered2DDecomposition = std::move(rNew);
    }
    const Primitive2DContainer& getBuffered2DDecomposition() const;
    void setBuffered2DDecomposition(Primitive2DContainer&& rNew);

    /// method which is to be used to implement the local decomposition of a 2D group primitive.
    virtual void
    create2DDecomposition(Primitive2DContainer& rContainer,
                          const geometry::ViewInformation2D& rViewInformation) const = 0;

    // when changing from null (which is inactive) to a count of seconds, the
    // callback mechanism to flush buffered content timer-based will be activated.
    // it is protected since the idea is that this gets called in the constructor
    // of derived classes.
    void setCallbackSeconds(sal_uInt16 nNew) { maCallbackSeconds = nNew; }

public:
    /// constructor/destructor. For GroupPrimitive2D we need the child parameter, too.
    BufferedDecompositionGroupPrimitive2D(Primitive2DContainer&& aChildren);
diff --git a/include/drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx b/include/drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx
index 8087a65..68d40e0 100644
--- a/include/drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx
+++ b/include/drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx
@@ -22,6 +22,7 @@
#include <drawinglayer/drawinglayerdllapi.h>
#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
#include <salhelper/timer.hxx>

namespace drawinglayer::geometry
{
@@ -62,35 +63,43 @@ namespace drawinglayer::primitive2d
class DRAWINGLAYERCORE_DLLPUBLIC BufferedDecompositionPrimitive2D : public BasePrimitive2D
{
private:
    // exclusive helper for Primitive2DFlusher
    friend void flushBufferedDecomposition(BufferedDecompositionPrimitive2D&);

    /// a sequence used for buffering the last create2DDecomposition() result
    Primitive2DContainer maBuffered2DDecomposition;

    /// offer callback mechanism to flush buffered content timer-based
    ::rtl::Reference<::salhelper::Timer> maCallbackTimer;
    sal_uInt16 maCallbackSeconds;

    /// When a shadow wraps a list of primitives, this primitive wants to influence the transparency
    /// of the shadow.
    sal_uInt16 mnTransparenceForShadow = 0;
    sal_uInt16 mnTransparenceForShadow;

protected:
    /** access methods to maBuffered2DDecomposition. The usage of this methods may allow
        later thread-safe stuff to be added if needed. Only to be used by getDecomposition()
        implementations for buffering the last decomposition.
     */
    const Primitive2DContainer& getBuffered2DDecomposition() const
    {
        return maBuffered2DDecomposition;
    }
    void setBuffered2DDecomposition(Primitive2DContainer&& rNew)
    {
        maBuffered2DDecomposition = std::move(rNew);
    }
    const Primitive2DContainer& getBuffered2DDecomposition() const;
    void setBuffered2DDecomposition(Primitive2DContainer&& rNew);

    /** method which is to be used to implement the local decomposition of a 2D primitive. */
    virtual void
    create2DDecomposition(Primitive2DContainer& rContainer,
                          const geometry::ViewInformation2D& rViewInformation) const = 0;

    // when changing from null (which is inactive) to a count of seconds, the
    // callback mechanism to flush buffered content timer-based will be activated.
    // it is protected since the idea is that this gets called in the constructor
    // of derived classes.
    void setCallbackSeconds(sal_uInt16 nNew) { maCallbackSeconds = nNew; }

public:
    // constructor/destructor
    BufferedDecompositionPrimitive2D();
    virtual ~BufferedDecompositionPrimitive2D();

    /** The getDecomposition default implementation will on demand use create2DDecomposition() if
        maBuffered2DDecomposition is empty. It will set maBuffered2DDecomposition to this obtained decomposition
diff --git a/svx/source/sdr/primitive2d/sdrcustomshapeprimitive2d.cxx b/svx/source/sdr/primitive2d/sdrcustomshapeprimitive2d.cxx
index 19717e2..b582741 100644
--- a/svx/source/sdr/primitive2d/sdrcustomshapeprimitive2d.cxx
+++ b/svx/source/sdr/primitive2d/sdrcustomshapeprimitive2d.cxx
@@ -102,6 +102,8 @@ namespace drawinglayer::primitive2d
            mb3DShape(b3DShape),
            maTransform(std::move(aTransform))
        {
            // activate callback to flush buffered decomposition content
            setCallbackSeconds(10);
        }

        bool SdrCustomShapePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/svx/source/sdr/primitive2d/sdrgrafprimitive2d.cxx b/svx/source/sdr/primitive2d/sdrgrafprimitive2d.cxx
index 18bef09..67d4d03 100644
--- a/svx/source/sdr/primitive2d/sdrgrafprimitive2d.cxx
+++ b/svx/source/sdr/primitive2d/sdrgrafprimitive2d.cxx
@@ -133,6 +133,9 @@ SdrGrafPrimitive2D::SdrGrafPrimitive2D(
    , maGraphicObject(rGraphicObject)
    , maGraphicAttr(rGraphicAttr)
{
    // activate callback to flush buffered decomposition content
    setCallbackSeconds(20);

    // reset some values from GraphicAttr which are part of transformation already
    maGraphicAttr.SetRotation(0_deg10);
}