/* -*- 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/svdedtv.hxx>
#include <svx/svdundo.hxx>
#include <svx/svdogrp.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svditer.hxx>
#include <svx/svdograf.hxx>
#include <svx/svdoole2.hxx>
#include <svx/dialmgr.hxx>
#include <svx/sdooitm.hxx>
#include <svx/sdshitm.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xtextit0.hxx>
#include "svdfmtf.hxx"
#include <svdpdf.hxx>
#include <svx/svdetc.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/eeitem.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <svx/strings.hrc>
#include <svx/svdoashp.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <i18nutil/unicode.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <memory>
#include <vector>
#include <vcl/graph.hxx>
#include <svx/svxids.hrc>
#include <dstribut_enum.hxx>
#include <osl/diagnose.h>

using namespace com::sun::star;

SdrObject* SdrEditView::GetMaxToTopObj(SdrObject* /*pObj*/) const
{
  return nullptr;
}

SdrObject* SdrEditView::GetMaxToBtmObj(SdrObject* /*pObj*/) const
{
  return nullptr;
}

void SdrEditView::ObjOrderChanged(SdrObject* /*pObj*/, size_t /*nOldPos*/, size_t /*nNewPos*/)
{
}

void SdrEditView::MovMarkedToTop()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0)
        return;

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
        BegUndo(SvxResId(STR_EditMovToTop),rMarkList.GetMarkDescription(),SdrRepeatFunc::MoveToTop);

    rMarkList.ForceSort();
    for (size_t nm=0; nm<nCount; ++nm)
    { // All Ordnums have to be correct!
        rMarkList.GetMark(nm)->GetMarkedSdrObj()->GetOrdNum();
    }
    bool bChg=false;
    SdrObjList* pOL0=nullptr;
    size_t nNewPos=0;
    for (size_t nm=nCount; nm>0;)
    {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
        if (pOL!=pOL0)
        {
            nNewPos = pOL->GetObjCount()-1;
            pOL0=pOL;
        }
        const size_t nNowPos = pObj->GetOrdNumDirect();
        const tools::Rectangle& rBR=pObj->GetCurrentBoundRect();
        size_t nCmpPos = nNowPos+1;
        SdrObject* pMaxObj=GetMaxToTopObj(pObj);
        if (pMaxObj!=nullptr)
        {
            size_t nMaxPos=pMaxObj->GetOrdNum();
            if (nMaxPos!=0)
                nMaxPos--;
            if (nNewPos>nMaxPos)
                nNewPos=nMaxPos; // neither go faster...
            if (nNewPos<nNowPos)
                nNewPos=nNowPos; // nor go in the other direction
        }
        bool bEnd=false;
        while (nCmpPos<nNewPos && !bEnd)
        {
            assert(pOL);
            SdrObject* pCmpObj=pOL->GetObj(nCmpPos);
            if (pCmpObj==nullptr)
            {
                OSL_FAIL("MovMarkedToTop(): Reference object not found.");
                bEnd=true;
            }
            else if (pCmpObj==pMaxObj)
            {
                nNewPos=nCmpPos;
                nNewPos--;
                bEnd=true;
            }
            else if (rBR.Overlaps(pCmpObj->GetCurrentBoundRect()))
            {
                nNewPos=nCmpPos;
                bEnd=true;
            }
            else
            {
                nCmpPos++;
            }
        }
        if (nNowPos!=nNewPos)
        {
            bChg=true;
            pOL->SetObjectOrdNum(nNowPos,nNewPos);
            if( bUndo )
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos));
            ObjOrderChanged(pObj,nNowPos,nNewPos);
        }
        nNewPos--;
    }

    if( bUndo )
        EndUndo();

    if (bChg)
        MarkListHasChanged();
}

void SdrEditView::MovMarkedToBtm()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0)
        return;

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
        BegUndo(SvxResId(STR_EditMovToBtm),rMarkList.GetMarkDescription(),SdrRepeatFunc::MoveToBottom);

    rMarkList.ForceSort();
    for (size_t nm=0; nm<nCount; ++nm)
    { // All Ordnums have to be correct!
        rMarkList.GetMark(nm)->GetMarkedSdrObj()->GetOrdNum();
    }

    bool bChg=false;
    SdrObjList* pOL0=nullptr;
    size_t nNewPos=0;
    for (size_t nm=0; nm<nCount; ++nm)
    {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
        if (pOL!=pOL0)
        {
            nNewPos=0;
            pOL0=pOL;
        }
        const size_t nNowPos = pObj->GetOrdNumDirect();
        const tools::Rectangle& rBR=pObj->GetCurrentBoundRect();
        size_t nCmpPos = nNowPos;
        if (nCmpPos>0)
            --nCmpPos;
        SdrObject* pMaxObj=GetMaxToBtmObj(pObj);
        if (pMaxObj!=nullptr)
        {
            const size_t nMinPos=pMaxObj->GetOrdNum()+1;
            if (nNewPos<nMinPos)
                nNewPos=nMinPos; // neither go faster...
            if (nNewPos>nNowPos)
                nNewPos=nNowPos; // nor go in the other direction
        }
        bool bEnd=false;
        // nNewPos in this case is the "maximum" position
        // the object may reach without going faster than the object before
        // it (multiple selection).
        while (nCmpPos>nNewPos && !bEnd)
        {
            assert(pOL);
            SdrObject* pCmpObj=pOL->GetObj(nCmpPos);
            if (pCmpObj==nullptr)
            {
                OSL_FAIL("MovMarkedToBtm(): Reference object not found.");
                bEnd=true;
            }
            else if (pCmpObj==pMaxObj)
            {
                nNewPos=nCmpPos;
                nNewPos++;
                bEnd=true;
            }
            else if (rBR.Overlaps(pCmpObj->GetCurrentBoundRect()))
            {
                nNewPos=nCmpPos;
                bEnd=true;
            }
            else
            {
                nCmpPos--;
            }
        }
        if (nNowPos!=nNewPos)
        {
            bChg=true;
            pOL->SetObjectOrdNum(nNowPos,nNewPos);
            if( bUndo )
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos));
            ObjOrderChanged(pObj,nNowPos,nNewPos);
        }
        nNewPos++;
    }

    if(bUndo)
        EndUndo();

    if(bChg)
        MarkListHasChanged();
}

void SdrEditView::PutMarkedToTop()
{
    PutMarkedInFrontOfObj(nullptr);
}

void SdrEditView::PutMarkedInFrontOfObj(const SdrObject* pRefObj)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0)
        return;

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
        BegUndo(SvxResId(STR_EditPutToTop),rMarkList.GetMarkDescription(),SdrRepeatFunc::PutToTop);

    rMarkList.ForceSort();

    if (pRefObj!=nullptr)
    {
        // Make "in front of the object" work, even if the
        // selected objects are already in front of the other object
        const size_t nRefMark=rMarkList.FindObject(pRefObj);
        SdrMark aRefMark;
        if (nRefMark!=SAL_MAX_SIZE)
        {
            aRefMark=*rMarkList.GetMark(nRefMark);
            GetMarkedObjectListWriteAccess().DeleteMark(nRefMark);
        }
        PutMarkedToBtm();
        if (nRefMark!=SAL_MAX_SIZE)
        {
            GetMarkedObjectListWriteAccess().InsertEntry(aRefMark);
            rMarkList.ForceSort();
        }
    }
    for (size_t nm=0; nm<nCount; ++nm)
    { // All Ordnums have to be correct!
        rMarkList.GetMark(nm)->GetMarkedSdrObj()->GetOrdNum();
    }
    bool bChg=false;
    SdrObjList* pOL0=nullptr;
    size_t nNewPos=0;
    for (size_t nm=nCount; nm>0;)
    {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        if (pObj!=pRefObj)
        {
            SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
            if (pOL!=pOL0)
            {
                nNewPos=pOL->GetObjCount()-1;
                pOL0=pOL;
            }
            const size_t nNowPos=pObj->GetOrdNumDirect();
            SdrObject* pMaxObj=GetMaxToTopObj(pObj);
            if (pMaxObj!=nullptr)
            {
                size_t nMaxOrd=pMaxObj->GetOrdNum(); // sadly doesn't work any other way
                if (nMaxOrd>0)
                    nMaxOrd--;
                if (nNewPos>nMaxOrd)
                    nNewPos=nMaxOrd; // neither go faster...
                if (nNewPos<nNowPos)
                    nNewPos=nNowPos; // nor go into the other direction
            }
            if (pRefObj!=nullptr)
            {
                if (pRefObj->getParentSdrObjListFromSdrObject()==pObj->getParentSdrObjListFromSdrObject())
                {
                    const size_t nMaxOrd=pRefObj->GetOrdNum(); // sadly doesn't work any other way
                    if (nNewPos>nMaxOrd)
                        nNewPos=nMaxOrd; // neither go faster...
                    if (nNewPos<nNowPos)
                        nNewPos=nNowPos; // nor go into the other direction
                }
                else
                {
                    nNewPos=nNowPos; // different PageView, so don't change
                }
            }
            if (nNowPos!=nNewPos)
            {
                bChg=true;
                pOL->SetObjectOrdNum(nNowPos,nNewPos);
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos));
                ObjOrderChanged(pObj,nNowPos,nNewPos);
            }
            nNewPos--;
        } // if (pObj!=pRefObj)
    } // for loop over all selected objects

    if( bUndo )
        EndUndo();

    if(bChg)
        MarkListHasChanged();
}

void SdrEditView::PutMarkedToBtm()
{
    PutMarkedBehindObj(nullptr);
}

void SdrEditView::PutMarkedBehindObj(const SdrObject* pRefObj)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0)
        return;

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
        BegUndo(SvxResId(STR_EditPutToBtm),rMarkList.GetMarkDescription(),SdrRepeatFunc::PutToBottom);

    rMarkList.ForceSort();
    if (pRefObj!=nullptr)
    {
        // Make "behind the object" work, even if the
        // selected objects are already behind the other object
        const size_t nRefMark=rMarkList.FindObject(pRefObj);
        SdrMark aRefMark;
        if (nRefMark!=SAL_MAX_SIZE)
        {
            aRefMark=*rMarkList.GetMark(nRefMark);
            GetMarkedObjectListWriteAccess().DeleteMark(nRefMark);
        }
        PutMarkedToTop();
        if (nRefMark!=SAL_MAX_SIZE)
        {
            GetMarkedObjectListWriteAccess().InsertEntry(aRefMark);
            rMarkList.ForceSort();
        }
    }
    for (size_t nm=0; nm<nCount; ++nm) { // All Ordnums have to be correct!
        rMarkList.GetMark(nm)->GetMarkedSdrObj()->GetOrdNum();
    }
    bool bChg=false;
    SdrObjList* pOL0=nullptr;
    size_t nNewPos=0;
    for (size_t nm=0; nm<nCount; ++nm) {
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        if (pObj!=pRefObj) {
            SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
            if (pOL!=pOL0) {
                nNewPos=0;
                pOL0=pOL;
            }
            const size_t nNowPos=pObj->GetOrdNumDirect();
            SdrObject* pMinObj=GetMaxToBtmObj(pObj);
            if (pMinObj!=nullptr) {
                const size_t nMinOrd=pMinObj->GetOrdNum()+1; // sadly doesn't work any differently
                if (nNewPos<nMinOrd) nNewPos=nMinOrd; // neither go faster...
                if (nNewPos>nNowPos) nNewPos=nNowPos; // nor go into the other direction
            }
            if (pRefObj!=nullptr) {
                if (pRefObj->getParentSdrObjListFromSdrObject()==pObj->getParentSdrObjListFromSdrObject()) {
                    const size_t nMinOrd=pRefObj->GetOrdNum(); // sadly doesn't work any differently
                    if (nNewPos<nMinOrd) nNewPos=nMinOrd; // neither go faster...
                    if (nNewPos>nNowPos) nNewPos=nNowPos; // nor go into the other direction
                } else {
                    nNewPos=nNowPos; // different PageView, so don't change
                }
            }
            if (nNowPos!=nNewPos) {
                bChg=true;
                pOL->SetObjectOrdNum(nNowPos,nNewPos);
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj,nNowPos,nNewPos));
                ObjOrderChanged(pObj,nNowPos,nNewPos);
            }
            nNewPos++;
        } // if (pObj!=pRefObj)
    } // for loop over all selected objects

    if(bUndo)
        EndUndo();

    if(bChg)
        MarkListHasChanged();

}

void SdrEditView::ReverseOrderOfMarked()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    const size_t nMarkCount=rMarkList.GetMarkCount();
    if (nMarkCount<=0)
        return;

    bool bChg=false;

    bool bUndo = IsUndoEnabled();
    if( bUndo )
        BegUndo(SvxResId(STR_EditRevOrder),rMarkList.GetMarkDescription(),SdrRepeatFunc::ReverseOrder);

    size_t a=0;
    do {
        // take into account selection across multiple PageViews
        size_t b=a+1;
        while (b<nMarkCount && rMarkList.GetMark(b)->GetPageView() == rMarkList.GetMark(a)->GetPageView()) ++b;
        --b;
        SdrObjList* pOL=rMarkList.GetMark(a)->GetPageView()->GetObjList();
        size_t c=b;
        if (a<c) { // make sure OrdNums aren't dirty
            rMarkList.GetMark(a)->GetMarkedSdrObj()->GetOrdNum();
        }
        while (a<c) {
            SdrObject* pObj1=rMarkList.GetMark(a)->GetMarkedSdrObj();
            SdrObject* pObj2=rMarkList.GetMark(c)->GetMarkedSdrObj();
            const size_t nOrd1=pObj1->GetOrdNumDirect();
            const size_t nOrd2=pObj2->GetOrdNumDirect();
            if( bUndo )
            {
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj1,nOrd1,nOrd2));
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj2,nOrd2-1,nOrd1));
            }
            pOL->SetObjectOrdNum(nOrd1,nOrd2);
            // Obj 2 has moved forward by one position, so now nOrd2-1
            pOL->SetObjectOrdNum(nOrd2-1,nOrd1);
            // use Replace instead of SetOrdNum for performance reasons (recalculation of Ordnums)
            ++a;
            --c;
            bChg=true;
        }
        a=b+1;
    } while (a<nMarkCount);

    if(bUndo)
        EndUndo();

    if(bChg)
        MarkListHasChanged();
}

void SdrEditView::ImpCheckToTopBtmPossible()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nCount=rMarkList.GetMarkCount();
    if (nCount==0)
        return;
    if (nCount==1)
    { // special-casing for single selection
        SdrObject* pObj=rMarkList.GetMark(0)->GetMarkedSdrObj();
        SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
        SAL_WARN_IF(!pOL, "svx", "Object somehow has no ObjList");
        size_t nMax = pOL ? pOL->GetObjCount() : 0;
        size_t nMin = 0;
        const size_t nObjNum=pObj->GetOrdNum();
        SdrObject* pRestrict=GetMaxToTopObj(pObj);
        if (pRestrict!=nullptr) {
            const size_t nRestrict=pRestrict->GetOrdNum();
            if (nRestrict<nMax) nMax=nRestrict;
        }
        pRestrict=GetMaxToBtmObj(pObj);
        if (pRestrict!=nullptr) {
            const size_t nRestrict=pRestrict->GetOrdNum();
            if (nRestrict>nMin) nMin=nRestrict;
        }
        m_bToTopPossible = nObjNum+1 < nMax;
        m_bToBtmPossible = nObjNum > nMin;
    } else { // multiple selection
        SdrObjList* pOL0=nullptr;
        size_t nPos0 = 0;
        for (size_t nm = 0; !m_bToBtmPossible && nm<nCount; ++nm) { // check 'send to background'
            SdrObject* pObj=rMarkList.GetMark(nm)->GetMarkedSdrObj();
            SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
            if (pOL!=pOL0) {
                nPos0 = 0;
                pOL0=pOL;
            }
            const size_t nPos = pObj->GetOrdNum();
            m_bToBtmPossible = nPos && (nPos-1 > nPos0);
            nPos0 = nPos;
        }

        pOL0=nullptr;
        nPos0 = SAL_MAX_SIZE;
        for (size_t nm=nCount; !m_bToTopPossible && nm>0; ) { // check 'bring to front'
            --nm;
            SdrObject* pObj=rMarkList.GetMark(nm)->GetMarkedSdrObj();
            SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
            if (pOL!=pOL0) {
                nPos0=pOL->GetObjCount();
                pOL0=pOL;
            }
            const size_t nPos = pObj->GetOrdNum();
            m_bToTopPossible = nPos+1 < nPos0;
            nPos0=nPos;
        }
    }
}


// Combine


void SdrEditView::ImpCopyAttributes(const SdrObject* pSource, SdrObject* pDest) const
{
    if (pSource!=nullptr) {
        SdrObjList* pOL=pSource->GetSubList();
        if (pOL!=nullptr && !pSource->Is3DObj()) { // get first non-group object from group
            SdrObjListIter aIter(pOL,SdrIterMode::DeepNoGroups);
            pSource=aIter.Next();
        }
    }

    if(!(pSource && pDest))
        return;

    SfxItemSetFixed<SDRATTR_START, SDRATTR_NOTPERSIST_FIRST-1,
        SDRATTR_NOTPERSIST_LAST+1, SDRATTR_END,
        EE_ITEMS_START, EE_ITEMS_END> aSet(GetModel().GetItemPool());

    aSet.Put(pSource->GetMergedItemSet());

    pDest->ClearMergedItem();
    pDest->SetMergedItemSet(aSet);

    pDest->NbcSetLayer(pSource->GetLayer());
    pDest->NbcSetStyleSheet(pSource->GetStyleSheet(), true);
}

bool SdrEditView::ImpCanConvertForCombine1(const SdrObject* pObj)
{
    // new condition IsLine() to be able to combine simple Lines
    bool bIsLine(false);

    const SdrPathObj* pPath = dynamic_cast< const SdrPathObj*>( pObj );

    if(pPath)
    {
        bIsLine = pPath->IsLine();
    }

    SdrObjTransformInfoRec aInfo;
    pObj->TakeObjInfo(aInfo);

    return (aInfo.bCanConvToPath || aInfo.bCanConvToPoly || bIsLine);
}

bool SdrEditView::ImpCanConvertForCombine(const SdrObject* pObj)
{
    SdrObjList* pOL = pObj->GetSubList();

    if(pOL && !pObj->Is3DObj())
    {
        SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups);

        while(aIter.IsMore())
        {
            SdrObject* pObj1 = aIter.Next();

            // all members of a group have to be convertible
            if(!ImpCanConvertForCombine1(pObj1))
            {
                return false;
            }
        }
    }
    else
    {
        if(!ImpCanConvertForCombine1(pObj))
        {
            return false;
        }
    }

    return true;
}

basegfx::B2DPolyPolygon SdrEditView::ImpGetPolyPolygon1(const SdrObject* pObj)
{
    basegfx::B2DPolyPolygon aRetval;
    const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>( pObj );

    if(pPath && !pObj->GetOutlinerParaObject())
    {
        aRetval = pPath->GetPathPoly();
    }
    else
    {
        rtl::Reference<SdrObject> pConvObj = pObj->ConvertToPolyObj(true/*bCombine*/, false);

        if(pConvObj)
        {
            SdrObjList* pOL = pConvObj->GetSubList();

            if(pOL)
            {
                SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups);

                while(aIter.IsMore())
                {
                    SdrObject* pObj1 = aIter.Next();
                    pPath = dynamic_cast<SdrPathObj*>( pObj1 );

                    if(pPath)
                    {
                        aRetval.append(pPath->GetPathPoly());
                    }
                }
            }
            else
            {
                pPath = dynamic_cast<SdrPathObj*>( pConvObj.get() );

                if(pPath)
                {
                    aRetval = pPath->GetPathPoly();
                }
            }
        }
    }

    return aRetval;
}

basegfx::B2DPolyPolygon SdrEditView::ImpGetPolyPolygon(const SdrObject* pObj)
{
    SdrObjList* pOL = pObj->GetSubList();

    if(pOL && !pObj->Is3DObj())
    {
        basegfx::B2DPolyPolygon aRetval;
        SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups);

        while(aIter.IsMore())
        {
            SdrObject* pObj1 = aIter.Next();
            aRetval.append(ImpGetPolyPolygon1(pObj1));
        }

        return aRetval;
    }
    else
    {
        return ImpGetPolyPolygon1(pObj);
    }
}

basegfx::B2DPolygon SdrEditView::ImpCombineToSinglePolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    const sal_uInt32 nPolyCount(rPolyPolygon.count());

    if(0 == nPolyCount)
    {
        return basegfx::B2DPolygon();
    }
    else if(1 == nPolyCount)
    {
        return rPolyPolygon.getB2DPolygon(0);
    }
    else
    {
        basegfx::B2DPolygon aRetval(rPolyPolygon.getB2DPolygon(0));

        for(sal_uInt32 a(1); a < nPolyCount; a++)
        {
            basegfx::B2DPolygon aCandidate(rPolyPolygon.getB2DPolygon(a));

            if(aRetval.count())
            {
                if(aCandidate.count())
                {
                    const basegfx::B2DPoint aCA(aCandidate.getB2DPoint(0));
                    const basegfx::B2DPoint aCB(aCandidate.getB2DPoint(aCandidate.count() - 1));
                    const basegfx::B2DPoint aRA(aRetval.getB2DPoint(0));
                    const basegfx::B2DPoint aRB(aRetval.getB2DPoint(aRetval.count() - 1));

                    const double fRACA(basegfx::B2DVector(aCA - aRA).getLength());
                    const double fRACB(basegfx::B2DVector(aCB - aRA).getLength());
                    const double fRBCA(basegfx::B2DVector(aCA - aRB).getLength());
                    const double fRBCB(basegfx::B2DVector(aCB - aRB).getLength());

                    const double fSmallestRA(std::min(fRACA, fRACB));
                    const double fSmallestRB(std::min(fRBCA, fRBCB));

                    if(fSmallestRA < fSmallestRB)
                    {
                        // flip result
                        aRetval.flip();
                    }

                    const double fSmallestCA(std::min(fRACA, fRBCA));
                    const double fSmallestCB(std::min(fRACB, fRBCB));

                    if(fSmallestCB < fSmallestCA)
                    {
                        // flip candidate
                        aCandidate.flip();
                    }

                    // append candidate to retval
                    aRetval.append(aCandidate);
                }
            }
            else
            {
                aRetval = std::move(aCandidate);
            }
        }

        return aRetval;
    }
}

namespace {

// for distribution dialog function
struct ImpDistributeEntry
{
    SdrObject*                  mpObj;
    sal_Int32                       mnPos;
    sal_Int32                       mnLength;
};

}

typedef std::vector<ImpDistributeEntry> ImpDistributeEntryList;

void SdrEditView::DistributeMarkedObjects(sal_uInt16 SlotID)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    const size_t nMark(rMarkList.GetMarkCount());

    if(nMark <= 2)
        return;

    SvxDistributeHorizontal eHor = SvxDistributeHorizontal::NONE;
    SvxDistributeVertical eVer = SvxDistributeVertical::NONE;

    switch (SlotID)
    {
        case SID_DISTRIBUTE_HLEFT: eHor = SvxDistributeHorizontal::Left; break;
        case SID_DISTRIBUTE_HCENTER: eHor = SvxDistributeHorizontal::Center; break;
        case SID_DISTRIBUTE_HDISTANCE: eHor = SvxDistributeHorizontal::Distance; break;
        case SID_DISTRIBUTE_HRIGHT: eHor = SvxDistributeHorizontal::Right; break;
        case SID_DISTRIBUTE_VTOP: eVer = SvxDistributeVertical::Top; break;
        case SID_DISTRIBUTE_VCENTER: eVer = SvxDistributeVertical::Center; break;
        case SID_DISTRIBUTE_VDISTANCE: eVer = SvxDistributeVertical::Distance; break;
        case SID_DISTRIBUTE_VBOTTOM: eVer = SvxDistributeVertical::Bottom; break;
    }

    ImpDistributeEntryList aEntryList;
    ImpDistributeEntryList::iterator itEntryList;
    sal_uInt32 nFullLength;

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
        BegUndo();

    if(eHor != SvxDistributeHorizontal::NONE)
    {
        // build sorted entry list
        nFullLength = 0;

        for( size_t a = 0; a < nMark; ++a )
        {
            SdrMark* pMark = rMarkList.GetMark(a);
            ImpDistributeEntry aNew;

            aNew.mpObj = pMark->GetMarkedSdrObj();

            switch(eHor)
            {
                case SvxDistributeHorizontal::Left:
                {
                    aNew.mnPos = aNew.mpObj->GetSnapRect().Left();
                    break;
                }
                case SvxDistributeHorizontal::Center:
                {
                    aNew.mnPos = (aNew.mpObj->GetSnapRect().Right() + aNew.mpObj->GetSnapRect().Left()) / 2;
                    break;
                }
                case SvxDistributeHorizontal::Distance:
                {
                    aNew.mnLength = aNew.mpObj->GetSnapRect().GetWidth() + 1;
                    nFullLength += aNew.mnLength;
                    aNew.mnPos = (aNew.mpObj->GetSnapRect().Right() + aNew.mpObj->GetSnapRect().Left()) / 2;
                    break;
                }
                case SvxDistributeHorizontal::Right:
                {
                    aNew.mnPos = aNew.mpObj->GetSnapRect().Right();
                    break;
                }
                default: break;
            }

            itEntryList = std::find_if(aEntryList.begin(), aEntryList.end(),
                [&aNew](const ImpDistributeEntry& rEntry) { return rEntry.mnPos >= aNew.mnPos; });
            if ( itEntryList < aEntryList.end() )
                aEntryList.insert( itEntryList, aNew );
            else
                aEntryList.push_back( aNew );
        }

        if(eHor == SvxDistributeHorizontal::Distance)
        {
            // calculate room in-between
            sal_Int32 nWidth = GetAllMarkedBoundRect().GetWidth() + 1;
            double fStepWidth = (static_cast<double>(nWidth) - static_cast<double>(nFullLength)) / static_cast<double>(aEntryList.size() - 1);
            double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos);
            fStepStart += fStepWidth + static_cast<double>((aEntryList[ 0 ].mnLength + aEntryList[ 1 ].mnLength) / 2);

            // move entries 1..n-1
            for( size_t i = 1, n = aEntryList.size()-1; i < n; ++i )
            {
                ImpDistributeEntry& rCurr = aEntryList[ i    ];
                ImpDistributeEntry& rNext = aEntryList[ i + 1];
                sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos;
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj));
                rCurr.mpObj->Move(Size(nDelta, 0));
                fStepStart += fStepWidth + static_cast<double>((rCurr.mnLength + rNext.mnLength) / 2);
            }
        }
        else
        {
            // calculate distances
            sal_Int32 nWidth = aEntryList[ aEntryList.size() - 1 ].mnPos - aEntryList[ 0 ].mnPos;
            double fStepWidth = static_cast<double>(nWidth) / static_cast<double>(aEntryList.size() - 1);
            double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos);
            fStepStart += fStepWidth;

            // move entries 1..n-1
            for( size_t i = 1 ; i < aEntryList.size()-1 ; ++i )
            {
                ImpDistributeEntry& rCurr = aEntryList[ i ];
                sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos;
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj));
                rCurr.mpObj->Move(Size(nDelta, 0));
                fStepStart += fStepWidth;
            }
        }

        // clear list
        aEntryList.clear();
    }

    if(eVer != SvxDistributeVertical::NONE)
    {
        // build sorted entry list
        nFullLength = 0;

        for( size_t a = 0; a < nMark; ++a )
        {
            SdrMark* pMark = rMarkList.GetMark(a);
            ImpDistributeEntry aNew;

            aNew.mpObj = pMark->GetMarkedSdrObj();

            switch(eVer)
            {
                case SvxDistributeVertical::Top:
                {
                    aNew.mnPos = aNew.mpObj->GetSnapRect().Top();
                    break;
                }
                case SvxDistributeVertical::Center:
                {
                    aNew.mnPos = (aNew.mpObj->GetSnapRect().Bottom() + aNew.mpObj->GetSnapRect().Top()) / 2;
                    break;
                }
                case SvxDistributeVertical::Distance:
                {
                    aNew.mnLength = aNew.mpObj->GetSnapRect().GetHeight() + 1;
                    nFullLength += aNew.mnLength;
                    aNew.mnPos = (aNew.mpObj->GetSnapRect().Bottom() + aNew.mpObj->GetSnapRect().Top()) / 2;
                    break;
                }
                case SvxDistributeVertical::Bottom:
                {
                    aNew.mnPos = aNew.mpObj->GetSnapRect().Bottom();
                    break;
                }
                default: break;
            }

            itEntryList = std::find_if(aEntryList.begin(), aEntryList.end(),
                [&aNew](const ImpDistributeEntry& rEntry) { return rEntry.mnPos >= aNew.mnPos; });
            if ( itEntryList < aEntryList.end() )
                aEntryList.insert( itEntryList, aNew );
            else
                aEntryList.push_back( aNew );
        }

        if(eVer == SvxDistributeVertical::Distance)
        {
            // calculate room in-between
            sal_Int32 nHeight = GetAllMarkedBoundRect().GetHeight() + 1;
            double fStepWidth = (static_cast<double>(nHeight) - static_cast<double>(nFullLength)) / static_cast<double>(aEntryList.size() - 1);
            double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos);
            fStepStart += fStepWidth + static_cast<double>((aEntryList[ 0 ].mnLength + aEntryList[ 1 ].mnLength) / 2);

            // move entries 1..n-1
            for( size_t i = 1, n = aEntryList.size()-1; i < n; ++i)
            {
                ImpDistributeEntry& rCurr = aEntryList[ i     ];
                ImpDistributeEntry& rNext = aEntryList[ i + 1 ];
                sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos;
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj));
                rCurr.mpObj->Move(Size(0, nDelta));
                fStepStart += fStepWidth + static_cast<double>((rCurr.mnLength + rNext.mnLength) / 2);
            }
        }
        else
        {
            // calculate distances
            sal_Int32 nHeight = aEntryList[ aEntryList.size() - 1 ].mnPos - aEntryList[ 0 ].mnPos;
            double fStepWidth = static_cast<double>(nHeight) / static_cast<double>(aEntryList.size() - 1);
            double fStepStart = static_cast<double>(aEntryList[ 0 ].mnPos);
            fStepStart += fStepWidth;

            // move entries 1..n-1
            for(size_t i = 1, n = aEntryList.size()-1; i < n; ++i)
            {
                ImpDistributeEntry& rCurr = aEntryList[ i ];
                sal_Int32 nDelta = static_cast<sal_Int32>(fStepStart + 0.5) - rCurr.mnPos;
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*rCurr.mpObj));
                rCurr.mpObj->Move(Size(0, nDelta));
                fStepStart += fStepWidth;
            }
        }

        // clear list
        aEntryList.clear();
    }

    // UNDO-Comment and end of UNDO
    GetModel().SetUndoComment(SvxResId(STR_DistributeMarkedObjects));

    if( bUndo )
        EndUndo();
}

void SdrEditView::MergeMarkedObjects(SdrMergeMode eMode)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    // #i73441# check content
    if(rMarkList.GetMarkCount() == 0)
        return;

    SdrMarkList aRemove;
    rMarkList.ForceSort();

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
        BegUndo();

    size_t nInsPos = SAL_MAX_SIZE;
    const SdrObject* pAttrObj = nullptr;
    basegfx::B2DPolyPolygon aMergePolyPolygonA;
    basegfx::B2DPolyPolygon aMergePolyPolygonB;

    SdrObjList* pInsOL = nullptr;
    SdrPageView* pInsPV = nullptr;
    bool bFirstObjectComplete(false);

    // make sure selected objects are contour objects
    // since now basegfx::utils::adaptiveSubdivide() is used, it is no longer
    // necessary to use ConvertMarkedToPolyObj which will subdivide curves using the old
    // mechanisms. In a next step the polygon clipper will even be able to clip curves...
    // ConvertMarkedToPolyObj(true);
    ConvertMarkedToPathObj(true);
    OSL_ENSURE(rMarkList.GetMarkCount() != 0, "no more objects selected after preparations (!)");

    for(size_t a=0; a<rMarkList.GetMarkCount(); ++a)
    {
        SdrMark* pM = rMarkList.GetMark(a);
        SdrObject* pObj = pM->GetMarkedSdrObj();

        if(ImpCanConvertForCombine(pObj))
        {
            if(!pAttrObj)
                pAttrObj = pObj;

            nInsPos = pObj->GetOrdNum() + 1;
            pInsPV = pM->GetPageView();
            pInsOL = pObj->getParentSdrObjListFromSdrObject();

            // #i76891# use single iteration from SJ here which works on SdrObjects and takes
            // groups into account by itself
            SdrObjListIter aIter(*pObj, SdrIterMode::DeepWithGroups);

            while(aIter.IsMore())
            {
                SdrObject* pCandidate = aIter.Next();
                SdrPathObj* pPathObj = dynamic_cast<SdrPathObj*>( pCandidate );
                if(pPathObj)
                {
                    basegfx::B2DPolyPolygon aTmpPoly(pPathObj->GetPathPoly());

                    // #i76891# unfortunately ConvertMarkedToPathObj has converted all
                    // involved polygon data to curve segments, even if not necessary.
                    // It is better to try to reduce to more simple polygons.
                    aTmpPoly = basegfx::utils::simplifyCurveSegments(aTmpPoly);

                    // for each part polygon as preparation, remove self-intersections
                    // correct orientations and get rid of possible neutral polygons.
                    aTmpPoly = basegfx::utils::prepareForPolygonOperation(aTmpPoly);

                    if(!bFirstObjectComplete)
                    {
                        // #i111987# Also need to collect ORed source shape when more than
                        // a single polygon is involved
                        if(aMergePolyPolygonA.count())
                        {
                            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aTmpPoly);
                        }
                        else
                        {
                            aMergePolyPolygonA = std::move(aTmpPoly);
                        }
                    }
                    else
                    {
                        if(aMergePolyPolygonB.count())
                        {
                            // to topologically correctly collect the 2nd polygon
                            // group it is necessary to OR the parts (each is seen as
                            // XOR-FillRule polygon and they are drawn over each-other)
                            aMergePolyPolygonB = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonB, aTmpPoly);
                        }
                        else
                        {
                            aMergePolyPolygonB = std::move(aTmpPoly);
                        }
                    }
                }
            }

            // was there something added to the first polygon?
            if(!bFirstObjectComplete && aMergePolyPolygonA.count())
            {
                bFirstObjectComplete = true;
            }

            // move object to temporary delete list
            aRemove.InsertEntry(SdrMark(pObj, pM->GetPageView()));
        }
    }

    switch(eMode)
    {
        case SdrMergeMode::Merge:
        {
            // merge all contained parts (OR)
            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB);
            break;
        }
        case SdrMergeMode::Subtract:
        {
            // Subtract B from A
            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationDiff(aMergePolyPolygonA, aMergePolyPolygonB);
            break;
        }
        case SdrMergeMode::Intersect:
        {
            // AND B and A
            aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB);
            break;
        }
    }

    // #i73441# check insert list before taking actions
    if(pInsOL)
    {
        rtl::Reference<SdrPathObj> pPath = new SdrPathObj(pAttrObj->getSdrModelFromSdrObject(), SdrObjKind::PathFill, std::move(aMergePolyPolygonA));
        ImpCopyAttributes(pAttrObj, pPath.get());
        pInsOL->InsertObject(pPath.get(), nInsPos);
        if( bUndo )
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath));

        // #i124760# To have a correct selection with only the new object it is necessary to
        // unmark all objects first. If not doing so, there may remain invalid pointers to objects
        // TTTT:Not needed for aw080 (!)
        UnmarkAllObj(pInsPV);

        MarkObj(pPath.get(), pInsPV, false, true);
    }

    aRemove.ForceSort();
    switch(eMode)
    {
        case SdrMergeMode::Merge:
        {
            SetUndoComment(
                SvxResId(STR_EditMergeMergePoly),
                aRemove.GetMarkDescription());
            break;
        }
        case SdrMergeMode::Subtract:
        {
            SetUndoComment(
                SvxResId(STR_EditMergeSubstractPoly),
                aRemove.GetMarkDescription());
            break;
        }
        case SdrMergeMode::Intersect:
        {
            SetUndoComment(
                SvxResId(STR_EditMergeIntersectPoly),
                aRemove.GetMarkDescription());
            break;
        }
    }
    DeleteMarkedList(aRemove);

    if( bUndo )
        EndUndo();
}

void SdrEditView::EqualizeMarkedObjects(bool bWidth)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    size_t nMarked = rMarkList.GetMarkCount();

    if (nMarked < 2)
        return;

    size_t nLastSelected = 0;
    sal_Int64 nLastSelectedTime = rMarkList.GetMark(0)->getTimeStamp();
    for (size_t a = 1; a < nMarked; ++a)
    {
        sal_Int64 nCandidateTime = rMarkList.GetMark(a)->getTimeStamp();
        if (nCandidateTime > nLastSelectedTime)
        {
            nLastSelectedTime = nCandidateTime;
            nLastSelected = a;
        }
    }

    SdrObject* pLastSelectedObj = rMarkList.GetMark(nLastSelected)->GetMarkedSdrObj();
    Size aLastRectSize(pLastSelectedObj->GetLogicRect().GetSize());

    const bool bUndo = IsUndoEnabled();

    if (bUndo)
        BegUndo();

    for (size_t a = 0; a < nMarked; ++a)
    {
        if (a == nLastSelected)
            continue;
        SdrMark* pM = rMarkList.GetMark(a);
        SdrObject* pObj = pM->GetMarkedSdrObj();
        tools::Rectangle aLogicRect(pObj->GetLogicRect());
        Size aLogicRectSize(aLogicRect.GetSize());
        if (bWidth)
            aLogicRectSize.setWidth( aLastRectSize.Width() );
        else
            aLogicRectSize.setHeight( aLastRectSize.Height() );
        aLogicRect.SetSize(aLogicRectSize);
        if (bUndo)
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj));
        pObj->SetLogicRect(aLogicRect);
    }

    SetUndoComment(
        SvxResId(bWidth ? STR_EqualizeWidthMarkedObjects : STR_EqualizeHeightMarkedObjects),
        rMarkList.GetMarkDescription());

    if (bUndo)
        EndUndo();
}

void SdrEditView::CombineMarkedTextObjects()
{
    SdrPageView* pPageView = GetSdrPageView();
    if ( !pPageView || pPageView->IsLayerLocked( GetActiveLayer() ) )
        return;

    bool bUndo = IsUndoEnabled();

    // Undo-String will be set later
    if ( bUndo )
        BegUndo();

    SdrOutliner& rDrawOutliner = getSdrModelFromSdrView().GetDrawOutliner();

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    SdrObjListIter aIter( rMarkList, SdrIterMode::Flat);
    while ( aIter.IsMore() )
    {
        SdrObject* pObj = aIter.Next();
        SdrTextObj* pTextObj = DynCastSdrTextObj( pObj );
        const OutlinerParaObject* pOPO = pTextObj ? pTextObj->GetOutlinerParaObject() : nullptr;
        if ( pOPO && pTextObj->IsTextFrame()
             &&  pTextObj->GetObjIdentifier() == SdrObjKind::Text   // not callouts (OBJ_CAPTION)
             && !pTextObj->IsOutlText()                     // not impress presentation objects
             &&  pTextObj->GetMergedItem(XATTR_FORMTXTSTYLE).GetValue() == XFormTextStyle::NONE // not Fontwork
           )
        {
            // if the last paragraph does not end in paragraph-end punctuation (ignoring whitespace),
            // assume this text should be added to the end of the last paragraph, instead of starting a new paragraph.
            const sal_Int32 nPara = rDrawOutliner.GetParagraphCount();
            const OUString sLastPara = nPara ? rDrawOutliner.GetText( rDrawOutliner.GetParagraph( nPara - 1 ) ) : u""_ustr;
            sal_Int32 n = sLastPara.getLength();
            while ( n && unicode::isWhiteSpace( sLastPara[--n] ) )
                ;
            //TODO: find way to use Locale to identify sentence final punctuation. Copied IsSentenceAtEnd() from autofmt.cxx
            const bool bAppend = !n || ( sLastPara[n] != '.' && sLastPara[n] != '?' && sLastPara[n] != '!' );
            rDrawOutliner.AddText( *pOPO, bAppend );
        }
        else
        {
            // Unmark non-textboxes, because all marked objects are deleted at the end. AdjustMarkHdl later.
            MarkObj(pObj, pPageView, /*bUnmark=*/true, /*bImpNoSetMarkHdl=*/true);
        }
    }

    MarkListHasChanged();
    AdjustMarkHdl();

    if ( rMarkList.GetMarkCount() > 1 )
    {
        rtl::Reference<SdrRectObj> pReplacement = new SdrRectObj( getSdrModelFromSdrView(), tools::Rectangle(), SdrObjKind::Text );
        pReplacement->SetOutlinerParaObject( rDrawOutliner.CreateParaObject() );
        pReplacement->SetSnapRect( GetMarkedObjRect() );

        const SdrInsertFlags nFlags = SdrInsertFlags::DONTMARK | SdrInsertFlags::SETDEFLAYER;
        if ( InsertObjectAtView( pReplacement.get(), *pPageView, nFlags ) )
            DeleteMarkedObj();
    }

    if ( bUndo )
        EndUndo();

    return;
}

void SdrEditView::CombineMarkedObjects(bool bNoPolyPoly)
{
    // #105899# Start of Combine-Undo put to front, else ConvertMarkedToPolyObj would
    // create a 2nd Undo-action and Undo-Comment.

    bool bUndo = IsUndoEnabled();

    // Undo-String will be set later
    if( bUndo )
        BegUndo(u""_ustr, u""_ustr, bNoPolyPoly ? SdrRepeatFunc::CombineOnePoly : SdrRepeatFunc::CombinePolyPoly);

    // #105899# First, guarantee that all objects are converted to polyobjects,
    // especially for SdrGrafObj with bitmap filling this is necessary to not
    // lose the bitmap filling.

    // #i12392#
    // ConvertMarkedToPolyObj was too strong here, it will lose quality and
    // information when curve objects are combined. This can be replaced by
    // using ConvertMarkedToPathObj without changing the previous fix.

    // #i21250#
    // Instead of simply passing true as LineToArea, use bNoPolyPoly as info
    // if this command is a 'Combine' or a 'Connect' command. On Connect it's true.
    // To not concert line segments with a set line width to polygons in that case,
    // use this info. Do not convert LineToArea on Connect commands.
    // ConvertMarkedToPathObj(!bNoPolyPoly);

    // This is used for Combine and Connect. In no case it is necessary to force
    // the content to curve, but it is also not good to force to polygons. Thus,
    // curve is the less information losing one. Remember: This place is not
    // used for merge.
    // LineToArea is never necessary, both commands are able to take over the
    // set line style and to display it correctly. Thus, i will use a
    // ConvertMarkedToPathObj with a false in any case. Only drawback is that
    // simple polygons will be changed to curves, but with no information loss.
    ConvertMarkedToPathObj(false /* bLineToArea */);

    // continue as before
    basegfx::B2DPolyPolygon aPolyPolygon;
    SdrObjList* pCurrentOL = nullptr;
    SdrMarkList aRemoveBuffer;

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    size_t nInsPos = SAL_MAX_SIZE;
    SdrObjList* pInsOL = nullptr;
    SdrPageView* pInsPV = nullptr;
    const SdrObject* pAttrObj = nullptr;

    for(size_t a = rMarkList.GetMarkCount(); a; )
    {
        --a;
        SdrMark* pM = rMarkList.GetMark(a);
        SdrObject* pObj = pM->GetMarkedSdrObj();
        SdrObjList* pThisOL = pObj->getParentSdrObjListFromSdrObject();

        if(pCurrentOL != pThisOL)
        {
            pCurrentOL = pThisOL;
        }

        if(ImpCanConvertForCombine(pObj))
        {
            // remember objects to be able to copy attributes
            pAttrObj = pObj;

            // unfortunately ConvertMarkedToPathObj has converted all
            // involved polygon data to curve segments, even if not necessary.
            // It is better to try to reduce to more simple polygons.
            basegfx::B2DPolyPolygon aTmpPoly(basegfx::utils::simplifyCurveSegments(ImpGetPolyPolygon(pObj)));
            aPolyPolygon.insert(0, aTmpPoly);

            if(!pInsOL)
            {
                nInsPos = pObj->GetOrdNum() + 1;
                pInsPV = pM->GetPageView();
                pInsOL = pObj->getParentSdrObjListFromSdrObject();
            }

            aRemoveBuffer.InsertEntry(SdrMark(pObj, pM->GetPageView()));
        }
    }

    if(bNoPolyPoly)
    {
        basegfx::B2DPolygon aCombinedPolygon(ImpCombineToSinglePolygon(aPolyPolygon));
        aPolyPolygon.clear();
        aPolyPolygon.append(aCombinedPolygon);
    }

    const sal_uInt32 nPolyCount(aPolyPolygon.count());

    if (nPolyCount && pAttrObj)
    {
        SdrObjKind eKind = SdrObjKind::PathFill;

        if(nPolyCount > 1)
        {
            aPolyPolygon.setClosed(true);
        }
        else
        {
            // check for Polyline
            const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
            const sal_uInt32 nPointCount(aPolygon.count());

            if(nPointCount <= 2)
            {
                eKind = SdrObjKind::PathLine;
            }
            else
            {
                if(!aPolygon.isClosed())
                {
                    const basegfx::B2DPoint aPointA(aPolygon.getB2DPoint(0));
                    const basegfx::B2DPoint aPointB(aPolygon.getB2DPoint(nPointCount - 1));
                    const double fDistance(basegfx::B2DVector(aPointB - aPointA).getLength());
                    const double fJoinTolerance(10.0);

                    if(fDistance < fJoinTolerance)
                    {
                        aPolyPolygon.setClosed(true);
                    }
                    else
                    {
                        eKind = SdrObjKind::PathLine;
                    }
                }
            }
        }

        rtl::Reference<SdrPathObj> pPath = new SdrPathObj(pAttrObj->getSdrModelFromSdrObject(), eKind, std::move(aPolyPolygon));

        // attributes of the lowest object
        ImpCopyAttributes(pAttrObj, pPath.get());

        // If LineStyle of pAttrObj is drawing::LineStyle_NONE force to drawing::LineStyle_SOLID to make visible.
        const drawing::LineStyle eLineStyle = pAttrObj->GetMergedItem(XATTR_LINESTYLE).GetValue();
        const drawing::FillStyle eFillStyle = pAttrObj->GetMergedItem(XATTR_FILLSTYLE).GetValue();

        // Take fill style/closed state of pAttrObj in account when deciding to change the line style
        bool bIsClosedPathObj = false;
        if (auto pPathObj = dynamic_cast<const SdrPathObj*>(pAttrObj))
            if (pPathObj->IsClosed())
                bIsClosedPathObj = true;

        if(drawing::LineStyle_NONE == eLineStyle && (drawing::FillStyle_NONE == eFillStyle || !bIsClosedPathObj))
        {
            pPath->SetMergedItem(XLineStyleItem(drawing::LineStyle_SOLID));
        }

        pInsOL->InsertObject(pPath.get(),nInsPos);
        if( bUndo )
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath));

        // Here was a severe error: Without UnmarkAllObj, the new object was marked
        // additionally to the two ones which are deleted below. As long as those are
        // in the UNDO there is no problem, but as soon as they get deleted, the
        // MarkList will contain deleted objects -> GPF.
        UnmarkAllObj(pInsPV);
        MarkObj(pPath.get(), pInsPV, false, true);
    }

    // build an UndoComment from the objects actually used
    aRemoveBuffer.ForceSort(); // important for remove (see below)
    if( bUndo )
        SetUndoComment(SvxResId(bNoPolyPoly?STR_EditCombine_OnePoly:STR_EditCombine_PolyPoly),aRemoveBuffer.GetMarkDescription());

    // remove objects actually used from the list
    DeleteMarkedList(aRemoveBuffer);
    if( bUndo )
        EndUndo();
}


// Dismantle


bool SdrEditView::ImpCanDismantle(const basegfx::B2DPolyPolygon& rPpolyPolygon, bool bMakeLines)
{
    bool bCan(false);
    const sal_uInt32 nPolygonCount(rPpolyPolygon.count());

    if(nPolygonCount >= 2)
    {
        // #i69172# dismantle makes sense with 2 or more polygons in a polyPolygon
        bCan = true;
    }
    else if(bMakeLines && 1 == nPolygonCount)
    {
        // #i69172# ..or with at least 2 edges (curves or lines)
        const basegfx::B2DPolygon& aPolygon(rPpolyPolygon.getB2DPolygon(0));
        const sal_uInt32 nPointCount(aPolygon.count());

        if(nPointCount > 2)
        {
            bCan = true;
        }
    }

    return bCan;
}

bool SdrEditView::ImpCanDismantle(const SdrObject* pObj, bool bMakeLines)
{
    bool bOtherObjs(false);    // true=objects other than PathObj's existent
    bool bMin1PolyPoly(false); // true=at least 1 tools::PolyPolygon with more than one Polygon existent
    SdrObjList* pOL = pObj->GetSubList();

    if(pOL)
    {
        // group object -- check all members if they're PathObjs
        SdrObjListIter aIter(pOL, SdrIterMode::DeepNoGroups);

        while(aIter.IsMore() && !bOtherObjs)
        {
            const SdrObject* pObj1 = aIter.Next();
            const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>( pObj1 );

            if(pPath)
            {
                if(ImpCanDismantle(pPath->GetPathPoly(), bMakeLines))
                {
                    bMin1PolyPoly = true;
                }

                SdrObjTransformInfoRec aInfo;
                pObj1->TakeObjInfo(aInfo);

                if(!aInfo.bCanConvToPath)
                {
                    // happens e. g. in the case of FontWork
                    bOtherObjs = true;
                }
            }
            else
            {
                bOtherObjs = true;
            }
        }
    }
    else
    {
        const SdrPathObj* pPath = dynamic_cast<const SdrPathObj*>(pObj);
        const SdrObjCustomShape* pCustomShape = dynamic_cast<const SdrObjCustomShape*>(pObj);

        // #i37011#
        if(pPath)
        {
            if(ImpCanDismantle(pPath->GetPathPoly(),bMakeLines))
            {
                bMin1PolyPoly = true;
            }

            SdrObjTransformInfoRec aInfo;
            pObj->TakeObjInfo(aInfo);

            // new condition IsLine() to be able to break simple Lines
            if(!(aInfo.bCanConvToPath || aInfo.bCanConvToPoly) && !pPath->IsLine())
            {
                // happens e. g. in the case of FontWork
                bOtherObjs = true;
            }
        }
        else if(pCustomShape)
        {
            if(bMakeLines)
            {
                // allow break command
                bMin1PolyPoly = true;
            }
        }
        else
        {
            bOtherObjs = true;
        }
    }
    return bMin1PolyPoly && !bOtherObjs;
}

void SdrEditView::ImpDismantleOneObject(const SdrObject* pObj, SdrObjList& rOL, size_t& rPos, SdrPageView* pPV, bool bMakeLines)
{
    const SdrPathObj* pSrcPath = dynamic_cast<const SdrPathObj*>( pObj );
    const SdrObjCustomShape* pCustomShape = dynamic_cast<const SdrObjCustomShape*>( pObj );

    const bool bUndo = IsUndoEnabled();

    if(pSrcPath)
    {
        // #i74631# redesigned due to XpolyPolygon removal and explicit constructors
        SdrObject* pLast = nullptr; // to be able to apply OutlinerParaObject
        const basegfx::B2DPolyPolygon& rPolyPolygon(pSrcPath->GetPathPoly());
        const sal_uInt32 nPolyCount(rPolyPolygon.count());

        for(sal_uInt32 a(0); a < nPolyCount; a++)
        {
            const basegfx::B2DPolygon& rCandidate(rPolyPolygon.getB2DPolygon(a));
            const sal_uInt32 nPointCount(rCandidate.count());

            if(!bMakeLines || nPointCount < 2)
            {
                rtl::Reference<SdrPathObj> pPath = new SdrPathObj(
                    pSrcPath->getSdrModelFromSdrObject(),
                    pSrcPath->GetObjIdentifier(),
                    basegfx::B2DPolyPolygon(rCandidate));
                ImpCopyAttributes(pSrcPath, pPath.get());
                pLast = pPath.get();
                rOL.InsertObject(pPath.get(), rPos);
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath, true));
                MarkObj(pPath.get(), pPV, false, true);
                rPos++;
            }
            else
            {
                const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);

                for(sal_uInt32 b(0); b < nLoopCount; b++)
                {
                    SdrObjKind eKind(SdrObjKind::PolyLine);
                    basegfx::B2DPolygon aNewPolygon;
                    const sal_uInt32 nNextIndex((b + 1) % nPointCount);

                    aNewPolygon.append(rCandidate.getB2DPoint(b));

                    if(rCandidate.areControlPointsUsed())
                    {
                        aNewPolygon.appendBezierSegment(
                            rCandidate.getNextControlPoint(b),
                            rCandidate.getPrevControlPoint(nNextIndex),
                            rCandidate.getB2DPoint(nNextIndex));
                        eKind = SdrObjKind::PathLine;
                    }
                    else
                    {
                        aNewPolygon.append(rCandidate.getB2DPoint(nNextIndex));
                    }

                    rtl::Reference<SdrPathObj> pPath = new SdrPathObj(
                        pSrcPath->getSdrModelFromSdrObject(),
                        eKind,
                        basegfx::B2DPolyPolygon(aNewPolygon));
                    ImpCopyAttributes(pSrcPath, pPath.get());
                    pLast = pPath.get();
                    rOL.InsertObject(pPath.get(), rPos);
                    if( bUndo )
                        AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pPath, true));
                    MarkObj(pPath.get(), pPV, false, true);
                    rPos++;
                }
            }
        }

        if(pLast && pSrcPath->GetOutlinerParaObject())
        {
            pLast->SetOutlinerParaObject(*pSrcPath->GetOutlinerParaObject());
        }
    }
    else if(pCustomShape)
    {
        if(bMakeLines)
        {
            // break up custom shape
            const SdrObject* pReplacement = pCustomShape->GetSdrObjectFromCustomShape();

            if(pReplacement)
            {
                rtl::Reference<SdrObject> pCandidate(pReplacement->CloneSdrObject(pReplacement->getSdrModelFromSdrObject()));
                DBG_ASSERT(pCandidate, "SdrEditView::ImpDismantleOneObject: Could not clone SdrObject (!)");

                if(pCustomShape->GetMergedItem(SDRATTR_SHADOW).GetValue())
                {
                    if(dynamic_cast<const SdrObjGroup*>( pReplacement) !=  nullptr)
                    {
                        pCandidate->SetMergedItem(makeSdrShadowItem(true));
                    }
                }

                rOL.InsertObject(pCandidate.get(), rPos);
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pCandidate, true));
                MarkObj(pCandidate.get(), pPV, false, true);

                if(pCustomShape->HasText() && !pCustomShape->IsTextPath())
                {
                    // #i37011# also create a text object and add at rPos + 1
                    rtl::Reference<SdrObject> pTextObj = SdrObjFactory::MakeNewObject(
                        pCustomShape->getSdrModelFromSdrObject(),
                        pCustomShape->GetObjInventor(),
                        SdrObjKind::Text);

                    // Copy text content
                    OutlinerParaObject* pParaObj = pCustomShape->GetOutlinerParaObject();
                    if(pParaObj)
                    {
                        pTextObj->NbcSetOutlinerParaObject(*pParaObj);
                    }

                    // copy all attributes
                    SfxItemSet aTargetItemSet(pCustomShape->GetMergedItemSet());

                    // clear fill and line style
                    aTargetItemSet.Put(XLineStyleItem(drawing::LineStyle_NONE));
                    aTargetItemSet.Put(XFillStyleItem(drawing::FillStyle_NONE));

                    // get the text bounds and set at text object
                    tools::Rectangle aTextBounds = pCustomShape->GetSnapRect();
                    if(pCustomShape->GetTextBounds(aTextBounds))
                    {
                        pTextObj->SetSnapRect(aTextBounds);
                    }

                    // if rotated, copy GeoStat, too.
                    const GeoStat& rSourceGeo = pCustomShape->GetGeoStat();
                    if(rSourceGeo.m_nRotationAngle)
                    {
                        pTextObj->NbcRotate(
                            pCustomShape->GetSnapRect().Center(), rSourceGeo.m_nRotationAngle,
                            rSourceGeo.mfSinRotationAngle, rSourceGeo.mfCosRotationAngle);
                    }

                    // set modified ItemSet at text object
                    pTextObj->SetMergedItemSet(aTargetItemSet);

                    // insert object
                    rOL.InsertObject(pTextObj.get(), rPos + 1);
                    if( bUndo )
                        AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pTextObj, true));
                    MarkObj(pTextObj.get(), pPV, false, true);
                }
            }
        }
    }
}

void SdrEditView::DismantleMarkedObjects(bool bMakeLines)
{
    // temporary MarkList
    SdrMarkList aRemoveBuffer;

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();

    const bool bUndo = IsUndoEnabled();

    if( bUndo )
    {
        // comment is constructed later
        BegUndo(u""_ustr, u""_ustr, bMakeLines ? SdrRepeatFunc::DismantleLines : SdrRepeatFunc::DismantlePolys);
    }

    SdrObjList* pOL0=nullptr;
    const bool bWasLocked = GetModel().isLocked();
    GetModel().setLock(true);
    for (size_t nm=rMarkList.GetMarkCount(); nm>0;) {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrPageView* pPV=pM->GetPageView();
        SdrObjList* pOL=pObj->getParentSdrObjListFromSdrObject();
        if (pOL!=pOL0) { pOL0=pOL; pObj->GetOrdNum(); } // make sure OrdNums are correct!
        if (ImpCanDismantle(pObj,bMakeLines)) {
            assert(pOL);
            aRemoveBuffer.InsertEntry(SdrMark(pObj,pM->GetPageView()));
            const size_t nPos0=pObj->GetOrdNumDirect();
            size_t nPos=nPos0+1;
            SdrObjList* pSubList=pObj->GetSubList();
            if (pSubList!=nullptr && !pObj->Is3DObj()) {
                SdrObjListIter aIter(pSubList,SdrIterMode::DeepNoGroups);
                while (aIter.IsMore()) {
                    const SdrObject* pObj1=aIter.Next();
                    ImpDismantleOneObject(pObj1,*pOL,nPos,pPV,bMakeLines);
                }
            } else {
                ImpDismantleOneObject(pObj,*pOL,nPos,pPV,bMakeLines);
            }
            if( bUndo )
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj,true));
            pOL->RemoveObject(nPos0);
        }
    }
    GetModel().setLock(bWasLocked);

    if( bUndo )
    {
        // construct UndoComment from objects actually used
        SetUndoComment(SvxResId(bMakeLines?STR_EditDismantle_Lines:STR_EditDismantle_Polys),aRemoveBuffer.GetMarkDescription());
        // remove objects actually used from the list
        EndUndo();
    }
}


// Group


void SdrEditView::GroupMarked()
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() == 0)
        return;

    rMarkList.ForceSort();

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
    {
        BegUndo(SvxResId(STR_EditGroup),rMarkList.GetMarkDescription(),SdrRepeatFunc::Group);

        for(size_t nm = rMarkList.GetMarkCount(); nm>0; )
        {
            // add UndoActions for all affected objects
            --nm;
            SdrMark* pM=rMarkList.GetMark(nm);
            SdrObject* pObj = pM->GetMarkedSdrObj();
            AddUndoActions( CreateConnectorUndo( *pObj ) );
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoRemoveObject( *pObj ));
        }
    }

    SdrMarkList aNewMark;
    SdrPageView* pPV = GetSdrPageView();

    if(pPV)
    {
        SdrObjList* pCurrentLst=pPV->GetObjList();
        SdrObjList* pSrcLst=pCurrentLst;
        SdrObjList* pSrcLst0=pSrcLst;
        // make sure OrdNums are correct
        if (pSrcLst->IsObjOrdNumsDirty())
            pSrcLst->RecalcObjOrdNums();
        rtl::Reference<SdrObject> pGrp;
        SdrObjList* pDstLst=nullptr;
        // if all selected objects come from foreign object lists.
        // the group object is the last one in the list.
        size_t      nInsPos=pSrcLst->GetObjCount();
        bool        bNeedInsPos=true;
        for (size_t nm=rMarkList.GetMarkCount(); nm>0;)
        {
            --nm;
            SdrMark* pM=rMarkList.GetMark(nm);
            if (pM->GetPageView()==pPV)
            {
                SdrObject* pObj=pM->GetMarkedSdrObj();
                if (!pGrp)
                {
                    pGrp = new SdrObjGroup(pObj->getSdrModelFromSdrObject());
                    pDstLst=pGrp->GetSubList();
                    assert(pDstLst && "Alleged group object doesn't return object list.");
                }
                pSrcLst=pObj->getParentSdrObjListFromSdrObject();
                if (pSrcLst!=pSrcLst0)
                {
                    if (pSrcLst->IsObjOrdNumsDirty())
                        pSrcLst->RecalcObjOrdNums();
                }
                bool bForeignList=pSrcLst!=pCurrentLst;
                if (!bForeignList && bNeedInsPos)
                {
                    nInsPos=pObj->GetOrdNum(); // this way, all ObjOrdNum of the page are set
                    nInsPos++;
                    bNeedInsPos=false;
                }
                pSrcLst->RemoveObject(pObj->GetOrdNumDirect());
                if (!bForeignList)
                    nInsPos--; // correct InsertPos
                pDstLst->InsertObject(pObj,0);
                GetMarkedObjectListWriteAccess().DeleteMark(nm);
                pSrcLst0=pSrcLst;
            }
        }
        if (pGrp!=nullptr)
        {
            assert(pDstLst); // keep coverity happy
            aNewMark.InsertEntry(SdrMark(pGrp.get(),pPV));
            const size_t nCount=pDstLst->GetObjCount();
            pCurrentLst->InsertObject(pGrp.get(),nInsPos);
            if( bUndo )
            {
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pGrp,true)); // no recalculation!
                for (size_t no=0; no<nCount; ++no)
                {
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoInsertObject(*pDstLst->GetObj(no)));
                }
            }
        }
    }
    GetMarkedObjectListWriteAccess().Merge(aNewMark);
    MarkListHasChanged();

    if( bUndo )
        EndUndo();
}


// Ungroup


void SdrEditView::UnGroupMarked()
{
    SdrMarkList aNewMark;

    const bool bUndo = IsUndoEnabled();
    if( bUndo )
        BegUndo(u""_ustr, u""_ustr, SdrRepeatFunc::Ungroup);

    size_t nCount=0;
    OUString aName1;
    OUString aName;
    bool bNameOk=false;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    for (size_t nm=rMarkList.GetMarkCount(); nm>0;) {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pGrp=pM->GetMarkedSdrObj();
        SdrObjList* pSrcLst=pGrp->GetSubList();
        if (pSrcLst!=nullptr) {
            nCount++;
            if (nCount==1) {
                aName = pGrp->TakeObjNameSingul();  // retrieve name of group
                aName1 = pGrp->TakeObjNamePlural(); // retrieve name of group
                bNameOk=true;
            } else {
                if (nCount==2) aName=aName1; // set plural name
                if (bNameOk) {
                    OUString aStr(pGrp->TakeObjNamePlural()); // retrieve name of group

                    if (aStr != aName)
                        bNameOk = false;
                }
            }
            size_t nDstCnt=pGrp->GetOrdNum();
            SdrObjList* pDstLst=pM->GetPageView()->GetObjList();
            size_t nObjCount=pSrcLst->GetObjCount();
            const bool bIsDiagram(pGrp->isDiagram());

            // If the Group is a Diagram, it has a filler BG object to guarantee
            // the Diagam's dimensions. Identify that shape
            if(bIsDiagram && nObjCount)
            {
                SdrObject* pObj(pSrcLst->GetObj(0));

                if(nullptr != pObj && !pObj->IsGroupObject() &&
                    !pObj->HasLineStyle() &&
                    pObj->IsMoveProtect() && pObj->IsResizeProtect())
                {
                    if(pObj->HasFillStyle())
                    {
                        // If it has FillStyle it is a useful object representing that possible
                        // defined fill from oox import. In this case, we should remove the
                        // Move/Resize protection to allow seamless further processing.

                        // Undo of these is handled by SdrUndoGeoObj which holds a SdrObjGeoData,
                        // create one
                        if( bUndo )
                            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pObj));

                        pObj->SetMoveProtect(false);
                        pObj->SetResizeProtect(false);
                    }
                    else
                    {
                        // If it has no FillStyle it is not useful for any further processing
                        // but only was used as a placeholder, get directly rid of it
                        if( bUndo )
                            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj));

                        pSrcLst->RemoveObject(0);

                        nObjCount = pSrcLst->GetObjCount();
                    }
                }
            }

            // FIRST move contained objects to parent of group, so that
            // the contained objects are NOT migrated to the UNDO-ItemPool
            // when AddUndo(new SdrUndoDelObj(*pGrp)) is called.
            if( bUndo )
            {
                for (size_t no=nObjCount; no>0;)
                {
                    no--;
                    SdrObject* pObj=pSrcLst->GetObj(no);
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoRemoveObject(*pObj));
                }
            }

            for (size_t no=0; no<nObjCount; ++no)
            {
                rtl::Reference<SdrObject> pObj=pSrcLst->RemoveObject(0);
                pDstLst->InsertObject(pObj.get(),nDstCnt);
                if( bUndo )
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoInsertObject(*pObj,true));
                nDstCnt++;
                // No SortCheck when inserting into MarkList, because that would
                // provoke a RecalcOrdNums() each time because of pObj->GetOrdNum():
                aNewMark.InsertEntry(SdrMark(pObj.get(),pM->GetPageView()),false);
            }

            if( bUndo )
            {
                // Now it is safe to add the delete-UNDO which triggers the
                // MigrateItemPool now only for itself, not for the sub-objects.
                // nDstCnt is right, because previous inserts move group
                // object deeper and increase nDstCnt.
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pGrp));
            }
            pDstLst->RemoveObject(nDstCnt);

            GetMarkedObjectListWriteAccess().DeleteMark(nm);
        }
    }
    if (nCount!=0)
    {
        if (!bNameOk)
            aName=SvxResId(STR_ObjNamePluralGRUP); // Use the term "Group Objects," if different objects are grouped.
        SetUndoComment(SvxResId(STR_EditUngroup),aName);
    }

    if( bUndo )
        EndUndo();

    if (nCount!=0)
    {
        GetMarkedObjectListWriteAccess().Merge(aNewMark,true); // Because of the sorting above, aNewMark is reversed
        MarkListHasChanged();
    }
}


// ConvertToPoly


rtl::Reference<SdrObject> SdrEditView::ImpConvertOneObj(SdrObject* pObj, bool bPath, bool bLineToArea)
{
    rtl::Reference<SdrObject> pNewObj = pObj->ConvertToPolyObj(bPath, bLineToArea);
    if (pNewObj)
    {
        SdrObjList* pOL = pObj->getParentSdrObjListFromSdrObject();
        const bool bUndo = IsUndoEnabled();
        if( bUndo )
            AddUndo(GetModel().GetSdrUndoFactory().CreateUndoReplaceObject(*pObj,*pNewObj));

        pOL->ReplaceObject(pNewObj.get(), pObj->GetOrdNum());
    }
    return pNewObj;
}

void SdrEditView::ImpConvertTo(bool bPath, bool bLineToArea)
{
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    if (rMarkList.GetMarkCount() == 0)
        return;

    bool bMrkChg = false;
    const size_t nMarkCount=rMarkList.GetMarkCount();
    TranslateId pDscrID;
    if(bLineToArea)
    {
        if(nMarkCount == 1)
            pDscrID = STR_EditConvToContour;
        else
            pDscrID = STR_EditConvToContours;

        BegUndo(SvxResId(pDscrID), rMarkList.GetMarkDescription());
    }
    else
    {
        if (bPath) {
            if (nMarkCount==1) pDscrID=STR_EditConvToCurve;
            else pDscrID=STR_EditConvToCurves;
            BegUndo(SvxResId(pDscrID),rMarkList.GetMarkDescription(),SdrRepeatFunc::ConvertToPath);
        } else {
            if (nMarkCount==1) pDscrID=STR_EditConvToPoly;
            else pDscrID=STR_EditConvToPolys;
            BegUndo(SvxResId(pDscrID),rMarkList.GetMarkDescription(),SdrRepeatFunc::ConvertToPoly);
        }
    }
    for (size_t nm=nMarkCount; nm>0;) {
        --nm;
        SdrMark* pM=rMarkList.GetMark(nm);
        SdrObject* pObj=pM->GetMarkedSdrObj();
        SdrPageView* pPV=pM->GetPageView();
        if (pObj->IsGroupObject() && !pObj->Is3DObj()) {
            SdrObject* pGrp=pObj;
            SdrObjListIter aIter(*pGrp, SdrIterMode::DeepNoGroups);
            while (aIter.IsMore()) {
                pObj=aIter.Next();
                ImpConvertOneObj(pObj,bPath,bLineToArea);
            }
        } else {
            rtl::Reference<SdrObject> pNewObj=ImpConvertOneObj(pObj,bPath,bLineToArea);
            if (pNewObj) {
                bMrkChg=true;
                GetMarkedObjectListWriteAccess().ReplaceMark(SdrMark(pNewObj.get(),pPV),nm);
            }
        }
    }
    EndUndo();
    if (bMrkChg)
    {
        AdjustMarkHdl();
        MarkListHasChanged();
    }
}

void SdrEditView::ConvertMarkedToPathObj(bool bLineToArea)
{
    ImpConvertTo(true, bLineToArea);
}

void SdrEditView::ConvertMarkedToPolyObj()
{
    ImpConvertTo(false, false/*bLineToArea*/);
}

namespace
{
    GDIMetaFile GetMetaFile(SdrGrafObj const * pGraf)
    {
        if (pGraf->HasGDIMetaFile())
            return pGraf->GetTransformedGraphic(SdrGrafObjTransformsAttrs::MIRROR).GetGDIMetaFile();
        assert(pGraf->isEmbeddedVectorGraphicData());
        return pGraf->getMetafileFromEmbeddedVectorGraphicData();
    }
}

// Metafile Import
void SdrEditView::DoImportMarkedMtf(SvdProgressInfo *pProgrInfo)
{
    const bool bUndo = IsUndoEnabled();

    if( bUndo )
        BegUndo(u""_ustr, u""_ustr, SdrRepeatFunc::ImportMtf);

    const SdrMarkList& rMarkList = GetMarkedObjectList();
    rMarkList.ForceSort();
    SdrMarkList aForTheDescription;
    SdrMarkList aNewMarked;
    for (size_t nm =rMarkList.GetMarkCount(); nm > 0; )
    {
        // create Undo objects for all new objects
        // check for cancellation between the metafiles
        if (pProgrInfo != nullptr)
        {
            pProgrInfo->SetNextObject();
            if (!pProgrInfo->ReportActions(0))
                break;
        }

        --nm;
        SdrMark*     pM=rMarkList.GetMark(nm);
        SdrObject*   pObj=pM->GetMarkedSdrObj();
        SdrPageView* pPV=pM->GetPageView();
        SdrObjList*  pOL=pObj->getParentSdrObjListFromSdrObject();
        const size_t nInsPos=pObj->GetOrdNum()+1;
        size_t      nInsCnt=0;
        tools::Rectangle aLogicRect;

        SdrGrafObj*  pGraf = dynamic_cast<SdrGrafObj*>( pObj );
        if (pGraf != nullptr)
        {
            Graphic aGraphic = pGraf->GetGraphic();
            auto const & pVectorGraphicData = aGraphic.getVectorGraphicData();

            if (pVectorGraphicData && pVectorGraphicData->getType() == VectorGraphicDataType::Pdf)
            {
                auto pPdfium = vcl::pdf::PDFiumLibrary::get();
                if (pPdfium)
                {
                    aLogicRect = pGraf->GetLogicRect();
                    ImpSdrPdfImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect, aGraphic);
                    if (aGraphic.getPageNumber() < aFilter.GetPageCount())
                    {
                        nInsCnt = aFilter.DoImport(*pOL, nInsPos, aGraphic.getPageNumber(), pProgrInfo);
                    }
                }
            }
            else if (pGraf->HasGDIMetaFile() || pGraf->isEmbeddedVectorGraphicData() )
            {
                GDIMetaFile aMetaFile(GetMetaFile(pGraf));
                if (aMetaFile.GetActionSize())
                {
                    aLogicRect = pGraf->GetLogicRect();
                    ImpSdrGDIMetaFileImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect);
                    nInsCnt = aFilter.DoImport(aMetaFile, *pOL, nInsPos, pProgrInfo);
                }
            }
        }

        SdrOle2Obj* pOle2 = dynamic_cast<SdrOle2Obj*>(pObj);
        if (pOle2)
        {
            if (const Graphic* pGraphic = pOle2->GetGraphic())
            {
                aLogicRect = pOle2->GetLogicRect();
                ImpSdrGDIMetaFileImport aFilter(GetModel(), pObj->GetLayer(), aLogicRect);
                nInsCnt = aFilter.DoImport(pGraphic->GetGDIMetaFile(), *pOL, nInsPos, pProgrInfo);
            }
        }

        if (nInsCnt != 0)
        {
            // transformation
            GeoStat aGeoStat(pGraf ? pGraf->GetGeoStat() : pOle2->GetGeoStat());
            size_t nObj = nInsPos;

            if (aGeoStat.m_nShearAngle)
                aGeoStat.RecalcTan();

            if (aGeoStat.m_nRotationAngle)
                aGeoStat.RecalcSinCos();

            for (size_t i = 0; i < nInsCnt; i++)
            {
                if (bUndo)
                    AddUndo(GetModel().GetSdrUndoFactory().CreateUndoNewObject(*pOL->GetObj(nObj)));

                // update new MarkList
                SdrObject* pCandidate = pOL->GetObj(nObj);

                // apply original transformation
                if (aGeoStat.m_nShearAngle)
                    pCandidate->NbcShear(aLogicRect.TopLeft(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false);

                if (aGeoStat.m_nRotationAngle)
                    pCandidate->NbcRotate(aLogicRect.TopLeft(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle);

                SdrMark aNewMark(pCandidate, pPV);
                aNewMarked.InsertEntry(aNewMark);

                nObj++;
            }

            aForTheDescription.InsertEntry(*pM);

            if (bUndo)
                AddUndo(GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pObj));

            // remove object from selection and delete
            GetMarkedObjectListWriteAccess().DeleteMark(rMarkList.FindObject(pObj));
            pOL->RemoveObject(nInsPos-1);
        }
    }

    if (aNewMarked.GetMarkCount())
    {
        // create new selection
        for (size_t a = 0; a < aNewMarked.GetMarkCount(); ++a)
        {
            GetMarkedObjectListWriteAccess().InsertEntry(*aNewMarked.GetMark(a));
        }

        rMarkList.ForceSort();
    }

    if (bUndo)
    {
        SetUndoComment(SvxResId(STR_EditImportMtf),aForTheDescription.GetMarkDescription());
        EndUndo();
    }
}

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