/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <com/sun/star/embed/NoVisualAreaSizeException.hpp>

#include <svx/svditer.hxx>
#include <svx/svdograf.hxx>
#include <svx/svdoole2.hxx>
#include <svx/svdouno.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svdundo.hxx>
#include <svx/xbtmpit.hxx>
#include <svx/xoutbmp.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/viewfrm.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <com/sun/star/embed/Aspects.hpp>

#include "document.hxx"
#include "viewfunc.hxx"
#include "tabvwsh.hxx"
#include "drawview.hxx"
#include "scmod.hxx"
#include "drwlayer.hxx"
#include "drwtrans.hxx"
#include "globstr.hrc"
#include "chartlis.hxx"
#include "docuno.hxx"
#include "docsh.hxx"
#include "convuno.hxx"
#include "dragdata.hxx"

extern Point aDragStartDiff;

sal_Bool bPasteIsMove = false;

using namespace com::sun::star;


static void lcl_AdjustInsertPos( ScViewData* pData, Point& rPos, Size& rSize )
{
    SdrPage* pPage = pData->GetScDrawView()->GetModel()->GetPage( static_cast<sal_uInt16>(pData->GetTabNo()) );
    OSL_ENSURE(pPage,"pPage ???");
    Size aPgSize( pPage->GetSize() );
    if (aPgSize.Width() < 0)
        aPgSize.Width() = -aPgSize.Width();
    long x = aPgSize.Width() - rPos.X() - rSize.Width();
    long y = aPgSize.Height() - rPos.Y() - rSize.Height();
    // if necessary: adjustments (80/200) for pixel approx. errors
    if( x < 0 )
        rPos.X() += x + 80;
    if( y < 0 )
        rPos.Y() += y + 200;
    rPos.X() += rSize.Width() / 2;          // position at paste is center
    rPos.Y() += rSize.Height() / 2;
}

void ScViewFunc::PasteDraw( const Point& rLogicPos, SdrModel* pModel,
                            bool bGroup, const OUString& rSrcShellID, const OUString& rDestShellID )
{
    bool bSameDocClipboard = rSrcShellID == rDestShellID;

    MakeDrawLayer();
    Point aPos( rLogicPos );

    // MapMode at Outliner-RefDevice has to be right (as in FuText::MakeOutliner)
    //! merge with FuText::MakeOutliner?
    MapMode aOldMapMode;
    OutputDevice* pRef = GetViewData()->GetDocument()->GetDrawLayer()->GetRefDevice();
    if (pRef)
    {
        aOldMapMode = pRef->GetMapMode();
        pRef->SetMapMode( MapMode(MAP_100TH_MM) );
    }

    sal_Bool bNegativePage = GetViewData()->GetDocument()->IsNegativePage( GetViewData()->GetTabNo() );

    SdrView* pDragEditView = NULL;
    ScModule* pScMod = SC_MOD();
    const ScDragData& rData = pScMod->GetDragData();
    ScDrawTransferObj* pDrawTrans = rData.pDrawTransfer;
    if (pDrawTrans)
    {
        pDragEditView = pDrawTrans->GetDragSourceView();

        aPos -= aDragStartDiff;
        if ( bNegativePage )
        {
            if (aPos.X() > 0) aPos.X() = 0;
        }
        else
        {
            if (aPos.X() < 0) aPos.X() = 0;
        }
        if (aPos.Y() < 0) aPos.Y() = 0;
    }

    ScDrawView* pScDrawView = GetScDrawView();
    if (bGroup)
        pScDrawView->BegUndo( ScGlobal::GetRscString( STR_UNDO_PASTE ) );

    sal_Bool bSameDoc = ( pDragEditView && pDragEditView->GetModel() == pScDrawView->GetModel() );
    if (bSameDoc)
    {
            // copy locally - incl. charts

        Point aSourceStart = pDragEditView->GetAllMarkedRect().TopLeft();
        long nDiffX = aPos.X() - aSourceStart.X();
        long nDiffY = aPos.Y() - aSourceStart.Y();

            // move within a page?

        if ( bPasteIsMove &&
                pScDrawView->GetSdrPageView()->GetPage() ==
                pDragEditView->GetSdrPageView()->GetPage() )
        {
            if ( nDiffX != 0 || nDiffY != 0 )
                pDragEditView->MoveAllMarked(Size(nDiffX,nDiffY), false);
        }
        else
        {
            SdrModel* pDrawModel = pDragEditView->GetModel();
            SCTAB nTab = GetViewData()->GetTabNo();
            SdrPage* pDestPage = pDrawModel->GetPage( static_cast< sal_uInt16 >( nTab ) );
            OSL_ENSURE(pDestPage,"who is this, Page?");

            ::std::vector< OUString > aExcludedChartNames;
            if ( pDestPage )
            {
                ScChartHelper::GetChartNames( aExcludedChartNames, pDestPage );
            }

            SdrMarkList aMark = pDragEditView->GetMarkedObjectList();
            aMark.ForceSort();
            sal_uLong nMarkAnz=aMark.GetMarkCount();
            for (sal_uLong nm=0; nm<nMarkAnz; nm++) {
                const SdrMark* pM=aMark.GetMark(nm);
                const SdrObject* pObj=pM->GetMarkedSdrObj();

                SdrObject* pNeuObj=pObj->Clone();

                if (pNeuObj!=NULL)
                {
                    pNeuObj->SetModel(pDrawModel);
                    pNeuObj->SetPage(pDestPage);

                    //  copy graphics within the same model - always needs new name
                    if ( pNeuObj->ISA(SdrGrafObj) && !bPasteIsMove )
                        pNeuObj->SetName(((ScDrawLayer*)pDrawModel)->GetNewGraphicName());

                    if (nDiffX!=0 || nDiffY!=0)
                        pNeuObj->NbcMove(Size(nDiffX,nDiffY));
                    pDestPage->InsertObject( pNeuObj );
                    pScDrawView->AddUndo(new SdrUndoInsertObj( *pNeuObj ));

                    if (ScDrawLayer::IsCellAnchored(*pNeuObj))
                        ScDrawLayer::SetCellAnchoredFromPosition(*pNeuObj, *GetViewData()->GetDocument(), nTab);
                }
            }

            if (bPasteIsMove)
                pDragEditView->DeleteMarked();

            ScDocument* pDocument = GetViewData()->GetDocument();
            ScDocShell* pDocShell = GetViewData()->GetDocShell();
            ScModelObj* pModelObj = ( pDocShell ? ScModelObj::getImplementation( pDocShell->GetModel() ) : NULL );
            if ( pDocument && pDestPage && pModelObj && pDrawTrans )
            {
                const ScRangeListVector& rProtectedChartRangesVector( pDrawTrans->GetProtectedChartRangesVector() );
                ScChartHelper::CreateProtectedChartListenersAndNotify( pDocument, pDestPage, pModelObj, nTab,
                    rProtectedChartRangesVector, aExcludedChartNames, bSameDoc );
            }
        }
    }
    else
    {
        bPasteIsMove = false;       // no internal move happened

        SdrView aView(pModel);      // #i71529# never create a base class of SdrView directly!
        SdrPageView* pPv = aView.ShowSdrPage(aView.GetModel()->GetPage(0));
        aView.MarkAllObj(pPv);
        Size aSize = aView.GetAllMarkedRect().GetSize();
        lcl_AdjustInsertPos( GetViewData(), aPos, aSize );

        // don't change marking if OLE object is active
        // (at Drop from OLE object it would be deactivated in the middle of ExecuteDrag!)

        sal_uLong nOptions = 0;
        SfxInPlaceClient* pClient = GetViewData()->GetViewShell()->GetIPClient();
        if ( pClient && pClient->IsObjectInPlaceActive() )
            nOptions |= SDRINSERT_DONTMARK;

        ::std::vector< OUString > aExcludedChartNames;
        SCTAB nTab = GetViewData()->GetTabNo();
        SdrPage* pPage = pScDrawView->GetModel()->GetPage( static_cast< sal_uInt16 >( nTab ) );
        OSL_ENSURE( pPage, "Page?" );
        if ( pPage )
        {
            ScChartHelper::GetChartNames( aExcludedChartNames, pPage );
        }

        // #89247# Set flag for ScDocument::UpdateChartListeners() which is
        // called during paste.
        if ( !bSameDocClipboard )
            GetViewData()->GetDocument()->SetPastingDrawFromOtherDoc( sal_True );

        pScDrawView->Paste(*pModel, aPos, NULL, nOptions, rSrcShellID, rDestShellID);

        if ( !bSameDocClipboard )
            GetViewData()->GetDocument()->SetPastingDrawFromOtherDoc( false );

        // Paste puts all objects on the active (front) layer
        // controls must be on SC_LAYER_CONTROLS
        if (pPage)
        {
            SdrObjListIter aIter( *pPage, IM_DEEPNOGROUPS );
            SdrObject* pObject = aIter.Next();
            while (pObject)
            {
                if ( pObject->ISA(SdrUnoObj) && pObject->GetLayer() != SC_LAYER_CONTROLS )
                    pObject->NbcSetLayer(SC_LAYER_CONTROLS);

                if (ScDrawLayer::IsCellAnchored(*pObject))
                    ScDrawLayer::SetCellAnchoredFromPosition(*pObject, *GetViewData()->GetDocument(), nTab);

                pObject = aIter.Next();
            }
        }

        // all graphics objects must have names
        GetViewData()->GetDocument()->EnsureGraphicNames();

        ScDocument* pDocument = GetViewData()->GetDocument();
        ScDocShell* pDocShell = GetViewData()->GetDocShell();
        ScModelObj* pModelObj = ( pDocShell ? ScModelObj::getImplementation( pDocShell->GetModel() ) : NULL );
        ScDrawTransferObj* pTransferObj = ScDrawTransferObj::GetOwnClipboard( NULL );
        if ( pDocument && pPage && pModelObj && ( pTransferObj || pDrawTrans ) )
        {
            const ScRangeListVector& rProtectedChartRangesVector(
                pTransferObj ? pTransferObj->GetProtectedChartRangesVector() : pDrawTrans->GetProtectedChartRangesVector() );
            ScChartHelper::CreateProtectedChartListenersAndNotify( pDocument, pPage, pModelObj, nTab,
                rProtectedChartRangesVector, aExcludedChartNames, bSameDocClipboard );
        }
    }

    if (bGroup)
    {
        pScDrawView->GroupMarked();
        pScDrawView->EndUndo();
    }

    if (pRef)
        pRef->SetMapMode( aOldMapMode );

    // GetViewData()->GetViewShell()->SetDrawShell( sal_True );
    // It is not sufficient to just set the DrawShell if we pasted, for
    // example, a chart.  SetDrawShellOrSub() would only work for D&D in the
    // same document but not if inserting from the clipboard, therefore
    // MarkListHasChanged() is what we need.
    pScDrawView->MarkListHasChanged();

}

sal_Bool ScViewFunc::PasteObject( const Point& rPos, const uno::Reference < embed::XEmbeddedObject >& xObj,
                                const Size* pDescSize, const Graphic* pReplGraph, const OUString& aMediaType, sal_Int64 nAspect )
{
    MakeDrawLayer();
    if ( xObj.is() )
    {
        OUString aName;
        //TODO/MBA: is that OK?
        comphelper::EmbeddedObjectContainer& aCnt = GetViewData()->GetViewShell()->GetObjectShell()->GetEmbeddedObjectContainer();
        if ( !aCnt.HasEmbeddedObject( xObj ) )
            aCnt.InsertEmbeddedObject( xObj, aName );
        else
            aName = aCnt.GetEmbeddedObjectName( xObj );

        svt::EmbeddedObjectRef aObjRef( xObj, nAspect );
        if ( pReplGraph )
            aObjRef.SetGraphic( *pReplGraph, aMediaType );

        Size aSize;
        if ( nAspect == embed::Aspects::MSOLE_ICON )
        {
            MapMode aMapMode( MAP_100TH_MM );
            aSize = aObjRef.GetSize( &aMapMode );
        }
        else
        {
            // working with visual area can switch object to running state
            MapUnit aMapObj = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( nAspect ) );
            MapUnit aMap100 = MAP_100TH_MM;

            if ( pDescSize && pDescSize->Width() && pDescSize->Height() )
            {
                // use size from object descriptor if given
                aSize = OutputDevice::LogicToLogic( *pDescSize, aMap100, aMapObj );
                awt::Size aSz;
                aSz.Width = aSize.Width();
                aSz.Height = aSize.Height();
                xObj->setVisualAreaSize( nAspect, aSz );
            }

            awt::Size aSz;
            try
            {
                aSz = xObj->getVisualAreaSize( nAspect );
            }
            catch ( embed::NoVisualAreaSizeException& )
            {
                // the default size will be set later
            }

            aSize = Size( aSz.Width, aSz.Height );
            aSize = OutputDevice::LogicToLogic( aSize, aMapObj, aMap100 );  // for SdrOle2Obj

            if( aSize.Height() == 0 || aSize.Width() == 0 )
            {
                OSL_FAIL("SvObjectDescriptor::GetSize == 0");
                aSize.Width() = 5000;
                aSize.Height() = 5000;
                aSize = OutputDevice::LogicToLogic( aSize, aMap100, aMapObj );
                aSz.Width = aSize.Width();
                aSz.Height = aSize.Height();
                xObj->setVisualAreaSize( nAspect, aSz );
            }
        }

        // don't call AdjustInsertPos
        Point aInsPos = rPos;
        if ( GetViewData()->GetDocument()->IsNegativePage( GetViewData()->GetTabNo() ) )
            aInsPos.X() -= aSize.Width();
        Rectangle aRect( aInsPos, aSize );

        ScDrawView* pDrView = GetScDrawView();
        SdrOle2Obj* pSdrObj = new SdrOle2Obj( aObjRef, aName, aRect );

        SdrPageView* pPV = pDrView->GetSdrPageView();
        pDrView->InsertObjectSafe( pSdrObj, *pPV );             // don't mark if OLE
        GetViewData()->GetViewShell()->SetDrawShell( sal_True );
        return sal_True;
    }
    else
        return false;
}

sal_Bool ScViewFunc::PasteBitmapEx( const Point& rPos, const BitmapEx& rBmpEx )
{
    OUString aEmpty;
    Graphic aGraphic(rBmpEx);
    return PasteGraphic( rPos, aGraphic, aEmpty, aEmpty );
}

sal_Bool ScViewFunc::PasteMetaFile( const Point& rPos, const GDIMetaFile& rMtf )
{
    OUString aEmpty;
    Graphic aGraphic(rMtf);
    return PasteGraphic( rPos, aGraphic, aEmpty, aEmpty );
}

sal_Bool ScViewFunc::PasteGraphic( const Point& rPos, const Graphic& rGraphic,
                                const OUString& rFile, const OUString& rFilter )
{
    MakeDrawLayer();
    ScDrawView* pScDrawView = GetScDrawView();

    Point aPos( rPos );
    Window* pWin = GetActiveWin();
    MapMode aSourceMap = rGraphic.GetPrefMapMode();
    MapMode aDestMap( MAP_100TH_MM );

    if (aSourceMap.GetMapUnit() == MAP_PIXEL)
    {
        // consider pixel correction, so bitmap fits to screen
        Fraction aScaleX, aScaleY;
        pScDrawView->CalcNormScale( aScaleX, aScaleY );
        aDestMap.SetScaleX(aScaleX);
        aDestMap.SetScaleY(aScaleY);
    }

    Size aSize = pWin->LogicToLogic( rGraphic.GetPrefSize(), &aSourceMap, &aDestMap );

    if ( GetViewData()->GetDocument()->IsNegativePage( GetViewData()->GetTabNo() ) )
        aPos.X() -= aSize.Width();

    GetViewData()->GetViewShell()->SetDrawShell( sal_True );

    Rectangle aRect(aPos, aSize);
    SdrGrafObj* pGrafObj = new SdrGrafObj(rGraphic, aRect);

    // path was the name of the graphic in history

    ScDrawLayer* pLayer = (ScDrawLayer*) pScDrawView->GetModel();
    OUString aName = pLayer->GetNewGraphicName();                 // "Graphics"
    pGrafObj->SetName(aName);

    // don't mark if OLE
    pScDrawView->InsertObjectSafe(pGrafObj, *pScDrawView->GetSdrPageView());

    // SetGraphicLink has to be used after inserting the object,
    // otherwise an empty graphic is swapped in and the contact stuff crashes.
    // See #i37444#.
    if (!rFile.isEmpty())
        pGrafObj->SetGraphicLink( rFile, ""/*TODO?*/, rFilter );

    return sal_True;
}

sal_Bool ScViewFunc::ApplyGraphicToObject( SdrObject* pPickObj, const Graphic& rGraphic )
{
    sal_Bool bRet = false;

    ScDrawView* pScDrawView = GetScDrawView();
    if ( pScDrawView && pPickObj )
    {
        /**********************************************************************
        * New attributes for object
        **********************************************************************/
        SdrPageView* pPV = pScDrawView->GetSdrPageView();
        if (pPickObj->ISA(SdrGrafObj))
        {
            /******************************************************************
            * Graphic object gets a new graphic
            ******************************************************************/
            SdrGrafObj* pNewGrafObj = (SdrGrafObj*) pPickObj->Clone();
            pNewGrafObj->SetGraphic(rGraphic);

            pScDrawView->BegUndo(ScGlobal::GetRscString(STR_UNDO_DRAGDROP));
            pScDrawView->ReplaceObjectAtView(pPickObj, *pPV, pNewGrafObj);
            pScDrawView->EndUndo();

            bRet = sal_True;
        }
        else if (pPickObj->IsClosedObj() && !pPickObj->ISA(SdrOle2Obj))
        {
            /******************************************************************
            * Object gets filled with the graphic
            ******************************************************************/
            pScDrawView->AddUndo(new SdrUndoAttrObj(*pPickObj));

            SfxItemSet aSet( pScDrawView->GetModel()->GetItemPool(),
                                XATTR_FILLSTYLE, XATTR_FILLBITMAP );
            aSet.Put(XFillStyleItem(XFILL_BITMAP));
            aSet.Put(XFillBitmapItem(OUString(), rGraphic));

            pPickObj->SetMergedItemSetAndBroadcast(aSet);

            bRet = sal_True;
        }
    }
    return bRet;
}


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