/* -*- 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/.
 */
 
#include <sfx2/lokcomponenthelpers.hxx>
 
#include <comphelper/lok.hxx>
#include <comphelper/propertyvalue.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <sfx2/ipclient.hxx>
#include <sfx2/lokhelper.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <tools/fract.hxx>
#include <tools/UnitConversion.hxx>
#include <vcl/virdev.hxx>
#include <vcl/window.hxx>
 
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/frame/XDispatch.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
 
using namespace com::sun::star;
 
css::uno::Reference<css::frame::XController>& LokChartHelper::GetXController()
{
    if(!mxController.is() && mpViewShell)
    {
        SfxInPlaceClient* pIPClient = mpViewShell->GetIPClient();
        if (pIPClient)
        {
            const css::uno::Reference< ::css::embed::XEmbeddedObject >& xEmbObj = pIPClient->GetObject();
            if( xEmbObj.is() )
            {
                ::css::uno::Reference< ::css::chart2::XChartDocument > xChart( xEmbObj->getComponent(), uno::UNO_QUERY );
                if( xChart.is() )
                {
                    ::css::uno::Reference< ::css::frame::XController > xChartController = xChart->getCurrentController();
                    if( xChartController.is() )
                    {
                        mxController = std::move(xChartController);
                    }
                }
            }
        }
    }
 
    return mxController;
}
 
css::uno::Reference<css::frame::XDispatch>& LokChartHelper::GetXDispatcher()
{
    if( !mxDispatcher.is() )
    {
        ::css::uno::Reference< ::css::frame::XController >& xChartController = GetXController();
        if( xChartController.is() )
        {
            ::css::uno::Reference< ::css::frame::XDispatch > xDispatcher( xChartController, uno::UNO_QUERY );
            if( xDispatcher.is() )
            {
                mxDispatcher = std::move(xDispatcher);
            }
        }
    }
 
    return mxDispatcher;
}
 
vcl::Window* LokChartHelper::GetWindow()
{
    if (!mpWindow)
    {
        ::css::uno::Reference< ::css::frame::XController >& xChartController = GetXController();
        if( xChartController.is() )
        {
            ::css::uno::Reference< ::css::frame::XFrame > xFrame = xChartController->getFrame();
            if (xFrame.is())
            {
                ::css::uno::Reference< ::css::awt::XWindow > xDockerWin = xFrame->getContainerWindow();
                vcl::Window* pParent = VCLUnoHelper::GetWindow( xDockerWin );
                if (pParent)
                {
                    sal_uInt16 nTotChildren = pParent->GetChildCount();
                    while (nTotChildren > 0)
                    {
                        --nTotChildren;
                        vcl::Window* pChildWin = pParent->GetChild(nTotChildren);
                        if (pChildWin && pChildWin->IsChart())
                        {
                            mpWindow = pChildWin;
                            break;
                        }
                    }
                }
            }
        }
    }
 
    return mpWindow.get();
}
 
tools::Rectangle LokChartHelper::GetChartBoundingBox()
{
    tools::Rectangle aBBox;
    if (mpViewShell)
    {
        SfxInPlaceClient* pIPClient = mpViewShell->GetIPClient();
        if (pIPClient)
        {
            vcl::Window* pRootWin = pIPClient->GetEditWin();
            if (pRootWin)
            {
                vcl::Window* pWindow = GetWindow();
                if (pWindow)
                {
                    // In all cases, the following code fragment
                    // returns the chart bounding box in twips.
                    const MapMode& aCWMapMode = pWindow->GetMapMode();
                    constexpr auto p = o3tl::getConversionMulDiv(o3tl::Length::px, o3tl::Length::twip);
                    const auto& scaleX = aCWMapMode.GetScaleX();
                    const auto& scaleY = aCWMapMode.GetScaleY();
                    const auto nXNum = p.first * scaleX.GetDenominator();
                    const auto nXDen = p.second * scaleX.GetNumerator();
                    const auto nYNum = p.first * scaleY.GetDenominator();
                    const auto nYDen = p.second * scaleY.GetNumerator();
 
                    Point aOffset = pWindow->GetOffsetPixelFrom(*pRootWin);
                    if (mbNegativeX && AllSettings::GetLayoutRTL())
                    {
                        // If global RTL flag is set, vcl-window X offset of chart window is
                        // mirrored w.r.t parent window rectangle. This needs to be reverted.
                        aOffset.setX(pRootWin->GetOutOffXPixel() + pRootWin->GetSizePixel().Width()
                            - pWindow->GetOutOffXPixel() - pWindow->GetSizePixel().Width());
 
                    }
 
                    aOffset = aOffset.scale(nXNum, nXDen, nYNum, nYDen);
                    Size aSize = pWindow->GetSizePixel().scale(nXNum, nXDen, nYNum, nYDen);
                    aBBox = tools::Rectangle(aOffset, aSize);
                }
            }
        }
    }
    return aBBox;
}
 
void LokChartHelper::Invalidate()
{
    mpWindow = nullptr;
    mxDispatcher.clear();
    mxController.clear();
}
 
bool LokChartHelper::Hit(const Point& aPos)
{
    if (mpViewShell)
    {
        vcl::Window* pChartWindow = GetWindow();
        if (pChartWindow)
        {
            tools::Rectangle rChartBBox = GetChartBoundingBox();
            return rChartBBox.Contains(aPos);
        }
    }
    return false;
}
 
bool LokChartHelper::HitAny(const Point& aPos, bool bNegativeX)
{
    SfxViewShell* pCurView = SfxViewShell::Current();
    int nPartForCurView = pCurView ? pCurView->getPart() : -1;
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pCurView && pViewShell->GetDocId() == pCurView->GetDocId() && pViewShell->getPart() == nPartForCurView)
        {
            LokChartHelper aChartHelper(pViewShell, bNegativeX);
            if (aChartHelper.Hit(aPos))
                return true;
        }
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
    return false;
}
 
void LokChartHelper::PaintTile(VirtualDevice& rRenderContext, const tools::Rectangle& rTileRect)
{
    if (!mpViewShell)
        return;
 
    vcl::Window* pChartWindow = GetWindow();
    if (!pChartWindow)
        return;
 
    tools::Rectangle aChartRect = GetChartBoundingBox();
    tools::Rectangle aTestRect = rTileRect;
    aTestRect.Intersection( aChartRect );
    if (aTestRect.IsEmpty())
        return;
 
    Point aOffset( aChartRect.Left() - rTileRect.Left(), aChartRect.Top() - rTileRect.Top() );
    Point aOffsetFromTile = convertTwipToMm100(aOffset);
    Size aSize = convertTwipToMm100(aChartRect.GetSize());
    tools::Rectangle aRectangle(Point(0,0), aSize);
 
    bool bEnableMapMode = !pChartWindow->IsMapModeEnabled();
    pChartWindow->EnableMapMode();
    bool bRenderContextEnableMapMode = !rRenderContext.IsMapModeEnabled();
    rRenderContext.EnableMapMode();
 
    rRenderContext.Push(vcl::PushFlags::MAPMODE);
 
    MapMode aCWMapMode = pChartWindow->GetMapMode();
    aCWMapMode.SetScaleX(rRenderContext.GetMapMode().GetScaleX());
    aCWMapMode.SetScaleY(rRenderContext.GetMapMode().GetScaleY());
 
    aCWMapMode.SetOrigin(aOffsetFromTile);
    rRenderContext.SetMapMode(aCWMapMode);
 
    pChartWindow->Paint(rRenderContext, aRectangle);
 
    rRenderContext.Pop();
 
    if (bRenderContextEnableMapMode)
        rRenderContext.EnableMapMode(false);
    if (bEnableMapMode)
        pChartWindow->EnableMapMode(false);
}
 
void LokChartHelper::PaintAllChartsOnTile(VirtualDevice& rDevice,
                                          int nOutputWidth, int nOutputHeight,
                                          int nTilePosX, int nTilePosY,
                                          tools::Long nTileWidth, tools::Long nTileHeight,
                                          bool bNegativeX)
{
    if (comphelper::LibreOfficeKit::isTiledAnnotations())
        return;
 
    // Resizes the virtual device so to contain the entries context
    rDevice.SetOutputSizePixel(Size(nOutputWidth, nOutputHeight));
 
    rDevice.Push(vcl::PushFlags::MAPMODE);
    MapMode aMapMode(rDevice.GetMapMode());
 
    // Scaling. Must convert from pixels to twips. We know
    // that VirtualDevices use a DPI of 96.
    const Fraction scale = conversionFract(o3tl::Length::px, o3tl::Length::twip);
    Fraction scaleX = Fraction(nOutputWidth, nTileWidth) * scale;
    Fraction scaleY = Fraction(nOutputHeight, nTileHeight) * scale;
    aMapMode.SetScaleX(scaleX);
    aMapMode.SetScaleY(scaleY);
    rDevice.SetMapMode(aMapMode);
 
    SfxViewShell* pCurView = SfxViewShell::Current();
    int nPartForCurView = pCurView ? pCurView->getPart() : -1;
    tools::Long nTileRectLeft = bNegativeX ? -nTilePosX - nTileWidth : nTilePosX;
    tools::Rectangle aTileRect(Point(nTileRectLeft, nTilePosY), Size(nTileWidth, nTileHeight));
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pCurView && pViewShell->GetDocId() == pCurView->GetDocId() && pViewShell->getPart() == nPartForCurView)
        {
            LokChartHelper aChartHelper(pViewShell, bNegativeX);
            aChartHelper.PaintTile(rDevice, aTileRect);
        }
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
    rDevice.Pop();
}
 
bool LokChartHelper::postMouseEvent(int nType, int nX, int nY,
                                    int nCount, int nButtons, int nModifier,
                                    double fScaleX, double fScaleY)
{
    Point aMousePos(nX, nY);
    vcl::Window* pChartWindow = GetWindow();
    if (pChartWindow)
    {
        tools::Rectangle rChartBBox = GetChartBoundingBox();
        if (rChartBBox.Contains(aMousePos))
        {
            int nChartWinX = nX - rChartBBox.Left();
            int nChartWinY = nY - rChartBBox.Top();
 
            // chart window expects pixels, but the conversion factor
            // can depend on the client zoom
            Point aPos(nChartWinX * fScaleX, nChartWinY * fScaleY);
 
            LokMouseEventData aMouseEventData(nType, aPos, nCount, MouseEventModifiers::SIMPLECLICK,
                                              nButtons, nModifier);
            SfxLokHelper::postMouseEventAsync(pChartWindow, aMouseEventData);
 
            return true;
        }
    }
    return false;
}
 
bool LokChartHelper::setTextSelection(int nType, int nX, int nY)
{
    tools::Rectangle rChartBBox = GetChartBoundingBox();
    if (rChartBBox.Contains(Point(nX, nY)))
    {
        css::uno::Reference<css::frame::XDispatch> xDispatcher = GetXDispatcher();
        if (xDispatcher.is())
        {
            int nChartWinX = nX - rChartBBox.Left();
            int nChartWinY = nY - rChartBBox.Top();
 
            // no scale here the chart controller expects twips
            // that are converted to hmm
            util::URL aURL;
            aURL.Path = "LOKSetTextSelection";
            uno::Sequence< beans::PropertyValue > aArgs{
                comphelper::makePropertyValue({}, static_cast<sal_Int32>(nType)), // Why no name?
                comphelper::makePropertyValue({}, static_cast<sal_Int32>(nChartWinX)),
                comphelper::makePropertyValue({}, static_cast<sal_Int32>(nChartWinY))
            };
            xDispatcher->dispatch(aURL, aArgs);
        }
        return true;
    }
    return false;
}
 
bool LokChartHelper::setGraphicSelection(int nType, int nX, int nY,
                                         double fScaleX, double fScaleY)
{
    tools::Rectangle rChartBBox = GetChartBoundingBox();
    if (rChartBBox.Contains(Point(nX, nY)))
    {
        int nChartWinX = nX - rChartBBox.Left();
        int nChartWinY = nY - rChartBBox.Top();
 
        vcl::Window* pChartWindow = GetWindow();
 
        Point aPos(nChartWinX * fScaleX, nChartWinY * fScaleY);
        switch (nType)
        {
        case LOK_SETGRAPHICSELECTION_START:
            {
                MouseEvent aClickEvent(aPos, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT);
                pChartWindow->MouseButtonDown(aClickEvent);
                MouseEvent aMoveEvent(aPos, 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT);
                pChartWindow->MouseMove(aMoveEvent);
            }
            break;
        case LOK_SETGRAPHICSELECTION_END:
            {
                MouseEvent aMoveEvent(aPos, 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT);
                pChartWindow->MouseMove(aMoveEvent);
                MouseEvent aClickEvent(aPos, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT);
                pChartWindow->MouseButtonUp(aClickEvent);
            }
            break;
        default:
            assert(false);
            break;
        }
        return true;
    }
    return false;
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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