/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/sdr/contact/displayinfo.hxx>
#include <svx/sdr/animation/animationstate.hxx>
#include <svx/sdr/contact/viewobjectcontactredirector.hxx>
#include <basegfx/color/bcolor.hxx>
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdomedia.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdotext.hxx>
#include <vcl/pdfwriter.hxx>
#include <vcl/pdfextoutdevdata.hxx>
 
using namespace com::sun::star;
 
namespace {
 
// animated extractor
 
// Necessary to filter a sequence of animated primitives from
// a sequence of primitives to find out if animated or not. The decision for
// what to decompose is hard-coded and only done for knowingly animated primitives
// to not decompose too deeply and unnecessarily. This implies that the list
// which is view-specific needs to be expanded by hand when new animated objects
// are added. This may eventually be changed to a dynamically configurable approach
// if necessary.
class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D
{
protected:
    // the found animated primitives
    drawinglayer::primitive2d::Primitive2DContainer  maPrimitive2DSequence;
 
    // text animation allowed?
    bool                                            mbTextAnimationAllowed : 1;
 
    // graphic animation allowed?
    bool                                            mbGraphicAnimationAllowed : 1;
 
    // as tooling, the process() implementation takes over API handling and calls this
    // virtual render method when the primitive implementation is BasePrimitive2D-based.
    virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override;
 
public:
    AnimatedExtractingProcessor2D(
        const drawinglayer::geometry::ViewInformation2D& rViewInformation,
        bool bTextAnimationAllowed,
        bool bGraphicAnimationAllowed);
 
    // data access
    const drawinglayer::primitive2d::Primitive2DContainer& getPrimitive2DSequence() const { return maPrimitive2DSequence; }
    drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence() { return std::move(maPrimitive2DSequence); }
};
 
AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D(
    const drawinglayer::geometry::ViewInformation2D& rViewInformation,
    bool bTextAnimationAllowed,
    bool bGraphicAnimationAllowed)
:   drawinglayer::processor2d::BaseProcessor2D(rViewInformation),
    mbTextAnimationAllowed(bTextAnimationAllowed),
    mbGraphicAnimationAllowed(bGraphicAnimationAllowed)
{
}
 
void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate)
{
    // known implementation, access directly
    switch(rCandidate.getPrimitive2DID())
    {
        // add and accept animated primitives directly, no need to decompose
        case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D :
        case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D :
        case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D :
        case PRIMITIVE2D_ID_ANIMATEDGRAPHICPRIMITIVE2D :
        {
            const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& rSwitchPrimitive = static_cast< const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& >(rCandidate);
 
            if((rSwitchPrimitive.isTextAnimation() && mbTextAnimationAllowed)
                || (rSwitchPrimitive.isGraphicAnimation() && mbGraphicAnimationAllowed))
            {
                const drawinglayer::primitive2d::Primitive2DReference xReference(const_cast< drawinglayer::primitive2d::BasePrimitive2D* >(&rCandidate));
                maPrimitive2DSequence.push_back(xReference);
            }
            break;
        }
 
        // decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D
        // which then produces the animation infos (all when used/needed)
        case PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D :
        case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D :
 
        // decompose SdrObjects with evtl. animated text
        case PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D :
        case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D :
 
        // #121194# With Graphic as Bitmap FillStyle, also check
        // for primitives filled with animated graphics
        case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
        case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
 
        // decompose evtl. animated text contained in MaskPrimitive2D
        // or group primitives
        case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
        case PRIMITIVE2D_ID_GROUPPRIMITIVE2D :
        {
            process(rCandidate);
            break;
        }
 
        default :
        {
            // nothing to do for the rest
            break;
        }
    }
}
 
} // end of anonymous namespace
 
namespace sdr::contact {
 
ViewObjectContact::ViewObjectContact(ObjectContact& rObjectContact, ViewContact& rViewContact)
:   mrObjectContact(rObjectContact),
    mrViewContact(rViewContact),
    maGridOffset(0.0, 0.0),
    mnActionChangedCount(0),
    mbLazyInvalidate(false)
{
    // make the ViewContact remember me
    mrViewContact.AddViewObjectContact(*this);
 
    // make the ObjectContact remember me
    mrObjectContact.AddViewObjectContact(*this);
}
 
ViewObjectContact::~ViewObjectContact()
{
    // if the object range is empty, then we have never had the primitive range change, so nothing to invalidate
    if (!maObjectRange.isEmpty())
    {
        // invalidate in view
        if(!getObjectRange().isEmpty())
        {
            GetObjectContact().InvalidatePartOfView(maObjectRange);
        }
    }
 
    // delete PrimitiveAnimation
    mpPrimitiveAnimation.reset();
 
    // take care of remembered ObjectContact. Remove from
    // OC first. The VC removal (below) CAN trigger a StopGettingViewed()
    // which (depending of its implementation) may destroy other OCs. This
    // can trigger the deletion of the helper OC of a page visualising object
    // which IS the OC of this object. Eventually StopGettingViewed() needs
    // to get asynchron later
    GetObjectContact().RemoveViewObjectContact(*this);
 
    // take care of remembered ViewContact
    GetViewContact().RemoveViewObjectContact(*this);
}
 
const basegfx::B2DRange& ViewObjectContact::getObjectRange() const
{
    if(maObjectRange.isEmpty())
    {
        const drawinglayer::geometry::ViewInformation2D& rViewInfo2D = GetObjectContact().getViewInformation2D();
        basegfx::B2DRange aTempRange = GetViewContact().getRange(rViewInfo2D);
        if (!aTempRange.isEmpty())
        {
            const_cast< ViewObjectContact* >(this)->maObjectRange = aTempRange;
        }
        else
        {
            // if range is not computed (new or LazyInvalidate objects), force it
            const DisplayInfo aDisplayInfo;
            const drawinglayer::primitive2d::Primitive2DContainer& xSequence(getPrimitive2DSequence(aDisplayInfo));
 
            if(!xSequence.empty())
            {
                const_cast< ViewObjectContact* >(this)->maObjectRange =
                    xSequence.getB2DRange(rViewInfo2D);
            }
        }
    }
 
    return maObjectRange;
}
 
void ViewObjectContact::ActionChanged()
{
    // clear cached primitives
    mxPrimitive2DSequence.clear();
    ++mnActionChangedCount;
 
    if(mbLazyInvalidate)
        return;
 
    // set local flag
    mbLazyInvalidate = true;
 
    // force ObjectRange
    getObjectRange();
 
    if(!getObjectRange().isEmpty())
    {
        // invalidate current valid range
        GetObjectContact().InvalidatePartOfView(maObjectRange);
 
        // reset gridOffset, it needs to be recalculated
        if (GetObjectContact().supportsGridOffsets())
            resetGridOffset();
        else
            maObjectRange.reset();
    }
 
    // register at OC for lazy invalidate
    GetObjectContact().setLazyInvalidate(*this);
}
 
// IASS: helper for IASS invalidates
void ViewObjectContact::ActionChangedIfDifferentPageView(SdrPageView& rSdrPageView)
{
    SdrPageView* pSdrPageView(GetObjectContact().TryToGetSdrPageView());
 
    // if there is no SdrPageView or different from given one, force
    // invalidate/repaint
    if (nullptr == pSdrPageView || pSdrPageView != &rSdrPageView)
        ActionChanged();
}
 
void ViewObjectContact::triggerLazyInvalidate()
{
    if(!mbLazyInvalidate)
        return;
 
    // reset flag
    mbLazyInvalidate = false;
 
    // force ObjectRange
    getObjectRange();
 
    if(!getObjectRange().isEmpty())
    {
        // invalidate current valid range
        GetObjectContact().InvalidatePartOfView(maObjectRange);
    }
}
 
// Take some action when new objects are inserted
void ViewObjectContact::ActionChildInserted(ViewContact& rChild)
{
    // force creation of the new VOC and trigger it's refresh, so it
    // will take part in LazyInvalidate immediately
    rChild.GetViewObjectContact(GetObjectContact()).ActionChanged();
 
    // forward action to ObjectContact
    // const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact());
    // GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange());
}
 
void ViewObjectContact::checkForPrimitive2DAnimations()
{
    // remove old one
    mpPrimitiveAnimation.reset();
 
    // check for animated primitives
    if(mxPrimitive2DSequence.empty())
        return;
 
    const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed());
    const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed());
 
    if(bTextAnimationAllowed || bGraphicAnimationAllowed)
    {
        AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(),
            bTextAnimationAllowed, bGraphicAnimationAllowed);
        aAnimatedExtractor.process(mxPrimitive2DSequence);
 
        if(!aAnimatedExtractor.getPrimitive2DSequence().empty())
        {
            // derived primitiveList is animated, setup new PrimitiveAnimation
            mpPrimitiveAnimation.reset( new sdr::animation::PrimitiveAnimation(*this, aAnimatedExtractor.extractPrimitive2DSequence()) );
        }
    }
}
 
void ViewObjectContact::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
{
    // get the view-independent Primitive from the viewContact
    drawinglayer::primitive2d::Primitive2DContainer xRetval;
    GetViewContact().getViewIndependentPrimitive2DContainer(xRetval);
 
    if(!xRetval.empty())
    {
        // handle GluePoint
        if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible())
        {
            drawinglayer::primitive2d::Primitive2DContainer xGlue(GetViewContact().createGluePointPrimitive2DSequence());
 
            if(!xGlue.empty())
            {
                xRetval.append(std::move(xGlue));
            }
        }
 
        // handle ghosted
        if(isPrimitiveGhosted(rDisplayInfo))
        {
            const basegfx::BColor aRGBWhite(1.0, 1.0, 1.0);
            const basegfx::BColorModifierSharedPtr aBColorModifier =
                std::make_shared<basegfx::BColorModifier_interpolate>(
                    aRGBWhite,
                    0.5);
            xRetval = drawinglayer::primitive2d::Primitive2DContainer{
                    new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
                        std::move(xRetval),
                        aBColorModifier)
            };
        }
    }
 
    rVisitor.visit(xRetval);
}
 
bool ViewObjectContact::isExportPDFTags() const
{
    return GetObjectContact().isExportTaggedPDF();
}
 
/** Check if we need to embed to a StructureTagPrimitive2D, too. This
    was done at ImplRenderPaintProc::createRedirectedPrimitive2DSequence before
*/
void ViewObjectContact::createStructureTag(drawinglayer::primitive2d::Primitive2DContainer & rNewPrimitiveSequence) const
{
    SdrObject *const pSdrObj(mrViewContact.TryToGetSdrObject());
 
    // Check if we need to embed to a StructureTagPrimitive2D, too. This
    // was done at ImplRenderPaintProc::createRedirectedPrimitive2DSequence before
    if (!rNewPrimitiveSequence.empty() && isExportPDFTags()
        // ISO 14289-1:2014, Clause: 7.3
        && (!pSdrObj || pSdrObj->getParentSdrObjectFromSdrObject() == nullptr))
    {
        if (nullptr != pSdrObj && !pSdrObj->IsDecorative())
        {
            vcl::PDFWriter::StructElement eElement(vcl::PDFWriter::NonStructElement);
            const SdrInventor nInventor(pSdrObj->GetObjInventor());
            const SdrObjKind nIdentifier(pSdrObj->GetObjIdentifier());
            const bool bIsTextObj(nullptr != DynCastSdrTextObj(pSdrObj));
 
            // Note: SwFlyDrawObj/SwVirtFlyDrawObj have SdrInventor::Swg - these
            // are *not* handled here because not all of them are painted
            // completely with primitives, so a tag here does not encapsulate them.
            // The tag must be created by SwTaggedPDFHelper until this is fixed.
            if ( nInventor == SdrInventor::Default )
            {
                if ( nIdentifier == SdrObjKind::Group )
                    eElement = vcl::PDFWriter::Figure;
                else if (nIdentifier == SdrObjKind::Table)
                    eElement = vcl::PDFWriter::Table;
                else if (nIdentifier == SdrObjKind::Media)
                    eElement = vcl::PDFWriter::Annot;
                else if ( nIdentifier == SdrObjKind::TitleText )
                    eElement = vcl::PDFWriter::Heading;
                else if ( nIdentifier == SdrObjKind::OutlineText )
                    eElement = vcl::PDFWriter::Division;
                else if ( !bIsTextObj || !static_cast<const SdrTextObj&>(*pSdrObj).HasText() )
                    eElement = vcl::PDFWriter::Figure;
                else
                    eElement = vcl::PDFWriter::Division;
            }
 
            if(vcl::PDFWriter::NonStructElement != eElement)
            {
                SdrPage* pSdrPage(pSdrObj->getSdrPageFromSdrObject());
 
                if(pSdrPage)
                {
                    const bool bBackground(pSdrPage->IsMasterPage());
                    const bool bImage(SdrObjKind::Graphic == pSdrObj->GetObjIdentifier());
                    // note: there must be output device here, in PDF export
                    void const* pAnchorKey(nullptr);
                    if (auto const pUserCall = pSdrObj->GetUserCall())
                    {
                        pAnchorKey = pUserCall->GetPDFAnchorStructureElementKey(*pSdrObj);
                    }
 
                    ::std::vector<sal_Int32> annotIds;
                    if (eElement == vcl::PDFWriter::Annot
                        && !static_cast<SdrMediaObj*>(pSdrObj)->getURL().isEmpty())
                    {
                        auto const pPDFExtOutDevData(GetObjectContact().GetPDFExtOutDevData());
                        assert(pPDFExtOutDevData);
                        annotIds = pPDFExtOutDevData->GetScreenAnnotIds(pSdrObj);
                    }
 
                    rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
                            new drawinglayer::primitive2d::StructureTagPrimitive2D(
                                eElement,
                                bBackground,
                                bImage,
                                false, // Decorative
                                std::move(rNewPrimitiveSequence),
                                pAnchorKey,
                                &annotIds)
                    };
                }
            }
        }
        else
        {
            // page backgrounds etc should be tagged as artifacts:
            rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
                    new drawinglayer::primitive2d::StructureTagPrimitive2D(
                        // lies to force silly VclMetafileProcessor2D to emit NonStructElement
                        vcl::PDFWriter::Division,
                        true,
                        true,
                        true, // Decorative
                        std::move(rNewPrimitiveSequence))
                };
        }
    }
}
 
drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
{
    // only some of the top-level apps are any good at reliably invalidating us (e.g. writer is not)
    SdrObject* pSdrObj(mrViewContact.TryToGetSdrObject());
 
    if (nullptr != pSdrObj && pSdrObj->getSdrModelFromSdrObject().IsVOCInvalidationIsReliable())
    {
        if (!mxPrimitive2DSequence.empty())
            return mxPrimitive2DSequence;
    }
 
    // prepare new representation
    drawinglayer::primitive2d::Primitive2DContainer xNewPrimitiveSequence;
 
    // take care of redirectors and create new list
    ViewObjectContactRedirector* pRedirector = GetObjectContact().GetViewObjectContactRedirector();
 
    if(pRedirector)
    {
        pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo, xNewPrimitiveSequence);
    }
    else
    {
        createPrimitive2DSequence(rDisplayInfo, xNewPrimitiveSequence);
    }
 
    // check and eventually embed to GridOffset transform primitive (calc only)
    if(!xNewPrimitiveSequence.empty() && GetObjectContact().supportsGridOffsets())
    {
        const basegfx::B2DVector& rGridOffset(getGridOffset());
 
        if(0.0 != rGridOffset.getX() || 0.0 != rGridOffset.getY())
        {
            const basegfx::B2DHomMatrix aTranslateGridOffset(
                basegfx::utils::createTranslateB2DHomMatrix(
                    rGridOffset));
            xNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
                    new drawinglayer::primitive2d::TransformPrimitive2D(
                        aTranslateGridOffset,
                        std::move(xNewPrimitiveSequence))
            };
        }
    }
 
    createStructureTag(xNewPrimitiveSequence);
 
    // Local up-to-date checks. New list different from local one?
    // This is the important point where it gets decided if the current or the new
    // representation gets used. This is important for performance, since the
    // current representation contains possible precious decompositions. That
    // comparisons triggers exactly if something in the object visualization
    // has changed.
    // Note: That is the main reason for BasePrimitive2D::operator== at all. I
    // have alternatively tried to invalidate the local representation on object
    // change, but that is simply not reliable.
    // Note2: I did that once in aw080, the lost CWS, and it worked well enough
    // so that I could remove *all* operator== from all derivations of
    // BasePrimitive2D, so it can be done again (with the needed resources)
    if(mxPrimitive2DSequence != xNewPrimitiveSequence)
    {
        // has changed, copy content
        const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = std::move(xNewPrimitiveSequence);
 
        // check for animated stuff
        const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations();
 
        // always update object range when PrimitiveSequence changes
        const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
        const_cast< ViewObjectContact* >(this)->maObjectRange = mxPrimitive2DSequence.getB2DRange(rViewInformation2D);
    }
 
    // return current Primitive2DContainer
    return mxPrimitive2DSequence;
}
 
bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const
{
    // default: always visible
    return true;
}
 
bool ViewObjectContact::isPrimitiveGhosted(const DisplayInfo& rDisplayInfo) const
{
    // default: standard check
    return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive());
}
 
void ViewObjectContact::getPrimitive2DSequenceHierarchy(DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
{
    // check model-view visibility
    if(!isPrimitiveVisible(rDisplayInfo))
        return;
 
    getPrimitive2DSequence(rDisplayInfo);
    if(mxPrimitive2DSequence.empty())
        return;
 
    // get ranges
    const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
    // tdf#147164 cannot use maObjectRange here, it is unreliable
    const basegfx::B2DRange aObjectRange(mxPrimitive2DSequence.getB2DRange(rViewInformation2D));
    const basegfx::B2DRange& aViewRange(rViewInformation2D.getViewport());
 
    // check geometrical visibility
    bool bVisible = aViewRange.isEmpty() || aViewRange.overlaps(aObjectRange);
    if(!bVisible)
        return;
 
    // temporarily take over the mxPrimitive2DSequence, in case it gets invalidated while we want to iterate over it
    auto tmp = std::move(const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence);
    int nPrevCount = mnActionChangedCount;
 
    rVisitor.visit(tmp);
 
    // if we received ActionChanged() calls while walking the primitives, then leave it empty, otherwise move it back
    if (mnActionChangedCount == nPrevCount)
        const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence = std::move(tmp);
}
 
void ViewObjectContact::getPrimitive2DSequenceSubHierarchy(DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
{
    ViewContact& rViewContact = GetViewContact();
    const sal_uInt32 nSubHierarchyCount(rViewContact.GetObjectCount());
 
    for(sal_uInt32 a(0); a < nSubHierarchyCount; a++)
        rViewContact.getPrimitive2DSequenceHierarchyOfIndex(a, rDisplayInfo, GetObjectContact(), rVisitor);
}
 
// Support getting a GridOffset per object and view for non-linear ViewToDevice
// transformation (calc). On-demand created by delegating to the ObjectContact
// (->View) that has then all needed information
const basegfx::B2DVector& ViewObjectContact::getGridOffset() const
{
    if (GetObjectContact().supportsGridOffsets())
    {
        if (fabs(maGridOffset.getX()) > 1000.0)
        {
            // Huge offsets are a hint for error -> usually the conditions for
            // calculation have changed. E.g. - I saw errors with +/-5740, that
            // was in the environment of massive external UNO API using LO as
            // target.
            // If conditions for this calculation change, it is usually required to call
            // - ViewObjectContact::resetGridOffset(), or
            // - ObjectContact::resetAllGridOffsets() or
            // - ScDrawView::resetGridOffsetsForAllSdrPageViews()
            // as it is done e.g. when zoom changes (see ScDrawView::RecalcScale()).
            // Theoretically these resets have to be done for any precondition
            // changed that is used in the calculation of that value (see
            // ScDrawView::calculateGridOffsetForSdrObject).
            // This is not complete and would be hard to do so.
            // Since it is just a buffered value and re-calculation is not
            // expensive (linear O(n)) we can just reset suspicious values here.
            // Hopefully - when that non-linear ViewTransformation problem for
            // the calc-view gets solved one day - all this can be removed
            // again. For now, let's just reset here and force re-calculation.
            // Add a SAL_WARN to inform about this.
            SAL_WARN("svx", "Suspicious GridOffset value resetted (!)");
            const_cast<ViewObjectContact*>(this)->maGridOffset.setX(0.0);
            const_cast<ViewObjectContact*>(this)->maGridOffset.setY(0.0);
        }
 
        if(0.0 == maGridOffset.getX() && 0.0 == maGridOffset.getY() && GetObjectContact().supportsGridOffsets())
        {
            // create on-demand
            GetObjectContact().calculateGridOffsetForViewObjectContact(const_cast<ViewObjectContact*>(this)->maGridOffset, *this);
        }
    }
 
    return maGridOffset;
}
 
void ViewObjectContact::resetGridOffset()
{
    // reset buffered GridOffset itself
    maGridOffset.setX(0.0);
    maGridOffset.setY(0.0);
 
    // also reset sequence to get a re-calculation when GridOffset changes
    mxPrimitive2DSequence.clear();
    maObjectRange.reset();
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'getObjectRange' is required to be utilized.
↑ V530 The return value of function 'getObjectRange' is required to be utilized.
↑ V530 The return value of function 'getPrimitive2DSequence' is required to be utilized.
↑ V1053 Calling the 'createPrimitive2DSequence' virtual function indirectly in the destructor may lead to unexpected result at runtime. Check lines: 'viewobjectcontact.cxx:174', 'viewobjectcontact.cxx:209', 'viewobjectcontact.cxx:481', 'viewobjectcontact.hxx:84'.
↑ V547 Expression 'mnActionChangedCount == nPrevCount' is always true.