/* -*- 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/svdmrkv.hxx>
#include <svx/svdview.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdotable.hxx>
#include <svx/svdomedia.hxx>
 
#include <osl/diagnose.h>
#include <osl/thread.h>
#include <rtl/strbuf.hxx>
#include <svx/svdoole2.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xflgrit.hxx>
#include "gradtrns.hxx"
#include <svx/xflftrit.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <svx/svdundo.hxx>
#include <svx/svdopath.hxx>
#include <svx/scene3d.hxx>
#include <svx/svdovirt.hxx>
#include <sdr/overlay/overlayrollingrectangle.hxx>
#include <svx/sdr/overlay/overlaypolypolygon.hxx>
#include <svx/sdr/contact/displayinfo.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/sdr/overlay/overlayselection.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <svx/sdrpagewindow.hxx>
#include <svx/sdrhittesthelper.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <vcl/window.hxx>
#include <o3tl/string_view.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
 
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <sfx2/lokhelper.hxx>
#include <sfx2/lokcomponenthelpers.hxx>
#include <sfx2/viewsh.hxx>
#include <svtools/optionsdrawinglayer.hxx>
 
#include <drawinglayer/processor2d/textextractor2d.hxx>
 
#include <array>
 
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
 
#include <boost/property_tree/json_parser.hpp>
 
using namespace com::sun::star;
 
// Migrate Marking of Objects, Points and GluePoints
 
class ImplMarkingOverlay
{
    // The OverlayObjects
    sdr::overlay::OverlayObjectList               maObjects;
 
    // The remembered second position in logical coordinates
    basegfx::B2DPoint                               maSecondPosition;
 
    // A flag to remember if the action is for unmarking.
    bool                                            mbUnmarking : 1;
 
public:
    ImplMarkingOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, bool bUnmarking);
 
    // The OverlayObjects are cleared using the destructor of OverlayObjectList.
    // That destructor calls clear() at the list which removes all objects from the
    // OverlayManager and deletes them.
 
    void SetSecondPosition(const basegfx::B2DPoint& rNewPosition);
    bool IsUnmarking() const { return mbUnmarking; }
};
 
ImplMarkingOverlay::ImplMarkingOverlay(const SdrPaintView& rView, const basegfx::B2DPoint& rStartPos, bool bUnmarking)
:   maSecondPosition(rStartPos),
    mbUnmarking(bUnmarking)
{
    if (comphelper::LibreOfficeKit::isActive())
        return; // We do client-side object manipulation with the Kit API
 
    for(sal_uInt32 a(0); a < rView.PaintWindowCount(); a++)
    {
        SdrPaintWindow* pCandidate = rView.GetPaintWindow(a);
        const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager();
 
        if (xTargetOverlay.is())
        {
            std::unique_ptr<sdr::overlay::OverlayRollingRectangleStriped> pNew(new sdr::overlay::OverlayRollingRectangleStriped(
                rStartPos, rStartPos, false));
            xTargetOverlay->add(*pNew);
            maObjects.append(std::move(pNew));
        }
    }
}
 
void ImplMarkingOverlay::SetSecondPosition(const basegfx::B2DPoint& rNewPosition)
{
    if(rNewPosition != maSecondPosition)
    {
        // apply to OverlayObjects
        for(sal_uInt32 a(0); a < maObjects.count(); a++)
        {
            sdr::overlay::OverlayRollingRectangleStriped& rCandidate = static_cast< sdr::overlay::OverlayRollingRectangleStriped&>(maObjects.getOverlayObject(a));
            rCandidate.setSecondPosition(rNewPosition);
        }
 
        // remember new position
        maSecondPosition = rNewPosition;
    }
}
 
class MarkingSelectionOverlay
{
    sdr::overlay::OverlayObjectList maObjects;
public:
    MarkingSelectionOverlay(const SdrPaintView& rView, basegfx::B2DRectangle const& rSelection)
    {
        if (comphelper::LibreOfficeKit::isActive())
            return; // We do client-side object manipulation with the Kit API
 
        for (sal_uInt32 a(0); a < rView.PaintWindowCount(); a++)
        {
            SdrPaintWindow* pPaintWindow = rView.GetPaintWindow(a);
            const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pPaintWindow->GetOverlayManager();
 
            if (xTargetOverlay.is())
            {
                basegfx::B2DPolyPolygon aPolyPoly(basegfx::utils::createPolygonFromRect(rSelection));
                auto pNew = std::make_unique<sdr::overlay::OverlayPolyPolygon>(aPolyPoly, COL_GRAY, 0, COL_TRANSPARENT);
                xTargetOverlay->add(*pNew);
                maObjects.append(std::move(pNew));
            }
        }
    }
};
 
class MarkingSubSelectionOverlay
{
    sdr::overlay::OverlayObjectList maObjects;
 
public:
    MarkingSubSelectionOverlay(const SdrPaintView& rView, std::vector<basegfx::B2DRectangle> const & rSelections)
    {
        if (comphelper::LibreOfficeKit::isActive())
            return; // We do client-side object manipulation with the Kit API
 
        for (sal_uInt32 a(0); a < rView.PaintWindowCount(); a++)
        {
            SdrPaintWindow* pCandidate = rView.GetPaintWindow(a);
            const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager();
 
            if (xTargetOverlay.is())
            {
                const Color aHighlightColor = SvtOptionsDrawinglayer::getHilightColor();
 
                std::unique_ptr<sdr::overlay::OverlaySelection> pNew =
                    std::make_unique<sdr::overlay::OverlaySelection>(
                        sdr::overlay::OverlayType::Transparent,
                        aHighlightColor, std::vector(rSelections), false);
 
                xTargetOverlay->add(*pNew);
                maObjects.append(std::move(pNew));
            }
        }
    }
};
 
SdrMarkView::SdrMarkView(SdrModel& rSdrModel, OutputDevice* pOut)
    : SdrSnapView(rSdrModel, pOut)
    , mpMarkedObj(nullptr)
    , mpMarkedPV(nullptr)
    , maHdlList(this)
    , meDragMode(SdrDragMode::Move)
    , meEditMode(SdrViewEditMode::Edit)
    , meEditMode0(SdrViewEditMode::Edit)
    , mbDesignMode(false)
    , mbForceFrameHandles(false)
    , mbPlusHdlAlways(false)
    , mbInsPolyPoint(false)
    , mbMarkedObjRectDirty(false)
    , mbMrkPntDirty(false)
    , mbMarkedPointsRectsDirty(false)
    , mbMarkHandlesHidden(false)
    , mbNegativeX(false)
{
 
    BrkMarkObj();
    BrkMarkPoints();
    BrkMarkGluePoints();
 
    StartListening(rSdrModel);
}
 
SdrMarkView::~SdrMarkView()
{
    // Migrate selections
    BrkMarkObj();
    BrkMarkPoints();
    BrkMarkGluePoints();
}
 
void SdrMarkView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
    {
        const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
        SdrHintKind eKind=pSdrHint->GetKind();
        if (eKind==SdrHintKind::ObjectChange || eKind==SdrHintKind::ObjectInserted || eKind==SdrHintKind::ObjectRemoved)
        {
            mbMarkedObjRectDirty=true;
            mbMarkedPointsRectsDirty=true;
        }
    }
    SdrSnapView::Notify(rBC,rHint);
}
 
void SdrMarkView::ModelHasChanged()
{
    SdrPaintView::ModelHasChanged();
    GetMarkedObjectListWriteAccess().SetNameDirty();
    mbMarkedObjRectDirty=true;
    mbMarkedPointsRectsDirty=true;
    // Example: Obj is selected and maMarkedObjectList is sorted.
    // In another View 2, the ObjOrder is changed (e. g. MovToTop())
    // Then we need to re-sort MarkList.
    GetMarkedObjectListWriteAccess().SetUnsorted();
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    mbMrkPntDirty=true;
    UndirtyMrkPnt();
    SdrView* pV=static_cast<SdrView*>(this);
    if (!pV->IsDragObj() && !pV->IsInsObjPoint()) {
        AdjustMarkHdl();
    }
 
    if (comphelper::LibreOfficeKit::isActive())
        modelHasChangedLOKit();
}
 
void SdrMarkView::modelHasChangedLOKit()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() <= 0)
        return;
 
    //TODO: Is MarkedObjRect valid at this point?
    tools::Rectangle aSelection(GetMarkedObjRect());
    tools::Rectangle* pResultSelection;
    if (aSelection.IsEmpty())
        pResultSelection = nullptr;
    else
    {
        sal_uInt32 nTotalPaintWindows = this->PaintWindowCount();
        if (nTotalPaintWindows == 1)
        {
            const OutputDevice* pOut = this->GetFirstOutputDevice();
            const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr;
            if (pWin && pWin->IsChart())
            {
                if (SfxViewShell* pViewShell = GetSfxViewShell())
                {
                    const vcl::Window* pViewShellWindow = pViewShell->GetEditWindowForActiveOLEObj();
                    if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin))
                    {
                        Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow);
                        Point aLogicOffset = pWin->PixelToLogic(aOffsetPx);
                        aSelection.Move(aLogicOffset.getX(), aLogicOffset.getY());
                    }
                }
            }
        }
 
        // In case the map mode is in 100th MM, then need to convert the coordinates over to twips for LOK.
        if (mpMarkedPV)
        {
            if (OutputDevice* pOutputDevice = mpMarkedPV->GetView().GetFirstOutputDevice())
            {
                if (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM)
                    aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip);
            }
        }
 
        pResultSelection = &aSelection;
 
        if (mbNegativeX)
        {
            // Convert to positive X doc-coordinates
            tools::Long nTmp = aSelection.Left();
            aSelection.SetLeft(-aSelection.Right());
            aSelection.SetRight(-nTmp);
        }
    }
 
    if (SfxViewShell* pViewShell = GetSfxViewShell())
        SfxLokHelper::notifyInvalidation(pViewShell, pResultSelection);
}
 
bool SdrMarkView::IsAction() const
{
    return SdrSnapView::IsAction() || IsMarkObj() || IsMarkPoints() || IsMarkGluePoints();
}
 
void SdrMarkView::MovAction(const Point& rPnt)
{
    SdrSnapView::MovAction(rPnt);
 
    if(IsMarkObj())
    {
        MovMarkObj(rPnt);
    }
    else if(IsMarkPoints())
    {
        MovMarkPoints(rPnt);
    }
    else if(IsMarkGluePoints())
    {
        MovMarkGluePoints(rPnt);
    }
}
 
void SdrMarkView::EndAction()
{
    if(IsMarkObj())
    {
        EndMarkObj();
    }
    else if(IsMarkPoints())
    {
        EndMarkPoints();
    }
    else if(IsMarkGluePoints())
    {
        EndMarkGluePoints();
    }
 
    SdrSnapView::EndAction();
}
 
void SdrMarkView::BckAction()
{
    SdrSnapView::BckAction();
    BrkMarkObj();
    BrkMarkPoints();
    BrkMarkGluePoints();
}
 
void SdrMarkView::BrkAction()
{
    SdrSnapView::BrkAction();
    BrkMarkObj();
    BrkMarkPoints();
    BrkMarkGluePoints();
}
 
void SdrMarkView::TakeActionRect(tools::Rectangle& rRect) const
{
    if(IsMarkObj() || IsMarkPoints() || IsMarkGluePoints())
    {
        rRect = tools::Rectangle(maDragStat.GetStart(), maDragStat.GetNow());
    }
    else
    {
        SdrSnapView::TakeActionRect(rRect);
    }
}
 
 
void SdrMarkView::ClearPageView()
{
    UnmarkAllObj();
    SdrSnapView::ClearPageView();
}
 
void SdrMarkView::HideSdrPage()
{
    bool bMrkChg(false);
 
    SdrPageView* pPageView = GetSdrPageView();
    if (pPageView)
    {
        // break all creation actions when hiding page (#75081#)
        BrkAction();
 
        // Discard all selections on this page
        bMrkChg = GetMarkedObjectListWriteAccess().DeletePageView(*pPageView);
    }
 
    SdrSnapView::HideSdrPage();
 
    if(bMrkChg)
    {
        MarkListHasChanged();
        AdjustMarkHdl();
    }
}
 
 
void SdrMarkView::BegMarkObj(const Point& rPnt, bool bUnmark)
{
    BrkAction();
 
    DBG_ASSERT(!mpMarkObjOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkObjOverlay (!)");
 
    basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y());
    mpMarkObjOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark));
 
    maDragStat.Reset(rPnt);
    maDragStat.NextPoint();
    maDragStat.SetMinMove(mnMinMovLog);
}
 
void SdrMarkView::MovMarkObj(const Point& rPnt)
{
    if(IsMarkObj() && maDragStat.CheckMinMoved(rPnt))
    {
        maDragStat.NextMove(rPnt);
        DBG_ASSERT(mpMarkObjOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y());
        mpMarkObjOverlay->SetSecondPosition(aNewPos);
    }
}
 
bool SdrMarkView::EndMarkObj()
{
    bool bRetval(false);
 
    if(IsMarkObj())
    {
        if(maDragStat.IsMinMoved())
        {
            tools::Rectangle aRect(maDragStat.GetStart(), maDragStat.GetNow());
            aRect.Normalize();
            MarkObj(aRect, mpMarkObjOverlay->IsUnmarking());
            bRetval = true;
        }
 
        // cleanup
        BrkMarkObj();
    }
 
    return bRetval;
}
 
void SdrMarkView::BrkMarkObj()
{
    if(IsMarkObj())
    {
        DBG_ASSERT(mpMarkObjOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        mpMarkObjOverlay.reset();
    }
}
 
 
bool SdrMarkView::BegMarkPoints(const Point& rPnt, bool bUnmark)
{
    if(HasMarkablePoints())
    {
        BrkAction();
 
        DBG_ASSERT(!mpMarkPointsOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkPointsOverlay (!)");
        basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y());
        mpMarkPointsOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark));
 
        maDragStat.Reset(rPnt);
        maDragStat.NextPoint();
        maDragStat.SetMinMove(mnMinMovLog);
 
        return true;
    }
 
    return false;
}
 
void SdrMarkView::MovMarkPoints(const Point& rPnt)
{
    if(IsMarkPoints() && maDragStat.CheckMinMoved(rPnt))
    {
        maDragStat.NextMove(rPnt);
 
        DBG_ASSERT(mpMarkPointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y());
        mpMarkPointsOverlay->SetSecondPosition(aNewPos);
    }
}
 
bool SdrMarkView::EndMarkPoints()
{
    bool bRetval(false);
 
    if(IsMarkPoints())
    {
        if(maDragStat.IsMinMoved())
        {
            tools::Rectangle aRect(maDragStat.GetStart(), maDragStat.GetNow());
            aRect.Normalize();
            MarkPoints(&aRect, mpMarkPointsOverlay->IsUnmarking());
 
            bRetval = true;
        }
 
        // cleanup
        BrkMarkPoints();
    }
 
    return bRetval;
}
 
void SdrMarkView::BrkMarkPoints()
{
    if(IsMarkPoints())
    {
        DBG_ASSERT(mpMarkPointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        mpMarkPointsOverlay.reset();
    }
}
 
 
bool SdrMarkView::BegMarkGluePoints(const Point& rPnt, bool bUnmark)
{
    if(HasMarkableGluePoints())
    {
        BrkAction();
 
        DBG_ASSERT(!mpMarkGluePointsOverlay, "SdrMarkView::BegMarkObj: There exists a mpMarkGluePointsOverlay (!)");
 
        basegfx::B2DPoint aStartPos(rPnt.X(), rPnt.Y());
        mpMarkGluePointsOverlay.reset(new ImplMarkingOverlay(*this, aStartPos, bUnmark));
        maDragStat.Reset(rPnt);
        maDragStat.NextPoint();
        maDragStat.SetMinMove(mnMinMovLog);
 
        return true;
    }
 
    return false;
}
 
void SdrMarkView::MovMarkGluePoints(const Point& rPnt)
{
    if(IsMarkGluePoints() && maDragStat.CheckMinMoved(rPnt))
    {
        maDragStat.NextMove(rPnt);
 
        DBG_ASSERT(mpMarkGluePointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        basegfx::B2DPoint aNewPos(rPnt.X(), rPnt.Y());
        mpMarkGluePointsOverlay->SetSecondPosition(aNewPos);
    }
}
 
void SdrMarkView::EndMarkGluePoints()
{
    if(IsMarkGluePoints())
    {
        if(maDragStat.IsMinMoved())
        {
            tools::Rectangle aRect(maDragStat.GetStart(),maDragStat.GetNow());
            aRect.Normalize();
            MarkGluePoints(&aRect, mpMarkGluePointsOverlay->IsUnmarking());
        }
 
        // cleanup
        BrkMarkGluePoints();
    }
}
 
void SdrMarkView::BrkMarkGluePoints()
{
    if(IsMarkGluePoints())
    {
        DBG_ASSERT(mpMarkGluePointsOverlay, "SdrSnapView::MovSetPageOrg: no ImplPageOriginOverlay (!)");
        mpMarkGluePointsOverlay.reset();
    }
}
 
bool SdrMarkView::MarkableObjectsExceed( int n ) const
{
    SdrPageView* pPV = GetSdrPageView();
    if (!pPV)
        return false;
 
    SdrObjList* pOL=pPV->GetObjList();
    for (const rtl::Reference<SdrObject>& pObj : *pOL)
        if (IsObjMarkable(pObj.get(),pPV) && --n<0)
            return true;
 
    return false;
}
 
void SdrMarkView::hideMarkHandles()
{
    if(!mbMarkHandlesHidden)
    {
        mbMarkHandlesHidden = true;
        AdjustMarkHdl();
    }
}
 
void SdrMarkView::showMarkHandles()
{
    if(mbMarkHandlesHidden)
    {
        mbMarkHandlesHidden = false;
        AdjustMarkHdl();
    }
}
 
bool SdrMarkView::ImpIsFrameHandles() const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    bool bFrmHdl=nMarkCount>static_cast<size_t>(mnFrameHandlesLimit) || mbForceFrameHandles;
    bool bStdDrag=meDragMode==SdrDragMode::Move;
    if (nMarkCount==1 && bStdDrag && bFrmHdl)
    {
        const SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
        if (pObj && pObj->GetObjInventor()==SdrInventor::Default)
        {
            SdrObjKind nIdent=pObj->GetObjIdentifier();
            if (nIdent==SdrObjKind::Line || nIdent==SdrObjKind::Edge || nIdent==SdrObjKind::Caption || nIdent==SdrObjKind::Measure || nIdent==SdrObjKind::CustomShape || nIdent==SdrObjKind::Table )
            {
                bFrmHdl=false;
            }
        }
    }
    if (!bStdDrag && !bFrmHdl) {
        // all other drag modes only with FrameHandles
        bFrmHdl=true;
        if (meDragMode==SdrDragMode::Rotate) {
            // when rotating, use ObjOwn drag, if there's at least 1 PolyObj
            for (size_t nMarkNum=0; nMarkNum<nMarkCount && bFrmHdl; ++nMarkNum) {
                const SdrMark* pM=rMarkList.GetMark(nMarkNum);
                const SdrObject* pObj=pM->GetMarkedSdrObj();
                bFrmHdl=!pObj->IsPolyObj();
            }
        }
    }
    if (!bFrmHdl) {
        // FrameHandles, if at least 1 Obj can't do SpecialDrag
        for (size_t nMarkNum=0; nMarkNum<nMarkCount && !bFrmHdl; ++nMarkNum) {
            const SdrMark* pM=rMarkList.GetMark(nMarkNum);
            const SdrObject* pObj=pM->GetMarkedSdrObj();
            bFrmHdl=!pObj->hasSpecialDrag();
        }
    }
 
    // no FrameHdl for crop
    if(bFrmHdl && SdrDragMode::Crop == meDragMode)
    {
        bFrmHdl = false;
    }
 
    return bFrmHdl;
}
 
namespace
{
std::u16string_view lcl_getDragMethodServiceName( std::u16string_view rCID )
{
    std::u16string_view aRet;
 
    size_t nIndexStart = rCID.find( u"DragMethod=" );
    if( nIndexStart != std::u16string_view::npos )
    {
        nIndexStart = rCID.find( '=', nIndexStart );
        if( nIndexStart != std::u16string_view::npos )
        {
            nIndexStart++;
            size_t nNextSlash = rCID.find( '/', nIndexStart );
            if( nNextSlash != std::u16string_view::npos )
            {
                sal_Int32 nIndexEnd = nNextSlash;
                size_t nNextColon = rCID.find( ':', nIndexStart );
                if( nNextColon == std::u16string_view::npos || nNextColon < nNextSlash )
                    nIndexEnd = nNextColon;
                aRet = rCID.substr(nIndexStart,nIndexEnd-nIndexStart);
            }
        }
    }
    return aRet;
}
 
std::u16string_view lcl_getDragParameterString( std::u16string_view rCID )
{
    std::u16string_view aRet;
 
    size_t nIndexStart = rCID.find( u"DragParameter=" );
    if( nIndexStart != std::u16string_view::npos )
    {
        nIndexStart = rCID.find( '=', nIndexStart );
        if( nIndexStart != std::u16string_view::npos )
        {
            nIndexStart++;
            size_t nNextSlash = rCID.find( '/', nIndexStart );
            if( nNextSlash != std::u16string_view::npos )
            {
                sal_Int32 nIndexEnd = nNextSlash;
                size_t nNextColon = rCID.find( ':', nIndexStart );
                if( nNextColon == std::u16string_view::npos || nNextColon < nNextSlash )
                    nIndexEnd = nNextColon;
                aRet = rCID.substr(nIndexStart,nIndexEnd-nIndexStart);
            }
        }
    }
    return aRet;
}
} // anonymous namespace
 
bool SdrMarkView::dumpGluePointsToJSON(boost::property_tree::ptree& rTree)
{
    bool result = false;
    tools::Long nSignX = mbNegativeX ? -1 : 1;
    if (OutputDevice* pOutDev = mpMarkedPV ? mpMarkedPV->GetView().GetFirstOutputDevice() : nullptr)
    {
        bool bConvertUnit = false;
        if (pOutDev->GetMapMode().GetMapUnit() == MapUnit::Map100thMM)
            bConvertUnit = true;
        const SdrObjList* pOL = mpMarkedPV->GetObjList();
        if (!pOL)
            return false;
        boost::property_tree::ptree elements;
        const SdrMarkList& rMarkList = GetMarkedObjectList();
        for (const rtl::Reference<SdrObject>& pObj : *pOL)
        {
            if (!pObj)
                continue;
            if (pObj == rMarkList.GetMark(0)->GetMarkedSdrObj())
                continue;
            const SdrGluePointList* pGPL = pObj->GetGluePointList();
            bool VertexObject = !(pGPL && pGPL->GetCount());
            const size_t count = !VertexObject ? pGPL->GetCount() : 4;
            boost::property_tree::ptree object;
            boost::property_tree::ptree points;
            for (size_t i = 0; i < count; ++i)
            {
                boost::property_tree::ptree node;
                boost::property_tree::ptree point;
                const SdrGluePoint aGP = !VertexObject ? (*pGPL)[i] : pObj->GetVertexGluePoint(i);
                Point rPoint = aGP.GetAbsolutePos(*pObj);
                if (bConvertUnit)
                {
                    rPoint = o3tl::convert(rPoint, o3tl::Length::mm100, o3tl::Length::twip);
                }
                point.put("x", nSignX * rPoint.getX());
                point.put("y", rPoint.getY());
                node.add_child("point", point);
                points.push_back(std::make_pair("", node));
            }
            basegfx::B2DVector aGridOffset(0.0, 0.0);
            Point objLogicRectTopLeft = pObj->GetLogicRect().TopLeft();
            if(getPossibleGridOffsetForPosition(aGridOffset, basegfx::B2DPoint(objLogicRectTopLeft.X(), objLogicRectTopLeft.Y()), GetSdrPageView()))
            {
                Point p(aGridOffset.getX(), aGridOffset.getY());
                if (bConvertUnit)
                {
                    p = o3tl::convert(p, o3tl::Length::mm100, o3tl::Length::twip);
                }
                boost::property_tree::ptree gridOffset;
                gridOffset.put("x", nSignX * p.getX());
                gridOffset.put("y", p.getY());
                object.add_child("gridoffset", gridOffset);
            }
            object.put("ordnum", pObj->GetOrdNum());
            object.add_child("gluepoints", points);
            elements.push_back(std::make_pair("", object));
            result = true;
        }
        rTree.add_child("shapes", elements);
    }
    return result;
}
 
namespace
{
    class TextBoundsExtractor final : public drawinglayer::processor2d::TextExtractor2D
    {
    private:
        basegfx::B2DRange maTextRange;
        void processTextPrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override
        {
            maTextRange.expand(rCandidate.getB2DRange(getViewInformation2D()));
        }
    public:
        explicit TextBoundsExtractor(const drawinglayer::geometry::ViewInformation2D& rViewInformation)
            : drawinglayer::processor2d::TextExtractor2D(rViewInformation)
        {
        }
 
        const basegfx::B2DRange & getTextBounds(const sdr::contact::ViewObjectContact &rVOC, sdr::contact::DisplayInfo &raDisplayInfo)
        {
            this->process(rVOC.getPrimitive2DSequence(raDisplayInfo));
            return maTextRange;
        }
    };
}
 
OString SdrMarkView::CreateInnerTextRectString() const
{
    if (!mpMarkedObj)
        return OString();
 
    SdrPageView* pPageView = GetSdrPageView();
    const sdr::contact::ViewObjectContact& rVOC = mpMarkedObj->GetViewContact().GetViewObjectContact(
        pPageView->GetPageWindow(0)->GetObjectContact());
 
    sdr::contact::DisplayInfo aDisplayInfo;
    TextBoundsExtractor aTextBoundsExtractor(rVOC.GetObjectContact().getViewInformation2D());
    basegfx::B2DRange aRange = aTextBoundsExtractor.getTextBounds(rVOC, aDisplayInfo);
    if (!aRange.isEmpty()) {
        tools::Rectangle rect(aRange.getMinX(), aRange.getMinY(), aRange.getMaxX(), aRange.getMaxY());
        tools::Rectangle aRangeTWIP = o3tl::convert(rect, o3tl::Length::mm100, o3tl::Length::twip);
        OString innerTextInfo = "\"innerTextRect\":[" +
            OString::number(aRangeTWIP.getX()) + "," +
            OString::number(aRangeTWIP.getY()) + "," +
            OString::number(aRangeTWIP.GetWidth()) + "," +
            OString::number(aRangeTWIP.GetHeight()) + "]";
        return innerTextInfo;
    }
 
    return OString();
}
 
void SdrMarkView::SetInnerTextAreaForLOKit() const
{
    if (!comphelper::LibreOfficeKit::isActive())
        return;
    SfxViewShell* pViewShell = GetSfxViewShell();
    OString sRectString = CreateInnerTextRectString();
    if (pViewShell && !sRectString.isEmpty())
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SHAPE_INNER_TEXT, sRectString);
}
 
void SdrMarkView::SetMarkHandlesForLOKit(tools::Rectangle const & rRect, const SfxViewShell* pOtherShell)
{
    SfxViewShell* pViewShell = GetSfxViewShell();
 
    tools::Rectangle aSelection(rRect);
    tools::Long nSignX = mbNegativeX ? -1 : 1;
    bool bIsChart = false;
    Point addLogicOffset(0, 0);
    bool convertMapMode = false;
    if (!rRect.IsEmpty())
    {
        sal_uInt32 nTotalPaintWindows = this->PaintWindowCount();
        if (nTotalPaintWindows == 1)
        {
            const OutputDevice* pOut = this->GetFirstOutputDevice();
            const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr;
            if (pWin && pWin->IsChart())
            {
                bIsChart = true;
                const vcl::Window* pViewShellWindow = GetSfxViewShell()->GetEditWindowForActiveOLEObj();
                if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin))
                {
                    Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow);
                    if (mbNegativeX && AllSettings::GetLayoutRTL())
                    {
                        // mbNegativeX is set only for Calc in RTL mode.
                        // If global RTL flag is set, vcl-window X offset of chart window is
                        // mirrored w.r.t parent window rectangle. This needs to be reverted.
                        aOffsetPx.setX(pViewShellWindow->GetOutOffXPixel() + pViewShellWindow->GetSizePixel().Width()
                            - pWin->GetOutOffXPixel() - pWin->GetSizePixel().Width());
                    }
                    Point aLogicOffset = pWin->PixelToLogic(aOffsetPx);
                    addLogicOffset = aLogicOffset;
                    aSelection.Move(aLogicOffset.getX(), aLogicOffset.getY());
                }
            }
        }
    }
 
    if (!aSelection.IsEmpty())
    {
        // In case the map mode is in 100th MM, then need to convert the coordinates over to twips for LOK.
        if (mpMarkedPV)
        {
            if (OutputDevice* pOutputDevice = mpMarkedPV->GetView().GetFirstOutputDevice())
            {
                if (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM)
                {
                    aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip);
                    convertMapMode = true;
                }
            }
        }
 
        // hide the text selection too
        if (pViewShell)
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, ""_ostr);
    }
 
    {
        OStringBuffer aExtraInfo;
        OString sSelectionText;
        OString sSelectionTextView;
        boost::property_tree::ptree aTableJsonTree;
        boost::property_tree::ptree aGluePointsTree;
        const bool bMediaObj = (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Media);
        bool bTableSelection = false;
        bool bConnectorSelection = false;
 
        if (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Table)
        {
            auto& rTableObject = dynamic_cast<sdr::table::SdrTableObj&>(*mpMarkedObj);
            bTableSelection = rTableObject.createTableEdgesJson(aTableJsonTree);
        }
        if (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Edge)
        {
            bConnectorSelection = dumpGluePointsToJSON(aGluePointsTree);
        }
 
        SdrPageView* pPageView = GetSdrPageView();
 
        const SdrMarkList& rMarkList = GetMarkedObjectList();
        if (rMarkList.GetMarkCount())
        {
            SdrMark* pM = rMarkList.GetMark(0);
            SdrObject* pO = pM->GetMarkedSdrObj();
            Degree100 nRotAngle = pO->GetRotateAngle();
            // true if we are dealing with a RotGrfFlyFrame
            // (SwVirtFlyDrawObj with a SwGrfNode)
            bool bWriterGraphic = pO->HasLimitedRotation();
 
            OString handleArrayStr;
 
            aExtraInfo.append("{\"id\":\""
                + OString::number(reinterpret_cast<sal_IntPtr>(pO))
                + "\",\"type\":"
                + OString::number(static_cast<sal_Int32>(pO->GetObjIdentifier()))
                + ",\"OrdNum\":" + OString::number(pO->GetOrdNum())
                );
 
            if (mpMarkedObj && !pOtherShell)
            {
                OString innerTextInfo = CreateInnerTextRectString();
                if (!innerTextInfo.isEmpty())
                    aExtraInfo.append("," + innerTextInfo);
            }
 
            // In core, the gridOffset is calculated based on the LogicRect's TopLeft coordinate
            // In online, we have the SnapRect and we calculate it based on its TopLeft coordinate
            // SnapRect's TopLeft and LogicRect's TopLeft match unless there is rotation
            // but the rotation is not applied to the LogicRect. Therefore,
            // what we calculate in online does not match with the core in case of the rotation.
            // Here we can send the correct gridOffset in the selection callback, this way
            // whether the shape is rotated or not, we will always have the correct gridOffset
            // Note that the gridOffset is calculated from the first selected obj
            basegfx::B2DVector aGridOffset(0.0, 0.0);
            if(getPossibleGridOffsetForSdrObject(aGridOffset, rMarkList.GetMark(0)->GetMarkedSdrObj(), pPageView))
            {
                Point p(aGridOffset.getX(), aGridOffset.getY());
                if (convertMapMode)
                    p = o3tl::convert(p, o3tl::Length::mm100, o3tl::Length::twip);
                aExtraInfo.append(",\"gridOffsetX\":"
                    + OString::number(nSignX * p.getX())
                    + ",\"gridOffsetY\":"
                    + OString::number(p.getY()));
            }
 
            if (bWriterGraphic)
            {
                aExtraInfo.append(", \"isWriterGraphic\": true");
            }
            else if (bIsChart)
            {
                LokChartHelper aChartHelper(pViewShell);
                css::uno::Reference<css::frame::XController>& xChartController = aChartHelper.GetXController();
                css::uno::Reference<css::view::XSelectionSupplier> xSelectionSupplier( xChartController, uno::UNO_QUERY);
                if (xSelectionSupplier.is())
                {
                    uno::Any aSel = xSelectionSupplier->getSelection();
                    OUString aValue;
                    if (aSel >>= aValue)
                    {
                        OString aObjectCID(aValue.getStr(), aValue.getLength(), osl_getThreadTextEncoding());
                        const std::vector<OString> aProps{"Draggable"_ostr, "Resizable"_ostr, "Rotatable"_ostr};
                        for (const auto& rProp: aProps)
                        {
                            sal_Int32 nPos = aObjectCID.indexOf(rProp);
                            if (nPos == -1) continue;
                            nPos += rProp.getLength() + 1; // '='
                            if (aExtraInfo.getLength() > 2) // != "{ "
                                aExtraInfo.append(", ");
                            aExtraInfo.append("\"is" + rProp + "\": "
                                + OString::boolean(aObjectCID[nPos] == '1'));
                        }
 
                        std::u16string_view sDragMethod = lcl_getDragMethodServiceName(aValue);
                        if (sDragMethod == u"PieSegmentDragging")
                        {
                            // old initial offset inside the CID returned by xSelectionSupplier->getSelection()
                            // after a pie segment dragging; using SdrObject::GetName for getting a CID with the updated offset
                            aValue = pO->GetName();
                            std::u16string_view sDragParameters = lcl_getDragParameterString(aValue);
                            if (!sDragParameters.empty())
                            {
                                aExtraInfo.append(", \"dragInfo\": { "
                                    "\"dragMethod\": \""
                                    + OUString(sDragMethod).toUtf8()
                                    + "\"");
 
                                sal_Int32 nStartIndex = 0;
                                std::array<int, 5> aDragParameters;
                                for (auto& rParam : aDragParameters)
                                {
                                    std::u16string_view sParam = o3tl::getToken(sDragParameters, 0, ',', nStartIndex);
                                    if (sParam.empty())
                                        break;
                                    rParam = o3tl::toInt32(sParam);
                                }
 
                                // initial offset in %
                                if (aDragParameters[0] < 0)
                                    aDragParameters[0] = 0;
                                else if (aDragParameters[0] > 100)
                                    aDragParameters[0] = 100;
 
                                aExtraInfo.append(", \"initialOffset\": "
                                    + OString::number(static_cast<sal_Int32>(aDragParameters[0])));
 
                                // drag direction constraint
                                Point aMinPos(aDragParameters[1], aDragParameters[2]);
                                Point aMaxPos(aDragParameters[3], aDragParameters[4]);
                                Point aDragDirection = aMaxPos - aMinPos;
                                aDragDirection = o3tl::convert(aDragDirection, o3tl::Length::mm100, o3tl::Length::twip);
 
                                aExtraInfo.append(", \"dragDirection\": ["
                                    + aDragDirection.toString()
                                    + "]");
 
                                // polygon approximating the pie segment or donut segment
                                if (pViewShell && pO->GetObjIdentifier() == SdrObjKind::PathFill)
                                {
                                    const basegfx::B2DPolyPolygon aPolyPolygon(pO->TakeXorPoly());
                                    if (aPolyPolygon.count() == 1)
                                    {
                                        const basegfx::B2DPolygon& aPolygon = aPolyPolygon.getB2DPolygon(0);
                                        if (sal_uInt32 nPolySize = aPolygon.count())
                                        {
                                            const OutputDevice* pOut = this->GetFirstOutputDevice();
                                            const vcl::Window* pWin = pOut ? pOut->GetOwnerWindow() : nullptr;
                                            const vcl::Window* pViewShellWindow = pViewShell->GetEditWindowForActiveOLEObj();
                                            if (pWin && pViewShellWindow && pViewShellWindow->IsAncestorOf(*pWin))
                                            {
                                                // in the following code escaping sequences used inside raw literal strings
                                                // are for making them understandable by the JSON parser
 
                                                Point aOffsetPx = pWin->GetOffsetPixelFrom(*pViewShellWindow);
                                                Point aLogicOffset = pWin->PixelToLogic(aOffsetPx);
                                                OString sPolygonElem("<polygon points=\\\""_ostr);
                                                for (sal_uInt32 nIndex = 0; nIndex < nPolySize; ++nIndex)
                                                {
                                                    const basegfx::B2DPoint aB2Point = aPolygon.getB2DPoint(nIndex);
                                                    Point aPoint(aB2Point.getX(), aB2Point.getY());
                                                    aPoint.Move(aLogicOffset.getX(), aLogicOffset.getY());
                                                    if (mbNegativeX)
                                                        aPoint.setX(-aPoint.X());
                                                    if (nIndex > 0)
                                                        sPolygonElem += " ";
                                                    sPolygonElem += aPoint.toString();
                                                }
                                                sPolygonElem += R"elem(\" style=\"stroke: none; fill: rgb(114,159,207); fill-opacity: 0.8\"/>)elem";
 
                                                OString sSVGElem = R"elem(<svg version=\"1.2\" width=\")elem" +
                                                    OString::number(aSelection.GetWidth() / 100.0) +
                                                    R"elem(mm\" height=\")elem" +
                                                    OString::number(aSelection.GetHeight() / 100.0) +
                                                    R"elem(mm\" viewBox=\")elem" +
                                                    aSelection.toString() +
                                                    R"elem(\" preserveAspectRatio=\"xMidYMid\" xmlns=\"http://www.w3.org/2000/svg\">)elem";
 
                                                aExtraInfo.append(", \"svg\": \""
                                                    + sSVGElem
                                                    + "\\n  "
                                                    + sPolygonElem
                                                    + "\\n</svg>"
                                                    "\""); // svg
                                            }
                                        }
                                    }
                                }
                                aExtraInfo.append("}"); // dragInfo
                            }
                        }
                    }
                }
            }
            if (!bTableSelection && !pOtherShell && maHdlList.GetHdlCount())
            {
                boost::property_tree::ptree responseJSON;
                boost::property_tree::ptree others;
                boost::property_tree::ptree anchor;
                boost::property_tree::ptree rectangle;
                boost::property_tree::ptree poly;
                boost::property_tree::ptree custom;
                boost::property_tree::ptree nodes;
                for (size_t i = 0; i < maHdlList.GetHdlCount(); i++)
                {
                    SdrHdl *pHdl = maHdlList.GetHdl(i);
                    boost::property_tree::ptree child;
                    boost::property_tree::ptree point;
                    sal_Int32 kind = static_cast<sal_Int32>(pHdl->GetKind());
                    child.put("id", pHdl->GetObjHdlNum());
                    child.put("kind", kind);
                    child.put("pointer", static_cast<sal_Int32>(pHdl->GetPointer()));
                    Point pHdlPos = pHdl->GetPos();
                    pHdlPos.Move(addLogicOffset.getX(), addLogicOffset.getY());
                    if (convertMapMode)
                    {
                        pHdlPos = o3tl::convert(pHdlPos, o3tl::Length::mm100, o3tl::Length::twip);
                    }
                    point.put("x", pHdlPos.getX());
                    point.put("y", pHdlPos.getY());
                    child.add_child("point", point);
                    const auto node = std::make_pair("", child);
                    boost::property_tree::ptree* selectedNode = nullptr;
                    if (kind >= static_cast<sal_Int32>(SdrHdlKind::UpperLeft) && kind <= static_cast<sal_Int32>(SdrHdlKind::LowerRight))
                    {
                        selectedNode = &rectangle;
                    }
                    else if (kind == static_cast<sal_Int32>(SdrHdlKind::Poly))
                    {
                        selectedNode = &poly;
                    }
                    else if (kind == static_cast<sal_Int32>(SdrHdlKind::CustomShape1))
                    {
                        selectedNode = &custom;
                    }
                    else if (kind == static_cast<sal_Int32>(SdrHdlKind::Anchor) || kind == static_cast<sal_Int32>(SdrHdlKind::Anchor_TR))
                    {
                        if (getSdrModelFromSdrView().IsWriter())
                            selectedNode = &anchor;
                        else
                            // put it to others as we don't render them except in writer
                            selectedNode = &others;
                    }
                    else
                    {
                        selectedNode = &others;
                    }
                    std::string sKind = std::to_string(kind);
                    boost::optional< boost::property_tree::ptree& > kindNode = selectedNode->get_child_optional(sKind.c_str());
                    if (!kindNode)
                    {
                        boost::property_tree::ptree newChild;
                        newChild.push_back(node);
                        selectedNode->add_child(sKind.c_str(), newChild);
                    }
                    else
                        kindNode.get().push_back(node);
                }
                nodes.add_child("rectangle", rectangle);
                nodes.add_child("poly", poly);
                nodes.add_child("custom", custom);
                nodes.add_child("anchor", anchor);
                nodes.add_child("others", others);
                responseJSON.add_child("kinds", nodes);
                std::stringstream aStream;
                boost::property_tree::write_json(aStream, responseJSON, /*pretty=*/ false);
                handleArrayStr = ", \"handles\":"_ostr;
                handleArrayStr = handleArrayStr + aStream.str().c_str();
                if (bConnectorSelection)
                {
                    aStream.str("");
                    boost::property_tree::write_json(aStream, aGluePointsTree, /*pretty=*/ false);
                    handleArrayStr = handleArrayStr + ", \"GluePoints\":";
                    handleArrayStr = handleArrayStr + aStream.str().c_str();
                }
            }
 
            if (mbNegativeX)
            {
                tools::Rectangle aNegatedRect(aSelection);
                aNegatedRect.SetLeft(-aNegatedRect.Left());
                aNegatedRect.SetRight(-aNegatedRect.Right());
                aNegatedRect.Normalize();
                sSelectionText = aNegatedRect.toString() +
                    ", " + OString::number(nRotAngle.get());
            }
            else
            {
                sSelectionText = aSelection.toString() +
                    ", " + OString::number(nRotAngle.get());
            }
 
            if (!aExtraInfo.isEmpty())
            {
                sSelectionTextView = sSelectionText + ", " + aExtraInfo + "}";
 
                if (bMediaObj && pOtherShell == nullptr)
                {
                    // Add the URL only if we have a Media Object and
                    // we are the selecting view.
                    SdrMediaObj* mediaObj = dynamic_cast<SdrMediaObj*>(mpMarkedObj);
                    if (mediaObj)
                        aExtraInfo.append(", \"url\": \"" + mediaObj->getTempURL().toUtf8() + "\"");
                }
 
                SdrPage *pPage = pPageView ? pPageView->GetPage(): nullptr;
 
                if (pPage && getSdrModelFromSdrView().IsImpress())
                {
                    // Send all objects' rectangles along with the selected object's information.
                    // Other rectangles can be used for aligning the selected object referencing the others.
                    // Replace curly braces with empty string in order to merge it with the resulting string.
                    OString objectRectangles = SdrObjList::GetObjectRectangles(*pPage).replaceAll("{"_ostr, ""_ostr).replaceAll("}"_ostr, ""_ostr);
                    aExtraInfo.append(", \"ObjectRectangles\": "_ostr + objectRectangles);
                }
 
                aExtraInfo.append(handleArrayStr
                    + "}");
                sSelectionText += ", " + aExtraInfo;
            }
        }
 
        if (sSelectionText.isEmpty())
        {
            sSelectionText = "EMPTY"_ostr;
            sSelectionTextView = "EMPTY"_ostr;
            if (!pOtherShell && pViewShell)
                pViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, OString());
        }
 
        if (bTableSelection)
        {
            boost::property_tree::ptree aTableRectangle;
            aTableRectangle.put("x", aSelection.Left());
            aTableRectangle.put("y", aSelection.Top());
            aTableRectangle.put("width", aSelection.GetWidth());
            aTableRectangle.put("height", aSelection.GetHeight());
            aTableJsonTree.push_back(std::make_pair("rectangle", aTableRectangle));
 
            std::stringstream aStream;
            boost::property_tree::write_json(aStream, aTableJsonTree);
            if (pViewShell)
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, OString(aStream.str()));
        }
        else if (!getSdrModelFromSdrView().IsWriter() && pViewShell)
        {
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, "{}"_ostr);
        }
 
        if (pOtherShell)
        {
            // Another shell wants to know about our existing
            // selection.
            if (pViewShell != pOtherShell)
                SfxLokHelper::notifyOtherView(pViewShell, pOtherShell, LOK_CALLBACK_GRAPHIC_VIEW_SELECTION, "selection", sSelectionTextView);
        }
        else if (pViewShell)
        {
            // We have a new selection, so both pViewShell and the
            // other views want to know about it.
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_GRAPHIC_SELECTION, sSelectionText);
 
            SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_GRAPHIC_VIEW_SELECTION, "selection", sSelectionTextView);
        }
    }
}
 
void SdrMarkView::SetMarkHandles(SfxViewShell* pOtherShell)
{
    // remember old focus handle values to search for it again
    const SdrHdl* pSaveOldFocusHdl = maHdlList.GetFocusHdl();
    bool bSaveOldFocus(false);
    sal_uInt32 nSavePolyNum(0), nSavePointNum(0);
    SdrHdlKind eSaveKind(SdrHdlKind::Move);
    SdrObject* pSaveObj = nullptr;
 
    mpMarkingSubSelectionOverlay.reset();
    mpMarkingSelectionOverlay.reset();
 
    if(pSaveOldFocusHdl
        && pSaveOldFocusHdl->GetObj()
        && dynamic_cast<const SdrPathObj*>(pSaveOldFocusHdl->GetObj()) != nullptr
        && (pSaveOldFocusHdl->GetKind() == SdrHdlKind::Poly || pSaveOldFocusHdl->GetKind() == SdrHdlKind::BezierWeight))
    {
        bSaveOldFocus = true;
        nSavePolyNum = pSaveOldFocusHdl->GetPolyNum();
        nSavePointNum = pSaveOldFocusHdl->GetPointNum();
        pSaveObj = pSaveOldFocusHdl->GetObj();
        eSaveKind = pSaveOldFocusHdl->GetKind();
    }
 
    // delete/clear all handles. This will always be done, even with areMarkHandlesHidden()
    maHdlList.Clear();
    maHdlList.SetRotateShear(meDragMode==SdrDragMode::Rotate);
    maHdlList.SetDistortShear(meDragMode==SdrDragMode::Shear);
    mpMarkedObj=nullptr;
    mpMarkedPV=nullptr;
 
    // are handles enabled at all? Create only then
    if(areMarkHandlesHidden())
        return;
 
    // There can be multiple mark views, but we're only interested in the one that has a window associated.
    const bool bTiledRendering = comphelper::LibreOfficeKit::isActive() && GetFirstOutputDevice() && GetFirstOutputDevice()->GetOutDevType() == OUTDEV_WINDOW;
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    bool bStdDrag=meDragMode==SdrDragMode::Move;
    bool bSingleTextObjMark=false;
    bool bLimitedRotation(false);
 
    if (nMarkCount==1)
    {
        mpMarkedObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
 
        if(nullptr != mpMarkedObj)
        {
            bSingleTextObjMark =
                DynCastSdrTextObj( mpMarkedObj) !=  nullptr &&
                static_cast<SdrTextObj*>(mpMarkedObj)->IsTextFrame();
 
            // RotGrfFlyFrame: we may have limited rotation
            bLimitedRotation = SdrDragMode::Rotate == meDragMode && mpMarkedObj->HasLimitedRotation();
        }
    }
 
    bool bFrmHdl=ImpIsFrameHandles();
 
    if (nMarkCount>0)
    {
        mpMarkedPV=rMarkList.GetMark(0)->GetPageView();
 
        for (size_t nMarkNum=0; nMarkNum<nMarkCount && (mpMarkedPV!=nullptr || !bFrmHdl); ++nMarkNum)
        {
            const SdrMark* pM=rMarkList.GetMark(nMarkNum);
 
            if (mpMarkedPV!=pM->GetPageView())
            {
                mpMarkedPV=nullptr;
            }
        }
    }
 
    tools::Rectangle aRect(GetMarkedObjRect());
 
    if (mpMarkedObj && mpMarkedObj->GetObjIdentifier() == SdrObjKind::Annotation)
    {
        basegfx::B2DRectangle aB2DRect(aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom());
        mpMarkingSelectionOverlay = std::make_unique<MarkingSelectionOverlay>(*this, aB2DRect);
 
        return;
 
    }
 
    SfxViewShell* pViewShell = GetSfxViewShell();
 
    // check if text edit or ole is active and handles need to be suppressed. This may be the case
    // when a single object is selected
    // Using a strict return statement is okay here; no handles means *no* handles.
    if (mpMarkedObj)
    {
        // formerly #i33755#: If TextEdit is active the EditEngine will directly paint
        // to the window, so suppress Overlay and handles completely; a text frame for
        // the active text edit will be painted by the repaint mechanism in
        // SdrObjEditView::ImpPaintOutlinerView in this case. This needs to be reworked
        // in the future
        // Also formerly #122142#: Pretty much the same for SdrCaptionObj's in calc.
        if(static_cast<SdrView*>(this)->IsTextEdit())
        {
            const SdrTextObj* pSdrTextObj = DynCastSdrTextObj(mpMarkedObj);
 
            if (pSdrTextObj && pSdrTextObj->IsInEditMode())
            {
                if (!bTiledRendering)
                    return;
            }
        }
 
        // formerly #i118524#: if inplace activated OLE is selected, suppress handles
        const SdrOle2Obj* pSdrOle2Obj = dynamic_cast< const SdrOle2Obj* >(mpMarkedObj);
 
        if(pSdrOle2Obj && (pSdrOle2Obj->isInplaceActive() || pSdrOle2Obj->isUiActive()))
        {
            return;
        }
 
        if (!maSubSelectionList.empty())
        {
            mpMarkingSubSelectionOverlay = std::make_unique<MarkingSubSelectionOverlay>(*this, maSubSelectionList);
        }
    }
 
    if (bFrmHdl)
    {
        if(!aRect.IsEmpty())
        {
            // otherwise nothing is found
            const size_t nSiz0(maHdlList.GetHdlCount());
 
            if( bSingleTextObjMark )
            {
                mpMarkedObj->AddToHdlList(maHdlList);
            }
            else
            {
                const bool bWdt0(aRect.Left() == aRect.Right());
                const bool bHgt0(aRect.Top() == aRect.Bottom());
 
                if (bWdt0 && bHgt0)
                {
                    maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft));
                }
                else if (!bStdDrag && (bWdt0 || bHgt0))
                {
                    maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft));
                    maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomRight(), SdrHdlKind::LowerRight));
                }
                else
                {
                    if (!bWdt0 && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopLeft(), SdrHdlKind::UpperLeft));
                    }
 
                    if (!bLimitedRotation && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopCenter(), SdrHdlKind::Upper));
                    }
 
                    if (!bWdt0 && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.TopRight(), SdrHdlKind::UpperRight));
                    }
 
                    if (!bLimitedRotation && !bWdt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.LeftCenter(), SdrHdlKind::Left ));
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.RightCenter(), SdrHdlKind::Right));
                    }
 
                    if (!bWdt0 && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomLeft(), SdrHdlKind::LowerLeft));
                    }
 
                    if (!bLimitedRotation && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomCenter(), SdrHdlKind::Lower));
                    }
 
                    if (!bWdt0 && !bHgt0)
                    {
                        maHdlList.AddHdl(std::make_unique<SdrHdl>(aRect.BottomRight(), SdrHdlKind::LowerRight));
                    }
                }
            }
 
            // Diagram selection visualization support
            // Caution: CppunitTest_sd_tiledrendering shows that mpMarkedObj *can* actually be nullptr (!)
            if(nullptr != mpMarkedObj && mpMarkedObj->isDiagram())
            {
                mpMarkedObj->AddToHdlList(maHdlList);
            }
 
            const size_t nSiz1(maHdlList.GetHdlCount());
 
            // moved setting the missing parameters at SdrHdl here from the
            // single loop above (bSingleTextObjMark), this was missing all
            // the time. Setting SdrObject is now required to correctly get
            // the View-Dependent evtl. GridOffset adapted
            for (size_t i=nSiz0; i<nSiz1; ++i)
            {
                SdrHdl* pHdl=maHdlList.GetHdl(i);
                pHdl->SetObj(mpMarkedObj);
                pHdl->SetPageView(mpMarkedPV);
                pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0));
            }
        }
    }
    else
    {
        bool bDone(false);
 
        // moved crop handling to non-frame part and the handle creation to SdrGrafObj
        if(1 == nMarkCount && mpMarkedObj && SdrDragMode::Crop == meDragMode)
        {
            // Default addCropHandles from SdrObject does nothing. When pMarkedObj is SdrGrafObj, previous
            // behaviour occurs (code in svx/source/svdraw/svdograf.cxx). When pMarkedObj is SwVirtFlyDrawObj
            // writer takes the responsibility of adding handles (code in sw/source/core/draw/dflyobj.cxx)
            const size_t nSiz0(maHdlList.GetHdlCount());
            mpMarkedObj->addCropHandles(maHdlList);
            const size_t nSiz1(maHdlList.GetHdlCount());
 
            // Was missing: Set infos at SdrCropHdl
            for (size_t i=nSiz0; i<nSiz1; ++i)
            {
                SdrHdl* pHdl=maHdlList.GetHdl(i);
                pHdl->SetObj(mpMarkedObj);
                pHdl->SetPageView(mpMarkedPV);
                pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0));
            }
 
            bDone = true;
        }
 
        if(!bDone)
        {
            for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum)
            {
                const SdrMark* pM=rMarkList.GetMark(nMarkNum);
                SdrObject* pObj=pM->GetMarkedSdrObj();
                SdrPageView* pPV=pM->GetPageView();
                const size_t nSiz0=maHdlList.GetHdlCount();
                pObj->AddToHdlList(maHdlList);
                const size_t nSiz1=maHdlList.GetHdlCount();
                bool bPoly=pObj->IsPolyObj();
                const SdrUShortCont& rMrkPnts = pM->GetMarkedPoints();
                for (size_t i=nSiz0; i<nSiz1; ++i)
                {
                    SdrHdl* pHdl=maHdlList.GetHdl(i);
                    pHdl->SetObj(pObj);
                    pHdl->SetPageView(pPV);
                    pHdl->SetObjHdlNum(sal_uInt16(i-nSiz0));
 
                    if (bPoly)
                    {
                        bool bSelected= rMrkPnts.find( sal_uInt16(i-nSiz0) ) != rMrkPnts.end();
                        pHdl->SetSelected(bSelected);
                        if (mbPlusHdlAlways || bSelected)
                        {
                            SdrHdlList plusList(nullptr);
                            pObj->AddToPlusHdlList(plusList, *pHdl);
                            sal_uInt32 nPlusHdlCnt=plusList.GetHdlCount();
                            for (sal_uInt32 nPlusNum=0; nPlusNum<nPlusHdlCnt; nPlusNum++)
                            {
                                SdrHdl* pPlusHdl=plusList.GetHdl(nPlusNum);
                                pPlusHdl->SetObj(pObj);
                                pPlusHdl->SetPageView(pPV);
                                pPlusHdl->SetPlusHdl(true);
                            }
                            plusList.MoveTo(maHdlList);
                        }
                    }
                }
            }
        }
    }
 
    // GluePoint handles
    for (size_t nMarkNum=0; nMarkNum<nMarkCount; ++nMarkNum)
    {
        const SdrMark* pM=rMarkList.GetMark(nMarkNum);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        const SdrGluePointList* pGPL=pObj->GetGluePointList();
        if (!pGPL)
            continue;
 
        SdrPageView* pPV=pM->GetPageView();
        const SdrUShortCont& rMrkGlue=pM->GetMarkedGluePoints();
        for (sal_uInt16 nId : rMrkGlue)
        {
            //nNum changed to nNumGP because already used in for loop
            sal_uInt16 nNumGP=pGPL->FindGluePoint(nId);
            if (nNumGP!=SDRGLUEPOINT_NOTFOUND)
            {
                const SdrGluePoint& rGP=(*pGPL)[nNumGP];
                Point aPos(rGP.GetAbsolutePos(*pObj));
                std::unique_ptr<SdrHdl> pGlueHdl(new SdrHdl(aPos,SdrHdlKind::Glue));
                pGlueHdl->SetObj(pObj);
                pGlueHdl->SetPageView(pPV);
                pGlueHdl->SetObjHdlNum(nId);
                maHdlList.AddHdl(std::move(pGlueHdl));
            }
        }
    }
 
    // rotation point/axis of reflection
    if(!bLimitedRotation)
    {
        AddDragModeHdl(meDragMode);
    }
 
    // sort handles
    maHdlList.Sort();
 
    // add custom handles (used by other apps, e.g. AnchorPos)
    AddCustomHdl();
 
    // moved it here to access all the handles for callback.
    if (bTiledRendering && pViewShell)
    {
        SetMarkHandlesForLOKit(aRect, pOtherShell);
    }
 
    // try to restore focus handle index from remembered values
    if(!bSaveOldFocus)
        return;
 
    for(size_t a = 0; a < maHdlList.GetHdlCount(); ++a)
    {
        SdrHdl* pCandidate = maHdlList.GetHdl(a);
 
        if(pCandidate->GetObj()
            && pCandidate->GetObj() == pSaveObj
            && pCandidate->GetKind() == eSaveKind
            && pCandidate->GetPolyNum() == nSavePolyNum
            && pCandidate->GetPointNum() == nSavePointNum)
        {
            maHdlList.SetFocusHdl(pCandidate);
            break;
        }
    }
}
 
void SdrMarkView::AddCustomHdl()
{
    // add custom handles (used by other apps, e.g. AnchorPos)
}
 
void SdrMarkView::SetDragMode(SdrDragMode eMode)
{
    SdrDragMode eMode0=meDragMode;
    meDragMode=eMode;
    if (meDragMode==SdrDragMode::Resize) meDragMode=SdrDragMode::Move;
    if (meDragMode!=eMode0) {
        ForceRefToMarked();
        SetMarkHandles(nullptr);
        {
            const SdrMarkList& rMarkList = GetMarkedObjectList();
            if (rMarkList.GetMarkCount() != 0) MarkListHasChanged();
        }
    }
}
 
void SdrMarkView::AddDragModeHdl(SdrDragMode eMode)
{
    switch(eMode)
    {
        case SdrDragMode::Rotate:
        {
            // add rotation center
            maHdlList.AddHdl(std::make_unique<SdrHdl>(maRef1, SdrHdlKind::Ref1));
            break;
        }
        case SdrDragMode::Mirror:
        {
            // add axis of reflection
            std::unique_ptr<SdrHdl> pHdl3(new SdrHdl(maRef2, SdrHdlKind::Ref2));
            std::unique_ptr<SdrHdl> pHdl2(new SdrHdl(maRef1, SdrHdlKind::Ref1));
            std::unique_ptr<SdrHdl> pHdl1(new SdrHdlLine(*pHdl2, *pHdl3, SdrHdlKind::MirrorAxis));
 
            pHdl1->SetObjHdlNum(1); // for sorting
            pHdl2->SetObjHdlNum(2); // for sorting
            pHdl3->SetObjHdlNum(3); // for sorting
 
            maHdlList.AddHdl(std::move(pHdl1)); // line comes first, so it is the last in HitTest
            maHdlList.AddHdl(std::move(pHdl2));
            maHdlList.AddHdl(std::move(pHdl3));
 
            break;
        }
        case SdrDragMode::Transparence:
        {
            // add interactive transparency handle
            const SdrMarkList& rMarkList = GetMarkedObjectList();
            const size_t nMarkCount = rMarkList.GetMarkCount();
            if(nMarkCount == 1)
            {
                SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
                SdrModel& rModel = GetModel();
                const SfxItemSet& rSet = pObj->GetMergedItemSet();
 
                if(SfxItemState::SET != rSet.GetItemState(XATTR_FILLFLOATTRANSPARENCE, false))
                {
                    // add this item, it's not yet there
                    XFillFloatTransparenceItem aNewItem(rSet.Get(XATTR_FILLFLOATTRANSPARENCE));
                    basegfx::BGradient aGrad = aNewItem.GetGradientValue();
 
                    aNewItem.SetEnabled(true);
                    aGrad.SetStartIntens(100);
                    aGrad.SetEndIntens(100);
                    aNewItem.SetGradientValue(aGrad);
 
                    // add undo to allow user to take back this step
                    if (rModel.IsUndoEnabled())
                    {
                        rModel.BegUndo(SvxResId(SIP_XA_FILLTRANSPARENCE));
                        rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoAttrObject(*pObj));
                        rModel.EndUndo();
                    }
 
                    SfxItemSet aNewSet(rModel.GetItemPool());
                    aNewSet.Put(aNewItem);
                    pObj->SetMergedItemSetAndBroadcast(aNewSet);
                }
 
                // set values and transform to vector set
                GradTransVector aGradTransVector;
                GradTransGradient aGradTransGradient;
 
                aGradTransGradient.aGradient = rSet.Get(XATTR_FILLFLOATTRANSPARENCE).GetGradientValue();
                GradTransformer::GradToVec(aGradTransGradient, aGradTransVector, pObj);
 
                // build handles
                const Point aTmpPos1(basegfx::fround<tools::Long>(aGradTransVector.maPositionA.getX()), basegfx::fround<tools::Long>(aGradTransVector.maPositionA.getY()));
                const Point aTmpPos2(basegfx::fround<tools::Long>(aGradTransVector.maPositionB.getX()), basegfx::fround<tools::Long>(aGradTransVector.maPositionB.getY()));
                std::unique_ptr<SdrHdlColor> pColHdl1(new SdrHdlColor(aTmpPos1, aGradTransVector.aCol1, SDR_HANDLE_COLOR_SIZE_NORMAL, true));
                std::unique_ptr<SdrHdlColor> pColHdl2(new SdrHdlColor(aTmpPos2, aGradTransVector.aCol2, SDR_HANDLE_COLOR_SIZE_NORMAL, true));
                std::unique_ptr<SdrHdlGradient> pGradHdl(new SdrHdlGradient(aTmpPos1, aTmpPos2, false));
                DBG_ASSERT(pColHdl1 && pColHdl2 && pGradHdl, "Could not get all necessary handles!");
 
                // link them
                pGradHdl->SetColorHandles(pColHdl1.get(), pColHdl2.get());
                pGradHdl->SetObj(pObj);
                pColHdl1->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl));
                pColHdl2->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl));
 
                // insert them
                maHdlList.AddHdl(std::move(pColHdl1));
                maHdlList.AddHdl(std::move(pColHdl2));
                maHdlList.AddHdl(std::move(pGradHdl));
            }
            break;
        }
        case SdrDragMode::Gradient:
        {
            // add interactive gradient handle
            const SdrMarkList& rMarkList = GetMarkedObjectList();
            const size_t nMarkCount = rMarkList.GetMarkCount();
            if(nMarkCount == 1)
            {
                SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
                const SfxItemSet& rSet = pObj->GetMergedItemSet();
                drawing::FillStyle eFillStyle = rSet.Get(XATTR_FILLSTYLE).GetValue();
 
                if(eFillStyle == drawing::FillStyle_GRADIENT)
                {
                    // set values and transform to vector set
                    GradTransVector aGradTransVector;
                    GradTransGradient aGradTransGradient;
                    Size aHdlSize(15, 15);
 
                    aGradTransGradient.aGradient = rSet.Get(XATTR_FILLGRADIENT).GetGradientValue();
                    GradTransformer::GradToVec(aGradTransGradient, aGradTransVector, pObj);
 
                    // build handles
                    const Point aTmpPos1(basegfx::fround<tools::Long>(aGradTransVector.maPositionA.getX()), basegfx::fround<tools::Long>(aGradTransVector.maPositionA.getY()));
                    const Point aTmpPos2(basegfx::fround<tools::Long>(aGradTransVector.maPositionB.getX()), basegfx::fround<tools::Long>(aGradTransVector.maPositionB.getY()));
                    std::unique_ptr<SdrHdlColor> pColHdl1(new SdrHdlColor(aTmpPos1, aGradTransVector.aCol1, aHdlSize, false));
                    std::unique_ptr<SdrHdlColor> pColHdl2(new SdrHdlColor(aTmpPos2, aGradTransVector.aCol2, aHdlSize, false));
                    std::unique_ptr<SdrHdlGradient> pGradHdl(new SdrHdlGradient(aTmpPos1, aTmpPos2, true));
                    DBG_ASSERT(pColHdl1 && pColHdl2 && pGradHdl, "Could not get all necessary handles!");
 
                    // link them
                    pGradHdl->SetColorHandles(pColHdl1.get(), pColHdl2.get());
                    pGradHdl->SetObj(pObj);
                    pColHdl1->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl));
                    pColHdl2->SetColorChangeHdl(LINK(pGradHdl.get(), SdrHdlGradient, ColorChangeHdl));
 
                    // insert them
                    maHdlList.AddHdl(std::move(pColHdl1));
                    maHdlList.AddHdl(std::move(pColHdl2));
                    maHdlList.AddHdl(std::move(pGradHdl));
                }
            }
            break;
        }
        case SdrDragMode::Crop:
        {
            // TODO
            break;
        }
        default: break;
    }
}
 
/** handle mouse over effects for handles */
bool SdrMarkView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin)
{
    if(maHdlList.GetHdlCount())
    {
        SdrHdl* pMouseOverHdl = nullptr;
        if( !rMEvt.IsLeaveWindow() && pWin )
        {
            Point aMDPos( pWin->PixelToLogic( rMEvt.GetPosPixel() ) );
            pMouseOverHdl = PickHandle(aMDPos);
        }
 
        // notify last mouse over handle that he lost the mouse
        const size_t nHdlCount = maHdlList.GetHdlCount();
 
        for(size_t nHdl = 0; nHdl < nHdlCount; ++nHdl)
        {
            SdrHdl* pCurrentHdl = GetHdl(nHdl);
            if( pCurrentHdl->mbMouseOver )
            {
                if( pCurrentHdl != pMouseOverHdl )
                {
                    pCurrentHdl->mbMouseOver = false;
                    pCurrentHdl->onMouseLeave();
                }
                break;
            }
        }
 
        // notify current mouse over handle
        if( pMouseOverHdl )
        {
            pMouseOverHdl->mbMouseOver = true;
            pMouseOverHdl->onMouseEnter(rMEvt);
        }
    }
    return SdrSnapView::MouseMove(rMEvt, pWin);
}
 
bool SdrMarkView::RequestHelp(const HelpEvent& rHEvt)
{
    if (maHdlList.GetHdlCount())
    {
        const size_t nHdlCount = maHdlList.GetHdlCount();
 
        for (size_t nHdl = 0; nHdl < nHdlCount; ++nHdl)
        {
            SdrHdl* pCurrentHdl = GetHdl(nHdl);
            if (pCurrentHdl->mbMouseOver)
            {
                pCurrentHdl->onHelpRequest();
                return true;
            }
        }
    }
    return SdrSnapView::RequestHelp(rHEvt);
}
 
void SdrMarkView::ForceRefToMarked()
{
    switch(meDragMode)
    {
        case SdrDragMode::Rotate:
        {
            tools::Rectangle aR(GetMarkedObjRect());
            maRef1 = aR.Center();
 
            break;
        }
 
        case SdrDragMode::Mirror:
        {
            // first calculate the length of the axis of reflection
            tools::Long nOutMin=0;
            tools::Long nOutMax=0;
            tools::Long nMinLen=0;
            tools::Long nObjDst=0;
            tools::Long nOutHgt=0;
            OutputDevice* pOut=GetFirstOutputDevice();
            if (pOut!=nullptr) {
                // minimum length: 50 pixels
                nMinLen=pOut->PixelToLogic(Size(0,50)).Height();
                // 20 pixels distance to the Obj for the reference point
                nObjDst=pOut->PixelToLogic(Size(0,20)).Height();
                // MinY/MaxY
                // margin = minimum length = 10 pixels
                tools::Long nDst=pOut->PixelToLogic(Size(0,10)).Height();
                nOutMin=-pOut->GetMapMode().GetOrigin().Y();
                nOutMax=pOut->GetOutputSize().Height()-1+nOutMin;
                nOutMin+=nDst;
                nOutMax-=nDst;
                // absolute minimum length, however, is 10 pixels
                if (nOutMax-nOutMin<nDst) {
                    nOutMin+=nOutMax+1;
                    nOutMin/=2;
                    nOutMin-=(nDst+1)/2;
                    nOutMax=nOutMin+nDst;
                }
                nOutHgt=nOutMax-nOutMin;
                // otherwise minimum length = 1/4 OutHgt
                tools::Long nTemp=nOutHgt/4;
                if (nTemp>nMinLen) nMinLen=nTemp;
            }
 
            tools::Rectangle aR(GetMarkedObjBoundRect());
            Point aCenter(aR.Center());
            tools::Long nMarkHgt=aR.GetHeight()-1;
            tools::Long nHgt=nMarkHgt+nObjDst*2;       // 20 pixels overlapping above and below
            if (nHgt<nMinLen) nHgt=nMinLen;     // minimum length 50 pixels or 1/4 OutHgt, respectively
 
            tools::Long nY1=aCenter.Y()-(nHgt+1)/2;
            tools::Long nY2=nY1+nHgt;
 
            if (pOut!=nullptr && nMinLen>nOutHgt) nMinLen=nOutHgt; // TODO: maybe shorten this a little
 
            if (pOut!=nullptr) { // now move completely into the visible area
                if (nY1<nOutMin) {
                    nY1=nOutMin;
                    if (nY2<nY1+nMinLen) nY2=nY1+nMinLen;
                }
                if (nY2>nOutMax) {
                    nY2=nOutMax;
                    if (nY1>nY2-nMinLen) nY1=nY2-nMinLen;
                }
            }
 
            maRef1.setX(aCenter.X() );
            maRef1.setY(nY1 );
            maRef2.setX(aCenter.X() );
            maRef2.setY(nY2 );
 
            break;
        }
 
        case SdrDragMode::Transparence:
        case SdrDragMode::Gradient:
        case SdrDragMode::Crop:
        {
            tools::Rectangle aRect(GetMarkedObjBoundRect());
            maRef1 = aRect.TopLeft();
            maRef2 = aRect.BottomRight();
            break;
        }
        default: break;
    }
}
 
void SdrMarkView::SetRef1(const Point& rPt)
{
    if(meDragMode == SdrDragMode::Rotate || meDragMode == SdrDragMode::Mirror)
    {
        maRef1 = rPt;
        SdrHdl* pH = maHdlList.GetHdl(SdrHdlKind::Ref1);
        if(pH)
            pH->SetPos(rPt);
    }
}
 
void SdrMarkView::SetRef2(const Point& rPt)
{
    if(meDragMode == SdrDragMode::Mirror)
    {
        maRef2 = rPt;
        SdrHdl* pH = maHdlList.GetHdl(SdrHdlKind::Ref2);
        if(pH)
            pH->SetPos(rPt);
    }
}
 
SfxViewShell* SdrMarkView::GetSfxViewShell() const
{
    return SfxViewShell::Current();
}
 
void SdrMarkView::CheckMarked()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    for (size_t nm=rMarkList.GetMarkCount(); nm>0;) {
        --nm;
        SdrMark* pM = rMarkList.GetMark(nm);
        SdrObject* pObj = pM->GetMarkedSdrObj();
        SdrPageView* pPV = pM->GetPageView();
        bool bRaus = !pObj || !pPV->IsObjMarkable(pObj);
        if (bRaus)
        {
            GetMarkedObjectListWriteAccess().DeleteMark(nm);
        }
        else
        {
            if (!IsGluePointEditMode()) { // selected gluepoints only in GlueEditMode
                SdrUShortCont& rPts = pM->GetMarkedGluePoints();
                rPts.clear();
            }
        }
    }
 
    // at least reset the remembered BoundRect to prevent handle
    // generation if bForceFrameHandles is TRUE.
    mbMarkedObjRectDirty = true;
}
 
void SdrMarkView::SetMarkRects()
{
    SdrPageView* pPV = GetSdrPageView();
 
    if(pPV)
    {
        const SdrMarkList& rMarkList = GetMarkedObjectList();
        pPV->SetHasMarkedObj(rMarkList.TakeSnapRect(pPV, pPV->MarkSnap()));
        rMarkList.TakeBoundRect(pPV, pPV->MarkBound());
    }
}
 
void SdrMarkView::SetFrameHandles(bool bOn)
{
    if (bOn!=mbForceFrameHandles) {
        bool bOld=ImpIsFrameHandles();
        mbForceFrameHandles=bOn;
        bool bNew=ImpIsFrameHandles();
        if (bNew!=bOld) {
            AdjustMarkHdl();
            MarkListHasChanged();
        }
    }
}
 
void SdrMarkView::SetEditMode(SdrViewEditMode eMode)
{
    if (eMode==meEditMode)        return;
 
    bool bGlue0=meEditMode==SdrViewEditMode::GluePointEdit;
    bool bEdge0=static_cast<SdrCreateView*>(this)->IsEdgeTool();
    meEditMode0=meEditMode;
    meEditMode=eMode;
    bool bGlue1=meEditMode==SdrViewEditMode::GluePointEdit;
    bool bEdge1=static_cast<SdrCreateView*>(this)->IsEdgeTool();
    // avoid flickering when switching between GlueEdit and EdgeTool
    if (bGlue1 && !bGlue0) ImpSetGlueVisible2(bGlue1);
    if (bEdge1!=bEdge0) ImpSetGlueVisible3(bEdge1);
    if (!bGlue1 && bGlue0) ImpSetGlueVisible2(bGlue1);
    if (bGlue0 && !bGlue1) UnmarkAllGluePoints();
}
 
 
bool SdrMarkView::IsObjMarkable(SdrObject const * pObj, SdrPageView const * pPV) const
{
    if (pObj)
    {
        if (pObj->IsMarkProtect() ||
            (!mbDesignMode && pObj->IsUnoObj()))
        {
            // object not selectable or
            // SdrUnoObj not in DesignMode
            return false;
        }
    }
    return pPV==nullptr || pPV->IsObjMarkable(pObj);
}
 
bool SdrMarkView::IsMarkedObjHit(const Point& rPnt, short nTol) const
{
    bool bRet=false;
    nTol=ImpGetHitTolLogic(nTol,nullptr);
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    for (size_t nm=0; nm<rMarkList.GetMarkCount() && !bRet; ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        bRet = nullptr != CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pM->GetPageView(),SdrSearchOptions::NONE,nullptr);
    }
    return bRet;
}
 
SdrHdl* SdrMarkView::PickHandle(const Point& rPnt) const
{
    if (mbSomeObjChgdFlag) { // recalculate handles, if necessary
        FlushComeBackTimer();
    }
    return maHdlList.IsHdlListHit(rPnt);
}
 
bool SdrMarkView::MarkObj(const Point& rPnt, short nTol, bool bToggle, bool bDeep)
{
    SdrPageView* pPV;
    nTol=ImpGetHitTolLogic(nTol,nullptr);
    SdrSearchOptions nOptions=SdrSearchOptions::PICKMARKABLE;
    if (bDeep) nOptions=nOptions|SdrSearchOptions::DEEP;
    SdrObject* pObj = PickObj(rPnt, static_cast<sal_uInt16>(nTol), pPV, nOptions);
    if (pObj) {
        bool bUnmark=bToggle && IsObjMarked(pObj);
        MarkObj(pObj,pPV,bUnmark);
    }
    return pObj != nullptr;
}
 
bool SdrMarkView::MarkNextObj(bool bPrev)
{
    SdrPageView* pPageView = GetSdrPageView();
 
    if(!pPageView)
    {
        return false;
    }
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    size_t nChgMarkNum = SAL_MAX_SIZE; // number of the MarkEntry we want to replace
    size_t nSearchObjNum = bPrev ? 0 : SAL_MAX_SIZE;
    if (nMarkCount!=0) {
        nChgMarkNum=bPrev ? 0 : nMarkCount-1;
        SdrMark* pM=rMarkList.GetMark(nChgMarkNum);
        assert(pM != nullptr);
        if (pM->GetMarkedSdrObj() != nullptr)
            nSearchObjNum = pM->GetMarkedSdrObj()->GetNavigationPosition();
    }
 
    SdrObject* pMarkObj=nullptr;
    SdrObjList* pSearchObjList=pPageView->GetObjList();
    const size_t nObjCount = pSearchObjList->GetObjCount();
    if (nObjCount!=0) {
        if (nSearchObjNum>nObjCount) nSearchObjNum=nObjCount;
        while (pMarkObj==nullptr && ((!bPrev && nSearchObjNum>0) || (bPrev && nSearchObjNum<nObjCount)))
        {
            if (!bPrev)
                nSearchObjNum--;
            SdrObject* pSearchObj = pSearchObjList->GetObjectForNavigationPosition(nSearchObjNum);
            if (IsObjMarkable(pSearchObj,pPageView))
            {
                if (rMarkList.FindObject(pSearchObj)==SAL_MAX_SIZE)
                {
                    pMarkObj=pSearchObj;
                }
            }
            if (bPrev) nSearchObjNum++;
        }
    }
 
    if(!pMarkObj)
    {
        return false;
    }
 
    if (nChgMarkNum!=SAL_MAX_SIZE)
    {
        GetMarkedObjectListWriteAccess().DeleteMark(nChgMarkNum);
    }
    MarkObj(pMarkObj,pPageView); // also calls MarkListHasChanged(), AdjustMarkHdl()
    return true;
}
 
bool SdrMarkView::MarkNextObj(const Point& rPnt, short nTol, bool bPrev)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    nTol=ImpGetHitTolLogic(nTol,nullptr);
    SdrMark* pTopMarkHit=nullptr;
    SdrMark* pBtmMarkHit=nullptr;
    size_t nTopMarkHit=0;
    size_t nBtmMarkHit=0;
    // find topmost of the selected objects that is hit by rPnt
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nm=nMarkCount; nm>0 && pTopMarkHit==nullptr;) {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        if(CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pM->GetPageView(),SdrSearchOptions::NONE,nullptr))
        {
            pTopMarkHit=pM;
            nTopMarkHit=nm;
        }
    }
    // nothing found, in this case, just select an object
    if (pTopMarkHit==nullptr) return MarkObj(rPnt,sal_uInt16(nTol));
 
    SdrObject* pTopObjHit=pTopMarkHit->GetMarkedSdrObj();
    SdrObjList* pObjList=pTopObjHit->getParentSdrObjListFromSdrObject();
    SdrPageView* pPV=pTopMarkHit->GetPageView();
    // find lowermost of the selected objects that is hit by rPnt
    // and is placed on the same PageView as pTopMarkHit
    for (size_t nm=0; nm<nMarkCount && pBtmMarkHit==nullptr; ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrPageView* pPV2=pM->GetPageView();
        if (pPV2==pPV && CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pM->GetMarkedSdrObj(),pPV2,SdrSearchOptions::NONE,nullptr))
        {
            pBtmMarkHit=pM;
            nBtmMarkHit=nm;
        }
    }
    if (pBtmMarkHit==nullptr) { pBtmMarkHit=pTopMarkHit; nBtmMarkHit=nTopMarkHit; }
    SdrObject* pBtmObjHit=pBtmMarkHit->GetMarkedSdrObj();
    const size_t nObjCount = pObjList->GetObjCount();
 
    size_t nSearchBeg(0);
    E3dScene* pScene(nullptr);
    SdrObject* pObjHit(bPrev ? pBtmObjHit : pTopObjHit);
    bool bRemap =
        nullptr != dynamic_cast< const E3dCompoundObject* >(pObjHit);
    if (bRemap)
    {
        pScene = DynCastE3dScene(pObjHit->getParentSdrObjectFromSdrObject());
        bRemap = nullptr != pScene;
    }
 
    if(bPrev)
    {
        sal_uInt32 nOrdNumBtm(pBtmObjHit->GetOrdNum());
 
        if(bRemap)
        {
            nOrdNumBtm = pScene->RemapOrdNum(nOrdNumBtm);
        }
 
        nSearchBeg = nOrdNumBtm + 1;
    }
    else
    {
        sal_uInt32 nOrdNumTop(pTopObjHit->GetOrdNum());
 
        if(bRemap)
        {
            nOrdNumTop = pScene->RemapOrdNum(nOrdNumTop);
        }
 
        nSearchBeg = nOrdNumTop;
    }
 
    size_t no=nSearchBeg;
    SdrObject* pFndObj=nullptr;
    while (pFndObj==nullptr && ((!bPrev && no>0) || (bPrev && no<nObjCount))) {
        if (!bPrev) no--;
        SdrObject* pObj;
 
        if(bRemap)
        {
            pObj = pObjList->GetObj(pScene->RemapOrdNum(no));
        }
        else
        {
            pObj = pObjList->GetObj(no);
        }
 
        if (CheckSingleSdrObjectHit(rPnt,sal_uInt16(nTol),pObj,pPV,SdrSearchOptions::TESTMARKABLE,nullptr))
        {
            if (rMarkList.FindObject(pObj)==SAL_MAX_SIZE) {
                pFndObj=pObj;
            } else {
                // TODO: for performance reasons set on to Top or Btm, if necessary
            }
        }
        if (bPrev) no++;
    }
    if (pFndObj!=nullptr)
    {
        GetMarkedObjectListWriteAccess().DeleteMark(bPrev?nBtmMarkHit:nTopMarkHit);
        GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pFndObj,pPV));
        MarkListHasChanged();
        AdjustMarkHdl();
    }
    return pFndObj!=nullptr;
}
 
void SdrMarkView::MarkObj(const tools::Rectangle& rRect, bool bUnmark)
{
    bool bFnd=false;
    tools::Rectangle aR(rRect);
    SdrObjList* pObjList;
    BrkAction();
    SdrPageView* pPV = GetSdrPageView();
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(pPV)
    {
        pObjList=pPV->GetObjList();
        tools::Rectangle aFrm1(aR);
        for (const rtl::Reference<SdrObject>& pObj : *pObjList) {
            tools::Rectangle aRect(pObj->GetCurrentBoundRect());
            if (aFrm1.Contains(aRect)) {
                if (!bUnmark) {
                    if (IsObjMarkable(pObj.get(),pPV))
                    {
                        GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pObj.get(),pPV));
                        bFnd=true;
                    }
                } else {
                    const size_t nPos=rMarkList.FindObject(pObj.get());
                    if (nPos!=SAL_MAX_SIZE)
                    {
                        GetMarkedObjectListWriteAccess().DeleteMark(nPos);
                        bFnd=true;
                    }
                }
            }
        }
    }
    if (bFnd) {
        rMarkList.ForceSort();
        MarkListHasChanged();
        AdjustMarkHdl();
    }
}
 
namespace {
 
void collectUIInformation(const SdrObject* pObj)
{
    EventDescription aDescription;
    aDescription.aAction = "SELECT";
    aDescription.aParent = "MainWindow";
    aDescription.aKeyWord = "CurrentApp";
 
    if (!pObj->GetName().isEmpty())
        aDescription.aParameters = {{"OBJECT", pObj->GetName()}};
    else
        aDescription.aParameters = {{"OBJECT", "Unnamed_Obj_" + OUString::number(pObj->GetOrdNum())}};
 
    UITestLogger::getInstance().logEvent(aDescription);
}
 
}
 
 void SdrMarkView::MarkObj(SdrObject* pObj, SdrPageView* pPV, bool bUnmark, bool bDoNoSetMarkHdl,
                          std::vector<basegfx::B2DRectangle> && rSubSelections)
{
    if (!(pObj!=nullptr && pPV!=nullptr && IsObjMarkable(pObj, pPV)))
        return;
 
    BrkAction();
    if (!bUnmark)
    {
        GetMarkedObjectListWriteAccess().InsertEntry(SdrMark(pObj,pPV));
        collectUIInformation(pObj);
    }
    else
    {
        const SdrMarkList& rMarkList = GetMarkedObjectList();
        const size_t nPos=rMarkList.FindObject(pObj);
        if (nPos!=SAL_MAX_SIZE)
        {
            GetMarkedObjectListWriteAccess().DeleteMark(nPos);
        }
    }
 
    maSubSelectionList = std::move(rSubSelections);
 
    if (!bDoNoSetMarkHdl) {
        MarkListHasChanged();
        AdjustMarkHdl();
    }
}
 
bool SdrMarkView::IsObjMarked(SdrObject const * pObj) const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    return rMarkList.FindObject(pObj)!=SAL_MAX_SIZE;
}
 
sal_uInt16 SdrMarkView::GetMarkHdlSizePixel() const
{
    return maHdlList.GetHdlSize()*2+1;
}
 
void SdrMarkView::SetMarkHdlSizePixel(sal_uInt16 nSiz)
{
    if (nSiz<3) nSiz=3;
    nSiz/=2;
    if (nSiz!=maHdlList.GetHdlSize()) {
        maHdlList.SetHdlSize(nSiz);
    }
}
 
bool SdrMarkView::getPossibleGridOffsetForSdrObject(
    basegfx::B2DVector& rOffset,
    const SdrObject* pObj,
    const SdrPageView* pPV) const
{
    if(nullptr == pObj || nullptr == pPV)
    {
        return false;
    }
 
    const OutputDevice* pOutputDevice(GetFirstOutputDevice());
 
    if(nullptr == pOutputDevice)
    {
        return false;
    }
 
    const SdrPageWindow* pSdrPageWindow(pPV->FindPageWindow(*pOutputDevice));
 
    if(nullptr == pSdrPageWindow)
    {
        return false;
    }
 
    const sdr::contact::ObjectContact& rObjectContact(pSdrPageWindow->GetObjectContact());
 
    if(!rObjectContact.supportsGridOffsets())
    {
        return false;
    }
 
    const sdr::contact::ViewObjectContact& rVOC(pObj->GetViewContact().GetViewObjectContact(
        const_cast<sdr::contact::ObjectContact&>(rObjectContact)));
 
    rOffset = rVOC.getGridOffset();
 
    return !rOffset.equalZero();
}
 
bool SdrMarkView::getPossibleGridOffsetForPosition(
    basegfx::B2DVector& rOffset,
    const basegfx::B2DPoint& rPoint,
    const SdrPageView* pPV) const
{
    if(nullptr == pPV)
    {
        return false;
    }
 
    const OutputDevice* pOutputDevice(GetFirstOutputDevice());
 
    if(nullptr == pOutputDevice)
    {
        return false;
    }
 
    const SdrPageWindow* pSdrPageWindow(pPV->FindPageWindow(*pOutputDevice));
 
    if(nullptr == pSdrPageWindow)
    {
        return false;
    }
 
    const sdr::contact::ObjectContact& rObjectContact(pSdrPageWindow->GetObjectContact());
 
    if(!rObjectContact.supportsGridOffsets())
    {
        return false;
    }
 
    rObjectContact.calculateGridOffsetForB2DRange(rOffset, basegfx::B2DRange(rPoint));
 
    return !rOffset.equalZero();
}
 
SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObject* pObj, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay) const
{
    assert(pObj);
    if(((nOptions & SdrSearchOptions::IMPISMASTER) && pObj->IsNotVisibleAsMaster()) || (!pObj->IsVisible()))
    {
        return nullptr;
    }
 
    const bool bCheckIfMarkable(nOptions & SdrSearchOptions::TESTMARKABLE);
    const bool bDeep(nOptions & SdrSearchOptions::DEEP);
    const bool bOLE(DynCastSdrOle2Obj(pObj) !=  nullptr);
    auto pTextObj = DynCastSdrTextObj( pObj);
    const bool bTXT(pTextObj && pTextObj->IsTextFrame());
    SdrObject* pRet=nullptr;
    tools::Rectangle aRect(pObj->GetCurrentBoundRect());
 
    // add possible GridOffset to up-to-now view-independent BoundRect data
    basegfx::B2DVector aGridOffset(0.0, 0.0);
    if(getPossibleGridOffsetForSdrObject(aGridOffset, pObj, pPV))
    {
        aRect += Point(
            basegfx::fround<tools::Long>(aGridOffset.getX()),
            basegfx::fround<tools::Long>(aGridOffset.getY()));
    }
 
    double nTol2(nTol);
 
    // double tolerance for OLE, text frames and objects in
    // active text edit
    if(bOLE || bTXT || pObj==static_cast<const SdrObjEditView*>(this)->GetTextEditObject())
    {
        nTol2*=2;
    }
 
    aRect.expand(nTol2); // add 1 tolerance for all objects
 
    if (aRect.Contains(rPnt))
    {
        if (!bCheckIfMarkable || IsObjMarkable(pObj,pPV))
        {
            SdrObjList* pOL=pObj->GetSubList();
 
            if (pOL!=nullptr && pOL->GetObjCount()!=0)
            {
                SdrObject* pTmpObj;
                // adjustment hit point for virtual objects
                Point aPnt( rPnt );
 
                if ( auto pVirtObj = dynamic_cast<const SdrVirtObj*>( pObj) )
                {
                    Point aOffset = pVirtObj->GetOffset();
                    aPnt.Move( -aOffset.X(), -aOffset.Y() );
                }
 
                pRet=CheckSingleSdrObjectHit(aPnt,nTol,pOL,pPV,nOptions,pMVisLay,pTmpObj);
            }
            else
            {
                if(!pMVisLay || pMVisLay->IsSet(pObj->GetLayer()))
                {
                    pRet = SdrObjectPrimitiveHit(*pObj, rPnt, {nTol2, nTol2}, *pPV, &pPV->GetVisibleLayers(), false);
                }
            }
        }
    }
 
    if (!bDeep && pRet!=nullptr)
    {
        pRet=pObj;
    }
 
    return pRet;
}
 
SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObjList const * pOL, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay, SdrObject*& rpRootObj) const
{
    return (*this).CheckSingleSdrObjectHit(rPnt,nTol,pOL,pPV,nOptions,pMVisLay,rpRootObj,nullptr);
}
SdrObject* SdrMarkView::CheckSingleSdrObjectHit(const Point& rPnt, sal_uInt16 nTol, SdrObjList const * pOL, SdrPageView* pPV, SdrSearchOptions nOptions, const SdrLayerIDSet* pMVisLay, SdrObject*& rpRootObj,const SdrMarkList * pMarkList) const
{
    SdrObject* pRet=nullptr;
    rpRootObj=nullptr;
    if (!pOL)
        return nullptr;
    const E3dScene* pRemapScene = DynCastE3dScene(pOL->getSdrObjectFromSdrObjList());
    const size_t nObjCount(pOL->GetObjCount());
    size_t nObjNum(nObjCount);
 
    while (pRet==nullptr && nObjNum>0)
    {
        nObjNum--;
        SdrObject* pObj;
 
        if(pRemapScene)
        {
            pObj = pOL->GetObj(pRemapScene->RemapOrdNum(nObjNum));
        }
        else
        {
            pObj = pOL->GetObj(nObjNum);
        }
        if (nOptions & SdrSearchOptions::BEFOREMARK)
        {
            if (pMarkList!=nullptr)
            {
                if ((*pMarkList).FindObject(pObj)!=SAL_MAX_SIZE)
                {
                    return nullptr;
                }
            }
        }
        pRet=CheckSingleSdrObjectHit(rPnt,nTol,pObj,pPV,nOptions,pMVisLay);
        if (pRet!=nullptr) rpRootObj=pObj;
    }
    return pRet;
}
 
SdrObject* SdrMarkView::PickObj(const Point& rPnt, short nTol, SdrPageView*& rpPV, SdrSearchOptions nOptions) const
{
    return PickObj(rPnt, nTol, rpPV, nOptions, nullptr);
}
 
SdrObject* SdrMarkView::PickObj(const Point& rPnt, short nTol, SdrPageView*& rpPV, SdrSearchOptions nOptions, SdrObject** ppRootObj, bool* pbHitPassDirect) const
{ // TODO: lacks a Pass2,Pass3
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    if (ppRootObj!=nullptr) *ppRootObj=nullptr;
    if (pbHitPassDirect!=nullptr) *pbHitPassDirect=true;
    SdrObject* pRet = nullptr;
    rpPV=nullptr;
    bool bMarked(nOptions & SdrSearchOptions::MARKED);
    bool bMasters=!bMarked && bool(nOptions & SdrSearchOptions::ALSOONMASTER);
    // nOptions & SdrSearchOptions::NEXT: n.i.
    // nOptions & SdrSearchOptions::PASS2BOUND: n.i.
    // nOptions & SdrSearchOptions::PASS3NEAREST// n.i.
    if (nTol<0) nTol=ImpGetHitTolLogic(nTol,nullptr);
    SdrObject* pObj=nullptr;
    SdrObject* pHitObj=nullptr;
    SdrPageView* pPV=nullptr;
    if (static_cast<const SdrObjEditView*>(this)->IsTextEditFrameHit(rPnt)) {
        pObj=static_cast<const SdrObjEditView*>(this)->GetTextEditObject();
        pHitObj=pObj;
        pPV=static_cast<const SdrObjEditView*>(this)->GetTextEditPageView();
    }
    if (bMarked) {
        const size_t nMrkCnt=rMarkList.GetMarkCount();
        size_t nMrkNum=nMrkCnt;
        while (pHitObj==nullptr && nMrkNum>0) {
            nMrkNum--;
            SdrMark* pM=rMarkList.GetMark(nMrkNum);
            pObj=pM->GetMarkedSdrObj();
            pPV=pM->GetPageView();
            pHitObj=CheckSingleSdrObjectHit(rPnt,nTol,pObj,pPV,nOptions,nullptr);
        }
    }
    else
    {
        pPV = GetSdrPageView();
 
        if(pPV)
        {
            SdrPage* pPage=pPV->GetPage();
            sal_uInt16 nPgCount=1;
 
            if(bMasters && pPage->TRG_HasMasterPage())
            {
                nPgCount++;
            }
            bool bWholePage(nOptions & SdrSearchOptions::WHOLEPAGE);
            bool bExtraPassForWholePage=bWholePage && pPage!=pPV->GetObjList();
            if (bExtraPassForWholePage) nPgCount++; // First search in AktObjList, then on the entire page
            sal_uInt16 nPgNum=nPgCount;
            while (pHitObj==nullptr && nPgNum>0) {
                SdrSearchOptions nTmpOptions=nOptions;
                nPgNum--;
                const SdrLayerIDSet* pMVisLay=nullptr;
                SdrObjList* pObjList=nullptr;
                if (pbHitPassDirect!=nullptr) *pbHitPassDirect = true;
                if (nPgNum>=nPgCount-1 || (bExtraPassForWholePage && nPgNum>=nPgCount-2))
                {
                    pObjList=pPV->GetObjList();
                    if (bExtraPassForWholePage && nPgNum==nPgCount-2) {
                        pObjList=pPage;
                        if (pbHitPassDirect!=nullptr) *pbHitPassDirect = false;
                    }
                }
                else
                {
                    // otherwise MasterPage
                    SdrPage& rMasterPage = pPage->TRG_GetMasterPage();
                    pMVisLay = &pPage->TRG_GetMasterPageVisibleLayers();
                    pObjList = &rMasterPage;
 
                    if (pbHitPassDirect!=nullptr) *pbHitPassDirect = false;
                    nTmpOptions=nTmpOptions | SdrSearchOptions::IMPISMASTER;
                }
                pHitObj=CheckSingleSdrObjectHit(rPnt,nTol,pObjList,pPV,nTmpOptions,pMVisLay,pObj,&rMarkList);
            }
        }
    }
    if (pHitObj!=nullptr) {
        if (ppRootObj!=nullptr) *ppRootObj=pObj;
        if (nOptions & SdrSearchOptions::DEEP) pObj=pHitObj;
        if (nOptions & SdrSearchOptions::TESTTEXTEDIT) {
            assert(pPV);
            if (!pObj->HasTextEdit() || pPV->GetLockedLayers().IsSet(pObj->GetLayer())) {
                pObj=nullptr;
            }
        }
        if (pObj!=nullptr && (nOptions & SdrSearchOptions::TESTMACRO)) {
            assert(pPV);
            SdrObjMacroHitRec aHitRec;
            aHitRec.aPos=rPnt;
            aHitRec.nTol=nTol;
            aHitRec.pVisiLayer=&pPV->GetVisibleLayers();
            aHitRec.pPageView=pPV;
            if (!pObj->HasMacro() || !pObj->IsMacroHit(aHitRec)) pObj=nullptr;
        }
        if (pObj!=nullptr) {
            pRet=pObj;
            rpPV=pPV;
        }
    }
    return pRet;
}
 
bool SdrMarkView::PickMarkedObj(const Point& rPnt, SdrObject*& rpObj, SdrPageView*& rpPV, SdrSearchOptions nOptions) const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    const bool bBoundCheckOn2ndPass(nOptions & SdrSearchOptions::PASS2BOUND);
    rpObj=nullptr;
    rpPV=nullptr;
    const size_t nMarkCount=rMarkList.GetMarkCount();
    for (size_t nMarkNum=nMarkCount; nMarkNum>0;) {
        --nMarkNum;
        SdrMark* pM=rMarkList.GetMark(nMarkNum);
        SdrPageView* pPV=pM->GetPageView();
        SdrObject* pObj=pM->GetMarkedSdrObj();
        if (CheckSingleSdrObjectHit(rPnt,mnHitTolLog,pObj,pPV,SdrSearchOptions::TESTMARKABLE,nullptr)) {
            rpObj=pObj;
            rpPV=pPV;
            return true;
        }
    }
    if (bBoundCheckOn2ndPass) {
        for (size_t nMarkNum=nMarkCount; nMarkNum>0;) {
            --nMarkNum;
            SdrMark* pM=rMarkList.GetMark(nMarkNum);
            SdrPageView* pPV=pM->GetPageView();
            SdrObject* pObj=pM->GetMarkedSdrObj();
            tools::Rectangle aRect(pObj->GetCurrentBoundRect());
            aRect.AdjustLeft( -mnHitTolLog );
            aRect.AdjustTop( -mnHitTolLog );
            aRect.AdjustRight(mnHitTolLog );
            aRect.AdjustBottom(mnHitTolLog );
            if (aRect.Contains(rPnt)) {
                rpObj=pObj;
                rpPV=pPV;
                return true;
            }
        }
    }
    return false;
}
 
 
void SdrMarkView::UnmarkAllObj(SdrPageView const * pPV)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount()==0)
        return;
 
    BrkAction();
    if (pPV!=nullptr)
    {
        GetMarkedObjectListWriteAccess().DeletePageView(*pPV);
    }
    else
    {
        GetMarkedObjectListWriteAccess().Clear();
    }
    mpMarkedObj=nullptr;
    mpMarkedPV=nullptr;
    MarkListHasChanged();
    AdjustMarkHdl();
}
 
void SdrMarkView::MarkAllObj(SdrPageView* pPV)
{
    BrkAction();
 
    if(!pPV)
    {
        pPV = GetSdrPageView();
    }
 
    // #i69171# pPV may still be NULL if there is no SDrPageView (!), e.g. when inserting
    // other files
    if(pPV)
    {
        const bool bMarkChg(GetMarkedObjectListWriteAccess().InsertPageView(*pPV));
 
        if(bMarkChg)
        {
            MarkListHasChanged();
        }
    }
 
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if(rMarkList.GetMarkCount())
    {
        AdjustMarkHdl();
    }
}
 
void SdrMarkView::AdjustMarkHdl(SfxViewShell* pOtherShell)
{
    CheckMarked();
    SetMarkRects();
    SetMarkHandles(pOtherShell);
}
 
// BoundRect in model coordinates, no GridOffset added
tools::Rectangle SdrMarkView::GetMarkedObjBoundRect() const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    tools::Rectangle aRect;
    for (size_t nm=0; nm<rMarkList.GetMarkCount(); ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pO=pM->GetMarkedSdrObj();
        tools::Rectangle aR1(pO->GetCurrentBoundRect());
        if (aRect.IsEmpty()) aRect=aR1;
        else aRect.Union(aR1);
    }
    return aRect;
}
 
// ObjRect in model coordinates, no GridOffset added
const tools::Rectangle& SdrMarkView::GetMarkedObjRect() const
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (mbMarkedObjRectDirty) {
        const_cast<SdrMarkView*>(this)->mbMarkedObjRectDirty=false;
        tools::Rectangle aRect;
        for (size_t nm=0; nm<rMarkList.GetMarkCount(); ++nm) {
            SdrMark* pM=rMarkList.GetMark(nm);
            SdrObject* pO = pM->GetMarkedSdrObj();
            if (!pO)
                continue;
            tools::Rectangle aR1(pO->GetSnapRect());
            if (aRect.IsEmpty()) aRect=aR1;
            else aRect.Union(aR1);
        }
        const_cast<SdrMarkView*>(this)->maMarkedObjRect=aRect;
    }
    return maMarkedObjRect;
}
 
 
OUString SdrMarkView::ImpGetDescriptionString(TranslateId pStrCacheID, ImpGetDescriptionOptions nOpt) const
{
    OUString sStr = SvxResId(pStrCacheID);
    const sal_Int32 nPos = sStr.indexOf("%1");
 
    if(nPos != -1)
    {
        const SdrMarkList& rMarkList = GetMarkedObjectList();
        if(nOpt == ImpGetDescriptionOptions::POINTS)
        {
            sStr = sStr.replaceAt(nPos, 2, rMarkList.GetPointMarkDescription());
        }
        else if(nOpt == ImpGetDescriptionOptions::GLUEPOINTS)
        {
            sStr = sStr.replaceAt(nPos, 2, rMarkList.GetGluePointMarkDescription());
        }
        else
        {
            sStr = sStr.replaceAt(nPos, 2, rMarkList.GetMarkDescription());
        }
    }
 
    return sStr.replaceFirst("%2", "0");
}
 
 
void SdrMarkView::EnterMarkedGroup()
{
    // We enter only the first group found (in only one PageView), because
    // PageView::EnterGroup calls an AdjustMarkHdl.
    // TODO: I'll have to prevent that via a flag.
    SdrPageView* pPV = GetSdrPageView();
 
    if(!pPV)
        return;
 
    bool bEnter=false;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    for (size_t nm = rMarkList.GetMarkCount(); nm > 0 && !bEnter;)
    {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        if (pM->GetPageView()==pPV) {
            SdrObject* pObj=pM->GetMarkedSdrObj();
            if (pObj->IsGroupObject()) {
                if (pPV->EnterGroup(pObj)) {
                    bEnter=true;
                }
            }
        }
    }
}
 
 
void SdrMarkView::MarkListHasChanged()
{
    GetMarkedObjectListWriteAccess().SetNameDirty();
    maSdrViewSelection.SetEdgesOfMarkedNodesDirty();
 
    mbMarkedObjRectDirty=true;
    mbMarkedPointsRectsDirty=true;
    bool bOneEdgeMarked=false;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount()==1) {
        const SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
        if (pObj->GetObjInventor()==SdrInventor::Default) {
            bOneEdgeMarked = pObj->GetObjIdentifier() == SdrObjKind::Edge;
        }
    }
    ImpSetGlueVisible4(bOneEdgeMarked);
}
 
 
void SdrMarkView::SetMoveOutside(bool bOn)
{
    maHdlList.SetMoveOutside(bOn);
}
 
void SdrMarkView::SetDesignMode( bool bOn )
{
    if ( mbDesignMode != bOn )
    {
        mbDesignMode = bOn;
        SdrPageView* pPageView = GetSdrPageView();
        if ( pPageView )
            pPageView->SetDesignMode( bOn );
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'Union' is required to be utilized.

V530 The return value of function 'Union' is required to be utilized.

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 2035, 2036.

V1004 The 'mpMarkedPV' pointer was used unsafely after it was verified against nullptr. Check lines: 739, 744.