/* -*- 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.