    /* -*- 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 <com/sun/star/util/XChangesNotifier.hpp>

    #include <basegfx/matrix/b2dhommatrix.hxx>
    #include <basegfx/polygon/b2dpolygontools.hxx>
    #include <basegfx/matrix/b2dhommatrixtools.hxx>

    #include <sfx2/viewfrm.hxx>
    #include <sfx2/dispatch.hxx>

    #include <svx/sdr/overlay/overlaymanager.hxx>
    #include <svx/sdr/overlay/overlaypolypolygon.hxx>
    #include <svx/svdpagv.hxx>
    #include <svx/sdrpagewindow.hxx>
    #include <svx/sdrpaintwindow.hxx>
    #include <svx/svdopath.hxx>
    #include <svx/xlndsit.hxx>
    #include <svx/xlnclit.hxx>
    #include <svx/xlnstit.hxx>
    #include <svx/xlnedit.hxx>
    #include <svx/xlnstwit.hxx>
    #include <svx/xlnedwit.hxx>
    #include <svx/xlnstcit.hxx>
    #include <svx/xlnedcit.hxx>
    #include <svx/xlntrit.hxx>
    #include <svx/svxids.hrc>
    #include <svx/polypolygoneditor.hxx>
    #include <svx/svddrgmt.hxx>

    #include "CustomAnimationPane.hxx"
    #include <View.hxx>
    #include "motionpathtag.hxx"
    #include <sdpage.hxx>
    #include <ViewShell.hxx>
    #include <app.hrc>
    #include <Window.hxx>

    #include <svx/sdr/contact/viewcontact.hxx>
    #include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx>

    using sdr::PolyPolygonEditor;
    using namespace ::com::sun::star;
    using namespace ::com::sun::star::uno;
    using namespace ::com::sun::star::lang;
    using namespace ::com::sun::star::util;
    using namespace ::com::sun::star::drawing;

    namespace sd
    {

    const sal_uInt32 SMART_TAG_HDL_NUM = SAL_MAX_UINT32;
    static const int DRGPIX     = 2;                               // Drag MinMove in Pixel

    class PathDragMove : public SdrDragMove
    {
    private:
        basegfx::B2DPolyPolygon         maPathPolyPolygon;

    protected:
        virtual void createSdrDragEntries() override;

    public:
        PathDragMove(SdrDragView& rNewView,
            const rtl::Reference <MotionPathTag >& xTag,
            const basegfx::B2DPolyPolygon& rPathPolyPolygon)
        :   SdrDragMove(rNewView),
            maPathPolyPolygon(rPathPolyPolygon),
            mxTag( xTag )
        {}

        PathDragMove(SdrDragView& rNewView,
            const rtl::Reference <MotionPathTag >& xTag)
        :   SdrDragMove(rNewView),
            maPathPolyPolygon(),
            mxTag( xTag )
        {}

        virtual bool BeginSdrDrag() override;
        virtual bool EndSdrDrag(bool bCopy) override;

        rtl::Reference <MotionPathTag > mxTag;
    };

    void PathDragMove::createSdrDragEntries()
    {
        // call parent
        SdrDragMove::createSdrDragEntries();

        if(maPathPolyPolygon.count())
        {
            addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
        }
    }

    bool PathDragMove::BeginSdrDrag()
    {
        if( mxTag.is() )
        {
            SdrPathObj* pPathObj = mxTag->getPathObj();
            if( pPathObj )
            {
                DragStat().SetActionRect(pPathObj->GetCurrentBoundRect());
            }
        }
        Show();
        return true;
    }

    bool PathDragMove::EndSdrDrag(bool /*bCopy*/)
    {
        Hide();
        if( mxTag.is() )
            mxTag->MovePath( DragStat().GetDX(), DragStat().GetDY() );
        return true;
    }

    class PathDragResize : public SdrDragResize
    {
    private:
        basegfx::B2DPolyPolygon         maPathPolyPolygon;

    protected:
        virtual void createSdrDragEntries() override;

    public:
        PathDragResize(SdrDragView& rNewView,
            const rtl::Reference <MotionPathTag >& xTag,
            const basegfx::B2DPolyPolygon& rPathPolyPolygon)
        :   SdrDragResize(rNewView),
            maPathPolyPolygon(rPathPolyPolygon),
            mxTag( xTag )
        {}

        PathDragResize(SdrDragView& rNewView,
            const rtl::Reference <MotionPathTag >& xTag)
        :   SdrDragResize(rNewView),
            maPathPolyPolygon(),
            mxTag( xTag )
        {}

        virtual bool EndSdrDrag(bool bCopy) override;
        rtl::Reference <MotionPathTag > mxTag;
    };

    void PathDragResize::createSdrDragEntries()
    {
        // call parent
        SdrDragResize::createSdrDragEntries();

        if(maPathPolyPolygon.count())
        {
            addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
        }
    }

    bool PathDragResize::EndSdrDrag(bool /*bCopy*/)
    {
        Hide();
        if( mxTag.is() )
        {
            SdrPathObj* pPathObj = mxTag->getPathObj();
            if( pPathObj )
            {
                const Point aRef( DragStat().GetRef1() );
                basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-aRef.X(), -aRef.Y()));
                aTrans.scale(double(aXFact), double(aYFact));
                aTrans.translate(aRef.X(), aRef.Y());
                basegfx::B2DPolyPolygon aDragPoly(pPathObj->GetPathPoly());
                aDragPoly.transform(aTrans);
                pPathObj->SetPathPoly( aDragPoly );
            }
        }
        return true;
    }

    class PathDragObjOwn : public SdrDragObjOwn
    {
    private:
        basegfx::B2DPolyPolygon         maPathPolyPolygon;

    protected:
        virtual void createSdrDragEntries() override;

    public:
        PathDragObjOwn(SdrDragView& rNewView,
            const basegfx::B2DPolyPolygon& rPathPolyPolygon)
        :   SdrDragObjOwn(rNewView),
            maPathPolyPolygon(rPathPolyPolygon)
        {}

        explicit PathDragObjOwn(SdrDragView& rNewView)
        :   SdrDragObjOwn(rNewView),
            maPathPolyPolygon()
        {}

        virtual bool EndSdrDrag(bool bCopy) override;
    };

    void PathDragObjOwn::createSdrDragEntries()
    {
        // call parent
        SdrDragObjOwn::createSdrDragEntries();

        if(maPathPolyPolygon.count())
        {
            addSdrDragEntry(std::unique_ptr<SdrDragEntry>(new SdrDragEntryPolyPolygon(maPathPolyPolygon)));
        }
    }

    bool PathDragObjOwn::EndSdrDrag(bool /*bCopy*/)
    {
        Hide();

        SdrObject* pObj = GetDragObj();

        if(pObj && pObj->applySpecialDrag(DragStat()))
        {
            pObj->SetChanged();
            pObj->BroadcastObjectChange();
            return true;
        }
        else
        {
            return false;
        }
    }

    class SdPathHdl : public SmartHdl
    {
    public:
        SdPathHdl( const SmartTagReference& xTag, SdrPathObj* mpPathObj );

        virtual void CreateB2dIAObject() override;
        virtual bool IsFocusHdl() const override;
        virtual bool isMarkable() const override;

    private:
        SdrPathObj* mpPathObj;
    };

    SdPathHdl::SdPathHdl( const SmartTagReference& xTag, SdrPathObj* pPathObj )
    : SmartHdl( xTag, pPathObj->GetCurrentBoundRect().TopLeft(), SdrHdlKind::SmartTag )
    , mpPathObj( pPathObj )
    {
    }

    void SdPathHdl::CreateB2dIAObject()
    {
        // first throw away old one
        GetRidOfIAObject();

        if(pHdlList)
        {
            SdrMarkView* pView = pHdlList->GetView();

            if(pView && !pView->areMarkHandlesHidden())
            {
                SdrPageView* pPageView = pView->GetSdrPageView();

                if(pPageView)
                {
                    for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++)
                    {
                        const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);

                        if(rPageWindow.GetPaintWindow().OutputToWindow())
                        {
                            rtl::Reference< sdr::overlay::OverlayManager > xManager = rPageWindow.GetOverlayManager();
                            if (xManager.is() && mpPathObj)
                            {
                                const sdr::contact::ViewContact& rVC = mpPathObj->GetViewContact();
                                const drawinglayer::primitive2d::Primitive2DContainer aSequence = rVC.getViewIndependentPrimitive2DContainer();
                                std::unique_ptr<sdr::overlay::OverlayObject> pNew(new sdr::overlay::OverlayPrimitive2DSequenceObject(aSequence));

                                xManager->add(*pNew);
                                maOverlayGroup.append(std::move(pNew));
                            }
                        }
                    }
                }
            }
        }
    }

    bool SdPathHdl::IsFocusHdl() const
    {
        return false;
    }

    bool SdPathHdl::isMarkable() const
    {
        return false;
    }

    MotionPathTag::MotionPathTag( CustomAnimationPane& rPane, ::sd::View& rView, const CustomAnimationEffectPtr& pEffect )
    : SmartTag( rView )
    , mrPane( rPane )
    , mpEffect( pEffect )
    , mxOrigin( pEffect->getTargetShape() )
    , msLastPath( pEffect->getPath() )
    , mbInUpdatePath( false )
    {
        mpPathObj = mpEffect->createSdrPathObjFromPath(rView.getSdrModelFromSdrView());
        mxPolyPoly = mpPathObj->GetPathPoly();
        if (mxOrigin.is())
            maOriginPos = mxOrigin->getPosition();

        XDash aDash( css::drawing::DashStyle_RECT, 1, 80, 1, 80, 80);
        OUString aEmpty( "?" );
        mpPathObj->SetMergedItem( XLineDashItem( aEmpty, aDash ) );
        mpPathObj->SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) );
        mpPathObj->SetMergedItem( XLineColorItem(aEmpty, COL_GRAY) );
        mpPathObj->SetMergedItem( XFillStyleItem( drawing::FillStyle_NONE ) );

        ::basegfx::B2DPolygon aStartArrow;
        aStartArrow.append(::basegfx::B2DPoint(20.0, 0.0));
        aStartArrow.append(::basegfx::B2DPoint(0.0,  0.0));
        aStartArrow.append(::basegfx::B2DPoint(10.0, 30.0));
        aStartArrow.setClosed(true);
        mpPathObj->SetMergedItem(XLineStartItem(aEmpty,::basegfx::B2DPolyPolygon(aStartArrow)));
        mpPathObj->SetMergedItem(XLineStartWidthItem(400));
        mpPathObj->SetMergedItem(XLineStartCenterItem(true));

        updatePathAttributes();

        mpPathObj->SetMergedItem(XLineTransparenceItem(50));

        mpMark.reset(new SdrMark( mpPathObj, mrView.GetSdrPageView() ));

        mpPathObj->AddListener( *this );

        Reference< XChangesNotifier > xNotifier( mpEffect->getNode(), UNO_QUERY );
        if( xNotifier.is() )
        {
            xNotifier->addChangesListener( this );
        }
    }

    MotionPathTag::~MotionPathTag()
    {
        DBG_ASSERT( mpPathObj == nullptr, "sd::MotionPathTag::~MotionPathTag(), dispose me first!" );
        Dispose();
    }

    void MotionPathTag::updatePathAttributes()
    {
        ::basegfx::B2DPolygon aCandidate;
        if( mxPolyPoly.count() )
        {
            aCandidate = mxPolyPoly.getB2DPolygon(0);
            ::basegfx::utils::checkClosed( aCandidate );
        }

        if( !aCandidate.isClosed() )
        {
            ::basegfx::B2DPolygon aEndArrow;
            aEndArrow.append(::basegfx::B2DPoint(10.0, 0.0));
            aEndArrow.append(::basegfx::B2DPoint(0.0, 30.0));
            aEndArrow.append(::basegfx::B2DPoint(20.0, 30.0));
            aEndArrow.setClosed(true);
            mpPathObj->SetMergedItem(XLineEndItem("?",::basegfx::B2DPolyPolygon(aEndArrow)));
            mpPathObj->SetMergedItem(XLineEndWidthItem(400));
            mpPathObj->SetMergedItem(XLineEndCenterItem(true));
        }
        else
        {
            mpPathObj->SetMergedItem(XLineEndItem());
        }
    }

    void MotionPathTag::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
    {
        if( mpPathObj && !mbInUpdatePath && dynamic_cast< const SdrHint* >( &rHint ) && (mpEffect.get() != nullptr) )
        {
            if( mxPolyPoly != mpPathObj->GetPathPoly() )
            {
                mbInUpdatePath = true;
                mxPolyPoly = mpPathObj->GetPathPoly();
                rtl::Reference< MotionPathTag > xTag( this );
                mrPane.updatePathFromMotionPathTag( xTag );
                msLastPath = mpEffect->getPath();
                updatePathAttributes();
                mbInUpdatePath = false;
            }
        }
    }

    void MotionPathTag::MovePath( int nDX, int nDY )
    {
        if( mpPathObj )
        {
            mpPathObj->Move( Size( nDX, nDY ) );
            mrView.updateHandles();
        }
    }

    /** returns true if the MotionPathTag handled the event. */
    bool MotionPathTag::MouseButtonDown( const MouseEvent& rMEvt, SmartHdl& rHdl )
    {
        if( !mpPathObj )
            return false;

        if( !isSelected() )
        {
            SmartTagReference xTag( this );
            mrView.getSmartTags().select( xTag );
            selectionChanged();
            return true;
        }
        else
        {
            if( rMEvt.IsLeft() && (rMEvt.GetClicks() == 2) )
            {
                mrView.GetViewShell()->GetViewFrame()->GetDispatcher()->Execute(SID_BEZIER_EDIT, SfxCallMode::ASYNCHRON);
                return true;
            }
            else if( rMEvt.IsLeft() )
            {
                OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow();
                Point aMDPos( pOut->PixelToLogic( rMEvt.GetPosPixel() ) );

                if( !mrView.IsFrameDragSingles() && mrView.IsInsObjPointMode() && (rHdl.GetObjHdlNum() == SMART_TAG_HDL_NUM) )
                {
                    // insert a point in edit mode
                    const bool bNewObj = rMEvt.IsMod1();

                    mrView.BrkAction();

                    Point aPt(aMDPos); // - pMarkedPV->GetOffset());

                    if(bNewObj)
                        aPt = mrView.GetSnapPos(aPt,mrView.GetSdrPageView());

                    bool bClosed0(mpPathObj->IsClosedObj());

                    sal_uInt32 nInsPointNum = mpPathObj->NbcInsPointOld(aPt, bNewObj);

                    if(bClosed0 != mpPathObj->IsClosedObj())
                    {
                        // Obj was closed implicit
                        // object changed
                        mpPathObj->SetChanged();
                        mpPathObj->BroadcastObjectChange();
                    }

                    if(0xffffffff != nInsPointNum)
                    {
                        mrView.UnmarkAllPoints();
                        mrView.updateHandles();

                        bool bRet = mrView.BegDragObj(aMDPos, pOut, mrView.GetHdl(nInsPointNum+1), 0, new PathDragObjOwn( mrView ) );

                        if (bRet)
                        {
                            const_cast< SdrDragStat* >( &mrView.GetDragStat() )->SetMinMoved();
                            mrView.MovDragObj(aMDPos);
                        }
                    }
                    return true;
                }
                else
                {
                    SmartHdl* pHdl = &rHdl;
                    if (!mrView.IsPointMarked(*pHdl) || rMEvt.IsShift())
                    {
                        if (!rMEvt.IsShift())
                        {
                            mrView.UnmarkAllPoints();
                            pHdl = dynamic_cast< SmartHdl* >( mrView.PickHandle(aMDPos) );
                        }
                        else
                        {
                            if (mrView.IsPointMarked(*pHdl) )
                            {
                                mrView.UnmarkPoint(*pHdl);
                                pHdl = nullptr;
                            }
                            else
                            {
                                pHdl = dynamic_cast< SmartHdl* >( mrView.PickHandle(aMDPos) );
                            }
                        }

                        if (pHdl)
                            mrView.MarkPoint(*pHdl);
                    }

                    if( pHdl && !rMEvt.IsRight() )
                    {
                        mrView.BrkAction();
                        const sal_uInt16 nDrgLog = static_cast<sal_uInt16>(pOut->PixelToLogic(Size(DRGPIX,0)).Width());

                        rtl::Reference< MotionPathTag > xTag( this );
                        SdrDragMethod* pDragMethod;

                        // #i95646# add DragPoly as geometry to each local SdrDragMethod to be able
                        // to create the needed local SdrDragEntry for it in createSdrDragEntries()
                        const basegfx::B2DPolyPolygon aDragPoly(mpPathObj->GetPathPoly());

                        if( (pHdl->GetKind() == SdrHdlKind::Move) || (pHdl->GetKind() == SdrHdlKind::SmartTag) )
                        {
                            pDragMethod = new PathDragMove( mrView, xTag, aDragPoly );
                            pHdl->SetPos( aMDPos );
                        }
                        else if( pHdl->GetKind() == SdrHdlKind::Poly )
                        {
                            pDragMethod = new PathDragObjOwn( mrView, aDragPoly );
                        }
                        else
                        {
                            pDragMethod = new PathDragResize( mrView, xTag, aDragPoly );
                        }

                        mrView.BegDragObj(aMDPos, nullptr, pHdl, nDrgLog, pDragMethod );
                    }
                    return true;
                }
            }
        }

        return false;
    }

    /** returns true if the SmartTag consumes this event. */
    bool MotionPathTag::KeyInput( const KeyEvent& rKEvt )
    {
        if( !mpPathObj )
            return false;

        sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
        switch( nCode )
        {
        case KEY_DELETE:
            return OnDelete();

        case KEY_DOWN:
        case KEY_UP:
        case KEY_LEFT:
        case KEY_RIGHT:
            return OnMove( rKEvt );

        case KEY_ESCAPE:
        {
            SmartTagReference xThis( this );
            mrView.getSmartTags().deselect();
            return true;
        }

        case KEY_TAB:
            return OnTabHandles( rKEvt );

        case KEY_SPACE:
            return OnMarkHandle( rKEvt );

        default:
            break;
        }
        return false;
    }

    bool MotionPathTag::OnDelete()
    {
        mrPane.remove( mpEffect );
        return true;
    }

    bool MotionPathTag::OnTabHandles( const KeyEvent& rKEvt )
    {
        if(rKEvt.GetKeyCode().IsMod1() || rKEvt.GetKeyCode().IsMod2())
        {
            const SdrHdlList& rHdlList = mrView.GetHdlList();
            bool bForward(!rKEvt.GetKeyCode().IsShift());

            const_cast<SdrHdlList&>(rHdlList).TravelFocusHdl(bForward);

            // guarantee visibility of focused handle
            SdrHdl* pHdl = rHdlList.GetFocusHdl();

            if(pHdl)
            {
                Window* pWindow = mrView.GetViewShell()->GetActiveWindow();
                if( pWindow )
                {
                    Point aHdlPosition(pHdl->GetPos());
                    ::tools::Rectangle aVisRect(aHdlPosition - Point(100, 100), Size(200, 200));
                    mrView.MakeVisible(aVisRect, *pWindow);
                }
            }

            return true;
        }

        return false;
    }

    bool MotionPathTag::OnMarkHandle( const KeyEvent& rKEvt )
    {
        const SdrHdlList& rHdlList = mrView.GetHdlList();
        SdrHdl* pHdl = rHdlList.GetFocusHdl();

        if(pHdl && pHdl->GetKind() == SdrHdlKind::Poly )
        {
            // rescue ID of point with focus
            sal_uInt32 nPol(pHdl->GetPolyNum());
            sal_uInt32 nPnt(pHdl->GetPointNum());

            if(mrView.IsPointMarked(*pHdl))
            {
                if(rKEvt.GetKeyCode().IsShift())
                {
                    mrView.UnmarkPoint(*pHdl);
                }
            }
            else
            {
                if(!rKEvt.GetKeyCode().IsShift())
                {
                    mrView.UnmarkAllPoints();
                }
                mrView.MarkPoint(*pHdl);
            }

            if(nullptr == rHdlList.GetFocusHdl())
            {
                // restore point with focus
                SdrHdl* pNewOne = nullptr;

                for(size_t a = 0; !pNewOne && a < rHdlList.GetHdlCount(); ++a)
                {
                    SdrHdl* pAct = rHdlList.GetHdl(a);

                    if(pAct && pAct->GetKind() == SdrHdlKind::Poly && pAct->GetPolyNum() == nPol && pAct->GetPointNum() == nPnt)
                        pNewOne = pAct;
                }

                if(pNewOne)
                    const_cast<SdrHdlList&>(rHdlList).SetFocusHdl(pNewOne);
            }
        }

        return true;
    }

    bool MotionPathTag::OnMove( const KeyEvent& rKEvt )
    {
        long nX = 0;
        long nY = 0;

        switch( rKEvt.GetKeyCode().GetCode() )
        {
        case KEY_UP:    nY = -1; break;
        case KEY_DOWN:  nY =  1; break;
        case KEY_LEFT:  nX = -1; break;
        case KEY_RIGHT: nX =  1; break;
        default: break;
        }

        if(rKEvt.GetKeyCode().IsMod2())
        {
            OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow();
            Size aLogicSizeOnePixel = pOut ? pOut->PixelToLogic(Size(1,1)) : Size(100, 100);
            nX *= aLogicSizeOnePixel.Width();
            nY *= aLogicSizeOnePixel.Height();
        }
        else
        {
            // old, fixed move distance
            nX *= 100;
            nY *= 100;
        }

        if( nX || nY )
        {
            // in point edit mode move the handle with the focus
            const SdrHdlList& rHdlList = mrView.GetHdlList();
            SdrHdl* pHdl = rHdlList.GetFocusHdl();

            if(pHdl)
            {
                // now move the Handle (nX, nY)
                Point aStartPoint(pHdl->GetPos());
                Point aEndPoint(pHdl->GetPos() + Point(nX, nY));

                // start dragging
                rtl::Reference< MotionPathTag > xTag( this );
                SdrDragMethod* pDragMethod = nullptr;
                if( (pHdl->GetKind() == SdrHdlKind::Move) || (pHdl->GetKind() == SdrHdlKind::SmartTag) )
                {
                    pDragMethod = new PathDragMove( mrView, xTag );
                }
                else if( pHdl->GetKind() == SdrHdlKind::Poly )
                {
                    pDragMethod = new PathDragObjOwn( mrView );
                }
                else if( pHdl->GetKind() != SdrHdlKind::BezierWeight )
                {
                    pDragMethod = new PathDragResize( mrView, xTag );
                }
                mrView.BegDragObj(aStartPoint, nullptr, pHdl, 0, pDragMethod);

                if(mrView.IsDragObj())
                {
                    bool bWasNoSnap = mrView.GetDragStat().IsNoSnap();
                    bool bWasSnapEnabled = mrView.IsSnapEnabled();

                    // switch snapping off
                    if(!bWasNoSnap)
                        const_cast<SdrDragStat&>(mrView.GetDragStat()).SetNoSnap();
                    if(bWasSnapEnabled)
                        mrView.SetSnapEnabled(false);

                    mrView.MovAction(aEndPoint);
                    mrView.EndDragObj();

                    // restore snap
                    if(!bWasNoSnap)
                        const_cast<SdrDragStat&>(mrView.GetDragStat()).SetNoSnap(bWasNoSnap);
                    if(bWasSnapEnabled)
                        mrView.SetSnapEnabled(bWasSnapEnabled);
                }
            }
            else
            {
                // move the path
                MovePath( nX, nY );
            }
        }

        return true;
    }

    sal_Int32 MotionPathTag::GetMarkablePointCount() const
    {
        if( mpPathObj && isSelected() )
        {
            return mpPathObj->GetPointCount();
        }
        else
        {
            return 0;
        }
    }

    sal_Int32 MotionPathTag::GetMarkedPointCount() const
    {
        if( mpMark )
        {
            const SdrUShortCont& rPts = mpMark->GetMarkedPoints();
            return rPts.size();
        }
        else
        {
            return 0;
        }
    }

    bool MotionPathTag::MarkPoint(SdrHdl& rHdl, bool bUnmark )
    {
        bool bRet=false;
        if( mpPathObj && mrView.IsPointMarkable( rHdl ) && (rHdl.GetKind() != SdrHdlKind::SmartTag) )
        {
            SmartHdl* pSmartHdl = dynamic_cast< SmartHdl* >( &rHdl );
            if( pSmartHdl && pSmartHdl->getTag().get() == this )
            {
                if (mrView.MarkPointHelper(&rHdl,mpMark.get(),bUnmark))
                {
                    mrView.MarkListHasChanged();
                    bRet=true;
                }
            }
        }
        return bRet;
    }

    bool MotionPathTag::MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark )
    {
        bool bChgd=false;

        if( mpPathObj && isSelected() )
        {
            size_t nHdlNum = mrView.GetHdlList().GetHdlCount();
            if ( nHdlNum <= 1 )
                return false;

            while( --nHdlNum > 0 )
            {
                SmartHdl* pHdl = dynamic_cast< SmartHdl* >( mrView.GetHdl( nHdlNum ) );

                if( pHdl && (pHdl->getTag().get() == this) && mrView.IsPointMarkable(*pHdl) && pHdl->IsSelected() == bUnmark)
                {
                    Point aPos(pHdl->GetPos());
                    if( pRect==nullptr || pRect->IsInside(aPos))
                    {
                        if( mrView.MarkPointHelper(pHdl,mpMark.get(),bUnmark) )
                            bChgd=true;
                    }
                }
            }

            if(bChgd)
                mrView.MarkListHasChanged();
        }

        return bChgd;
    }

    bool MotionPathTag::getContext( SdrViewContext& rContext )
    {
        if( mpPathObj && isSelected() && !mrView.IsFrameDragSingles() )
        {
            rContext = SdrViewContext::PointEdit;
            return true;
        }
        else
        {
            return false;
        }
    }

    void MotionPathTag::CheckPossibilities()
    {
        if( mpPathObj )
        {
            if( isSelected() )
            {
                mrView.SetMoveAllowed( true );
                mrView.SetMoveProtected( false );
                mrView.SetResizeFreeAllowed( true );
                mrView.SetResizePropAllowed( true );
                mrView.SetResizeProtected( false );

                if( !mrView.IsFrameDragSingles() )
                {
                    bool b1stSmooth(true);
                    bool b1stSegm(true);
                    bool bCurve(false);
                    bool bSmoothFuz(false);
                    bool bSegmFuz(false);
                    basegfx::B2VectorContinuity eSmooth = basegfx::B2VectorContinuity::NONE;

                    mrView.CheckPolyPossibilitiesHelper( mpMark.get(), b1stSmooth, b1stSegm, bCurve, bSmoothFuz, bSegmFuz, eSmooth );
                }
            }
        }
    }

    void MotionPathTag::addCustomHandles( SdrHdlList& rHandlerList )
    {
        if( mpPathObj )
        {
            css::awt::Point aPos;
            if (mxOrigin.is())
                aPos = mxOrigin->getPosition();
            if( (aPos.X != maOriginPos.X) || (aPos.Y != maOriginPos.Y) )
            {
                const basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(
                    aPos.X - maOriginPos.X, aPos.Y - maOriginPos.Y));
                mxPolyPoly.transform( aTransform );
                mpPathObj->SetPathPoly( mxPolyPoly );
                maOriginPos = aPos;
            }

            SmartTagReference xThis( this );
            SdPathHdl* pHdl = new SdPathHdl( xThis, mpPathObj );
            pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM );
            pHdl->SetPageView( mrView.GetSdrPageView() );

            pHdl->SetObj(mpPathObj);
            rHandlerList.AddHdl( pHdl );

            if( isSelected() )
            {
                mrView.GetSdrPageView()->SetHasMarkedObj(true);

                if( !mrView.IsFrameDragSingles() )
                {
                    SdrHdlList aTemp( rHandlerList.GetView() );
                    mpPathObj->AddToHdlList( aTemp );
                    const SdrUShortCont& rMrkPnts = mpMark->GetMarkedPoints();

                    for( size_t nHandle = 0; nHandle < aTemp.GetHdlCount(); ++nHandle )
                    {
                        SdrHdl* pTempHdl = aTemp.GetHdl( nHandle );

                        SmartHdl* pSmartHdl = new SmartHdl( xThis, mpPathObj, pTempHdl->GetPos(), pTempHdl->GetKind() );
                        pSmartHdl->SetObjHdlNum( static_cast<sal_uInt32>(nHandle) );
                        pSmartHdl->SetPolyNum( pTempHdl->GetPolyNum() );
                        pSmartHdl->SetPointNum( pTempHdl->GetPointNum() );
                        pSmartHdl->SetPlusHdl(  pTempHdl->IsPlusHdl() );
                        pSmartHdl->SetSourceHdlNum( pTempHdl->GetSourceHdlNum() );
                        pSmartHdl->SetPageView( mrView.GetSdrPageView() );

                        rHandlerList.AddHdl( pSmartHdl );

                        const bool bSelected = rMrkPnts.find( sal_uInt16(nHandle) ) != rMrkPnts.end();
                        pSmartHdl->SetSelected(bSelected);

                        if( mrView.IsPlusHandlesAlwaysVisible() || bSelected )
                        {
                            sal_uInt32 nPlusHdlCnt=mpPathObj->GetPlusHdlCount(*pSmartHdl);
                            for (sal_uInt32 nPlusNum=0; nPlusNum<nPlusHdlCnt; nPlusNum++)
                            {
                                SdrHdl* pPlusHdl = mpPathObj->GetPlusHdl(*pSmartHdl,nPlusNum);
                                if (pPlusHdl!=nullptr)
                                {
                                    pPlusHdl->SetObj(mpPathObj);
                                    pPlusHdl->SetPageView(mrView.GetSdrPageView());
                                    pPlusHdl->SetPlusHdl(true);
                                    rHandlerList.AddHdl(pPlusHdl);
                                }
                            }
                        }
                    }
                }
                else
                {
                    ::tools::Rectangle aRect(mpPathObj->GetCurrentBoundRect());

                    if(!aRect.IsEmpty())
                    {
                        size_t nCount = rHandlerList.GetHdlCount();

                        bool bWdt0=aRect.Left()==aRect.Right();
                        bool bHgt0=aRect.Top()==aRect.Bottom();
                        if (bWdt0 && bHgt0)
                        {
                            rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.TopLeft(),SdrHdlKind::UpperLeft));
                        }
                        else if (bWdt0 || bHgt0)
                        {
                            rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.TopLeft()    ,SdrHdlKind::UpperLeft));
                            rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.BottomRight(),SdrHdlKind::LowerRight));
                        }
                        else
                        {
                            if (!bWdt0 && !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.TopLeft()     ,SdrHdlKind::UpperLeft));
                            if (          !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.TopCenter()   ,SdrHdlKind::Upper));
                            if (!bWdt0 && !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.TopRight()    ,SdrHdlKind::UpperRight));
                            if (!bWdt0          ) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.LeftCenter()  ,SdrHdlKind::Left ));
                            if (!bWdt0          ) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.RightCenter() ,SdrHdlKind::Right));
                            if (!bWdt0 && !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.BottomLeft()  ,SdrHdlKind::LowerLeft));
                            if (          !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.BottomCenter(),SdrHdlKind::Lower));
                            if (!bWdt0 && !bHgt0) rHandlerList.AddHdl(new SmartHdl( xThis, mpPathObj, aRect.BottomRight() ,SdrHdlKind::LowerRight));
                        }

                        while( nCount < rHandlerList.GetHdlCount() )
                        {
                            rHandlerList.GetHdl(nCount++)->SetPageView( mrView.GetSdrPageView() );
                        }
                    }
                }
            }
        }
    }

    void MotionPathTag::disposing()
    {
        Reference< XChangesNotifier > xNotifier( mpEffect->getNode(), UNO_QUERY );
        if( xNotifier.is() )
        {
            xNotifier->removeChangesListener( this );
        }

        if( mpPathObj )
        {
            SdrObject* pTemp(mpPathObj);
            mpPathObj = nullptr;
            mrView.updateHandles();

            // always use SdrObject::Free(...) for SdrObjects (!)
            SdrObject::Free(pTemp);
        }

        mpMark.reset();

        SmartTag::disposing();
    }

    void MotionPathTag::deselect()
    {
        SmartTag::deselect();

        if( mpMark )
        {
            SdrUShortCont& rPts = mpMark->GetMarkedPoints();
            rPts.clear();
        }

        selectionChanged();
    }

    void MotionPathTag::selectionChanged()
    {
        if( mrView.GetViewShell() && mrView.GetViewShell()->GetViewFrame() )
        {
            SfxBindings& rBindings = mrView.GetViewShell()->GetViewFrame()->GetBindings();
            rBindings.InvalidateAll(true);
        }
    }

    // IPolyPolygonEditorController

    void MotionPathTag::DeleteMarkedPoints()
    {
        if( mpPathObj && IsDeleteMarkedPointsPossible() )
        {
            mrView.BrkAction();

            SdrUShortCont& rPts = mpMark->GetMarkedPoints();
            PolyPolygonEditor aEditor( mpPathObj->GetPathPoly());
            if (aEditor.DeletePoints(rPts))
            {
                if( aEditor.GetPolyPolygon().count() )
                {
                    mpPathObj->SetPathPoly( aEditor.GetPolyPolygon() );
                }

                mrView.UnmarkAllPoints();
                mrView.MarkListHasChanged();
                mrView.updateHandles();
            }
        }
    }

    bool MotionPathTag::IsDeleteMarkedPointsPossible() const
    {
        return mpPathObj && isSelected() && (GetMarkedPointCount() != 0);
    }

    void MotionPathTag::RipUpAtMarkedPoints()
    {
        // not supported for motion path
    }

    bool MotionPathTag::IsRipUpAtMarkedPointsPossible() const
    {
        // not supported for motion path
        return false;
    }

    bool MotionPathTag::IsSetMarkedSegmentsKindPossible() const
    {
        if( mpPathObj )
            return mrView.IsSetMarkedSegmentsKindPossible();
        else
            return false;
    }

    SdrPathSegmentKind MotionPathTag::GetMarkedSegmentsKind() const
    {
        if( mpPathObj )
            return mrView.GetMarkedSegmentsKind();
        else
            return SdrPathSegmentKind::Line;
    }

    void MotionPathTag::SetMarkedSegmentsKind(SdrPathSegmentKind eKind)
    {
        if(mpPathObj && isSelected() && (GetMarkedPointCount() != 0))
        {
            SdrUShortCont& rPts = mpMark->GetMarkedPoints();
            PolyPolygonEditor aEditor( mpPathObj->GetPathPoly() );
            if (aEditor.SetSegmentsKind(eKind, rPts))
            {
                mpPathObj->SetPathPoly(aEditor.GetPolyPolygon());
                mrView.MarkListHasChanged();
                mrView.updateHandles();
            }
        }
    }

    bool MotionPathTag::IsSetMarkedPointsSmoothPossible() const
    {
        if( mpPathObj )
            return mrView.IsSetMarkedPointsSmoothPossible();
        else
            return false;
    }

    SdrPathSmoothKind MotionPathTag::GetMarkedPointsSmooth() const
    {
        if( mpPathObj )
            return mrView.GetMarkedPointsSmooth();
        else
            return SdrPathSmoothKind::Angular;
    }

    void MotionPathTag::SetMarkedPointsSmooth(SdrPathSmoothKind eKind)
    {
        basegfx::B2VectorContinuity eFlags;

        if(SdrPathSmoothKind::Angular == eKind)
        {
            eFlags = basegfx::B2VectorContinuity::NONE;
        }
        else if(SdrPathSmoothKind::Asymmetric == eKind)
        {
            eFlags = basegfx::B2VectorContinuity::C1;
        }
        else if(SdrPathSmoothKind::Symmetric == eKind)
        {
            eFlags = basegfx::B2VectorContinuity::C2;
        }
        else
        {
            return;
        }

        if(mpPathObj && mpMark && isSelected() && (GetMarkedPointCount() != 0))
        {
            SdrUShortCont& rPts = mpMark->GetMarkedPoints();
            PolyPolygonEditor aEditor( mpPathObj->GetPathPoly());
            if (aEditor.SetPointsSmooth(eFlags, rPts))
            {
                mpPathObj->SetPathPoly(aEditor.GetPolyPolygon());
                mrView.MarkListHasChanged();
                mrView.updateHandles();
            }
        }
    }

    bool MotionPathTag::IsOpenCloseMarkedObjectsPossible() const
    {
        // not supported for motion path
        return false;
    }

    SdrObjClosedKind MotionPathTag::GetMarkedObjectsClosedState() const
    {
        // not supported for motion path
        return SdrObjClosedKind::Open;
    }

    // XChangesListener
    void SAL_CALL MotionPathTag::changesOccurred( const ChangesEvent& /*Event*/ )
    {
        if( mpPathObj && !mbInUpdatePath && (mpEffect->getPath() != msLastPath) )
        {
            mbInUpdatePath =true;
            msLastPath = mpEffect->getPath();
            mpEffect->updateSdrPathObjFromPath( *mpPathObj );
            mbInUpdatePath = false;
            updatePathAttributes();
            mrView.updateHandles();
        }
    }

    void SAL_CALL MotionPathTag::disposing( const EventObject& /*Source*/ )
    {
        if( mpPathObj )
            Dispose();
    }

    Any SAL_CALL MotionPathTag::queryInterface( const css::uno::Type& aType )
    {
        if( aType == cppu::UnoType<XChangesListener>::get() )
            return Any( Reference< XChangesListener >( this ) );
        if( aType == cppu::UnoType<XEventListener>::get() )
            return Any( Reference< XEventListener >( this ) );
        if( aType == cppu::UnoType<XInterface>::get() )
            return Any( Reference< XInterface >( this ) );

        return Any();
    }

    void SAL_CALL MotionPathTag::acquire() throw ()
    {
        SimpleReferenceComponent::acquire();
    }

    void SAL_CALL MotionPathTag::release(  ) throw ()
    {
        SimpleReferenceComponent::release();
    }

    } // end of namespace sd

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V560 A part of conditional expression is always true: !bWdt0.

↑ V547 Expression '!bHgt0' is always true.

↑ V560 A part of conditional expression is always true: !bWdt0.

↑ V560 A part of conditional expression is always true: !bHgt0.

↑ V547 Expression '!bWdt0' is always true.

↑ V547 Expression '!bWdt0' is always true.

↑ V560 A part of conditional expression is always true: !bWdt0.

↑ V560 A part of conditional expression is always true: !bHgt0.

↑ V560 A part of conditional expression is always true: !bHgt0.

↑ V547 Expression '!bHgt0' is always true.

↑ V560 A part of conditional expression is always true: !bHgt0.

↑ V560 A part of conditional expression is always true: !bWdt0.