/* -*- 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 <svdata.hxx>
#include <brdwin.hxx>
#include <window.h>
#include <salframe.hxx>
#include <helpwin.hxx>

#include <comphelper/lok.hxx>
#include <sal/log.hxx>
#include <vcl/layout.hxx>
#include <vcl/svapp.hxx>
#include <vcl/wrkwin.hxx>
#include <vcl/event.hxx>
#include <vcl/toolbox.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/settings.hxx>
#include <vcl/IDialogRenderable.hxx>

class FloatingWindow::ImplData
{
public:
    ImplData();

    VclPtr<ToolBox> mpBox;
    AbsoluteScreenPixelRectangle maItemEdgeClipRect; // used to clip the common edge between a toolbar item and the border of this window
    Point maPos; // position of the floating window wrt. parent
    Point maLOKTwipsPos; ///< absolute position of the floating window in the document - in twips (for toplevel floating windows).
};

FloatingWindow::ImplData::ImplData()
{
    mpBox = nullptr;
}

const AbsoluteScreenPixelRectangle & FloatingWindow::ImplGetItemEdgeClipRect()
{
    return mpImplData->maItemEdgeClipRect;
}

void FloatingWindow::ImplInitFloating( vcl::Window* pParent, WinBits nStyle )
{
    mpImplData.reset(new ImplData);

    mpWindowImpl->mbFloatWin = true;
    mbInCleanUp = false;
    mbGrabFocus = false;

    SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL!");

    if (!pParent)
        pParent = ImplGetSVData()->maFrameData.mpAppWin;

    SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL and no AppWindow exists");

    // no Border, then we don't need a border window
    if (!nStyle)
    {
        mpWindowImpl->mbOverlapWin = true;
        nStyle |= WB_DIALOGCONTROL;
        ImplInit(pParent, nStyle, nullptr);
    }
    else
    {
        if (!(nStyle & WB_NODIALOGCONTROL))
            nStyle |= WB_DIALOGCONTROL;

        if (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE | WB_STANDALONE)
            && !(nStyle & WB_OWNERDRAWDECORATION))
        {
            WinBits nFloatWinStyle = nStyle;
            // #99154# floaters are not closeable by default anymore, eg fullscreen floater
            // nFloatWinStyle |= WB_CLOSEABLE;
            mpWindowImpl->mbFrame = true;
            mpWindowImpl->mbOverlapWin = true;
            ImplInit(pParent, nFloatWinStyle & ~WB_BORDER, nullptr);
        }
        else
        {
            VclPtr<ImplBorderWindow> pBorderWin;
            BorderWindowStyle nBorderStyle = BorderWindowStyle::Float;

            if (nStyle & WB_OWNERDRAWDECORATION)
                nBorderStyle |= BorderWindowStyle::Frame;
            else
                nBorderStyle |= BorderWindowStyle::Overlap;

            if ((nStyle & WB_SYSTEMWINDOW) && !(nStyle & (WB_MOVEABLE | WB_SIZEABLE)))
            {
                nBorderStyle |= BorderWindowStyle::Frame;
                nStyle |= WB_CLOSEABLE; // make undecorated floaters closeable
            }
            pBorderWin  = VclPtr<ImplBorderWindow>::Create(pParent, nStyle, nBorderStyle);
            ImplInit(pBorderWin, nStyle & ~WB_BORDER, nullptr);
            pBorderWin->mpWindowImpl->mpClientWindow = this;
            pBorderWin->GetBorder(mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder,
                                  mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder);
            pBorderWin->SetDisplayActive(true);
            mpWindowImpl->mpBorderWindow = pBorderWin;
            mpWindowImpl->mpRealParent = pParent;
        }
    }
    SetActivateMode( ActivateModeFlags::NONE );

    mpNextFloat             = nullptr;
    mpFirstPopupModeWin     = nullptr;
    mnPostId                = nullptr;
    mnTitle                 = (nStyle & (WB_MOVEABLE | WB_POPUP)) ? FloatWinTitleType::Normal : FloatWinTitleType::NONE;
    mnOldTitle              = mnTitle;
    mnPopupModeFlags        = FloatWinPopupFlags::NONE;
    mbInPopupMode           = false;
    mbPopupMode             = false;
    mbPopupModeCanceled     = false;
    mbPopupModeTearOff      = false;
    mbMouseDown             = false;

    ImplInitSettings();
}

void FloatingWindow::ImplInitSettings()
{
    const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();

    Color aColor;
    if (IsControlBackground())
        aColor = GetControlBackground();
    else if (Window::GetStyle() & WB_3DLOOK)
        aColor = rStyleSettings.GetFaceColor();
    else
        aColor = rStyleSettings.GetWindowColor();
    SetBackground(aColor);
}

FloatingWindow::FloatingWindow(vcl::Window* pParent, WinBits nStyle) :
    SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle")
{
    ImplInitFloating(pParent, nStyle);
}

FloatingWindow::FloatingWindow(vcl::Window* pParent, const OUString& rID, const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame)
    : SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle")
    , mpNextFloat(nullptr)
    , mpFirstPopupModeWin(nullptr)
    , mnPostId(nullptr)
    , mnPopupModeFlags(FloatWinPopupFlags::NONE)
    , mnTitle(FloatWinTitleType::Unknown)
    , mnOldTitle(FloatWinTitleType::Unknown)
    , mbInPopupMode(false)
    , mbPopupMode(false)
    , mbPopupModeCanceled(false)
    , mbPopupModeTearOff(false)
    , mbMouseDown(false)
    , mbGrabFocus(false)
    , mbInCleanUp(false)
{
    loadUI(pParent, rID, rUIXMLDescription, rFrame);
}

void FloatingWindow::ImplDeferredInit(vcl::Window* pParent, WinBits nBits)
{
    ImplInitFloating(pParent, nBits);
}

void FloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext)
{
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();

    Color aColor;
    if (Window::GetStyle() & WB_3DLOOK)
        aColor = rStyleSettings.GetFaceColor();
    else
        aColor = rStyleSettings.GetWindowColor();

    ApplyControlBackground(rRenderContext, aColor);
}

FloatingWindow::~FloatingWindow()
{
    disposeOnce();
    assert (!mnPostId);
}

void FloatingWindow::dispose()
{
    ReleaseLOKNotifier();

    if (mpImplData)
    {
        if( mbPopupModeCanceled )
            // indicates that ESC key was pressed
            // will be handled in Window::ImplGrabFocus()
            SetDialogControlFlags( GetDialogControlFlags() | DialogControlFlags::FloatWinPopupModeEndCancel );

        if ( IsInPopupMode() )
            EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll | FloatWinPopupEndFlags::DontCallHdl );

        if ( mnPostId )
            Application::RemoveUserEvent( mnPostId );
        mnPostId = nullptr;
    }

    mpImplData.reset();

    mpNextFloat.reset();
    mpFirstPopupModeWin.reset();
    mxPrevFocusWin.reset();
    SystemWindow::dispose();
}

Point FloatingWindow::ImplCalcPos(vcl::Window* pWindow,
                                  const tools::Rectangle& rRect, FloatWinPopupFlags nFlags,
                                  sal_uInt16& rArrangeIndex, Point* pLOKTwipsPos)
{
    // get window position
    AbsoluteScreenPixelPoint aPos;
    Size        aSize = ::isLayoutEnabled(pWindow) ? pWindow->get_preferred_size() : pWindow->GetSizePixel();
    AbsoluteScreenPixelRectangle aScreenRect = pWindow->ImplGetFrameWindow()->GetDesktopRectPixel();
    FloatingWindow *pFloatingWindow = dynamic_cast<FloatingWindow*>( pWindow );

    // convert...
    vcl::Window* pW = pWindow;
    if ( pW->mpWindowImpl->mpRealParent )
        pW = pW->mpWindowImpl->mpRealParent;

    tools::Rectangle normRect( rRect );  // rRect is already relative to top-level window
    normRect.SetPos( pW->ScreenToOutputPixel( normRect.TopLeft() ) );

    bool bRTL = AllSettings::GetLayoutRTL();

    AbsoluteScreenPixelRectangle devRect(  pW->OutputToAbsoluteScreenPixel( normRect.TopLeft() ),
                        pW->OutputToAbsoluteScreenPixel( normRect.BottomRight() ) );

    AbsoluteScreenPixelRectangle devRectRTL( devRect );
    if( bRTL )
        // create a rect that can be compared to desktop coordinates
        devRectRTL = pW->ImplOutputToUnmirroredAbsoluteScreenPixel( normRect );
    if( Application::GetScreenCount() > 1 )
        aScreenRect = Application::GetScreenPosSizePixel(
            Application::GetBestScreen( bRTL ? devRectRTL : devRect ) );

    FloatWinPopupFlags nArrangeAry[5];
    sal_uInt16 nArrangeAttempts = 5;
    AbsoluteScreenPixelPoint e1,e2;  // the common edge between the item rect and the floating window

    if ( nFlags & FloatWinPopupFlags::Left )
    {
        nArrangeAry[0]  = FloatWinPopupFlags::Left;
        nArrangeAry[1]  = FloatWinPopupFlags::Right;
        nArrangeAry[2]  = FloatWinPopupFlags::Up;
        nArrangeAry[3]  = FloatWinPopupFlags::Down;
        nArrangeAry[4]  = FloatWinPopupFlags::Left;
    }
    else if ( nFlags & FloatWinPopupFlags::Right )
    {
        nArrangeAry[0]  = FloatWinPopupFlags::Right;
        nArrangeAry[1]  = FloatWinPopupFlags::Left;
        nArrangeAry[2]  = FloatWinPopupFlags::Up;
        nArrangeAry[3]  = FloatWinPopupFlags::Down;
        nArrangeAry[4]  = FloatWinPopupFlags::Right;
    }
    else if ( nFlags & FloatWinPopupFlags::Up )
    {
        nArrangeAry[0]  = FloatWinPopupFlags::Up;
        nArrangeAry[1]  = FloatWinPopupFlags::Down;
        if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
        {
            nArrangeAry[2]  = FloatWinPopupFlags::Up;
            nArrangeAttempts = 3;
        }
        else
        {
            nArrangeAry[2]  = FloatWinPopupFlags::Right;
            nArrangeAry[3]  = FloatWinPopupFlags::Left;
            nArrangeAry[4]  = FloatWinPopupFlags::Up;
        }
    }
    else
    {
        nArrangeAry[0]  = FloatWinPopupFlags::Down;
        nArrangeAry[1]  = FloatWinPopupFlags::Up;
        if (nFlags & FloatWinPopupFlags::NoHorzPlacement)
        {
            nArrangeAry[2]  = FloatWinPopupFlags::Down;
            nArrangeAttempts = 3;
        }
        else
        {
            nArrangeAry[2]  = FloatWinPopupFlags::Right;
            nArrangeAry[3]  = FloatWinPopupFlags::Left;
            nArrangeAry[4]  = FloatWinPopupFlags::Down;
        }
    }

    sal_uInt16 nArrangeIndex = 0;
    const bool bLOKActive = comphelper::LibreOfficeKit::isActive();

    for ( ; nArrangeIndex < nArrangeAttempts; nArrangeIndex++ )
    {
        bool bBreak = true;
        switch ( nArrangeAry[nArrangeIndex] )
        {

            case FloatWinPopupFlags::Left:
                aPos.setX( devRect.Left()-aSize.Width()+1 );
                aPos.setY( devRect.Top() );
                aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
                if( bRTL )
                {
                    if( (devRectRTL.Right()+aSize.Width()) > aScreenRect.Right() )
                        bBreak = false;
                }
                else
                {
                    if ( aPos.X() < aScreenRect.Left() )
                        bBreak = false;
                }
                if (bBreak || bLOKActive)
                {
                    e1 = devRect.TopLeft();
                    e2 = devRect.BottomLeft();
                    // set non-zero width
                    e2.AdjustX( 1 );
                    // don't clip corners
                    e1.AdjustY( 1 );
                    e2.AdjustY( -1 );
                }
                break;
            case FloatWinPopupFlags::Right:
                aPos     = devRect.TopRight();
                aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) );
                if( bRTL )
                {
                    if( (devRectRTL.Left() - aSize.Width()) < aScreenRect.Left() )
                        bBreak = false;
                }
                else
                {
                    if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
                        bBreak = false;
                }
                if (bBreak || bLOKActive)
                {
                    e1 = devRect.TopRight();
                    e2 = devRect.BottomRight();
                    // set non-zero width
                    e2.AdjustX( 1 );
                    // don't clip corners
                    e1.AdjustY( 1 );
                    e2.AdjustY( -1 );
                }
                break;
            case FloatWinPopupFlags::Up:
                aPos.setX( devRect.Left() );
                aPos.setY( devRect.Top()-aSize.Height()+1 );
                if ( aPos.Y() < aScreenRect.Top() )
                    bBreak = false;
                if (bBreak || bLOKActive)
                {
                    e1 = devRect.TopLeft();
                    e2 = devRect.TopRight();
                    // set non-zero height
                    e2.AdjustY( 1 );
                    // don't clip corners
                    e1.AdjustX( 1 );
                    e2.AdjustX( -1 );
                }
                break;
            case FloatWinPopupFlags::Down:
                aPos = devRect.BottomLeft();
                if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() )
                    bBreak = false;
                if (bBreak || bLOKActive)
                {
                    e1 = devRect.BottomLeft();
                    e2 = devRect.BottomRight();
                    // set non-zero height
                    e2.AdjustY( 1 );
                    // don't clip corners
                    e1.AdjustX( 1 );
                    e2.AdjustX( -1 );
                }
                break;
            default: break;
        }

        // no further adjustment for LibreOfficeKit
        if (bLOKActive)
            break;

        // set bBreak true on last attempt
        if (nArrangeIndex + 1 == nArrangeAttempts)
            bBreak = true;

        // adjust if necessary
        if (bBreak)
        {
            if (aPos.Y() + aSize.Height() > aScreenRect.Bottom())
            {
                aPos.setY(devRect.Bottom() - aSize.Height() + 1);
                if (aPos.Y() < aScreenRect.Top())
                    aPos.setY(aScreenRect.Top());
                // move to the right or left of the parent if possible
                if (devRect.Right() + 4 + aSize.Width() < aScreenRect.Right())
                    aPos.setX(devRect.Right() + 4);
                else if (devRect.Left() - 4 - aSize.Width() > aScreenRect.Left())
                    aPos.setX(devRect.Left() - 4 - aSize.Width());
            }
            else
            {
                if( bRTL )
                {
                    if( devRectRTL.Right()-aSize.Width()+1 < aScreenRect.Left() )
                        aPos.AdjustX( -(aScreenRect.Left() - devRectRTL.Right() + aSize.Width() - 1) );
                }
                else if ( aPos.X()+aSize.Width() > aScreenRect.Right() )
                {
                    aPos.setX( devRect.Right()-aSize.Width()+1 );
                    if ( aPos.X() < aScreenRect.Left() )
                        aPos.setX( aScreenRect.Left() );
                }
            }
        }

        if ( bBreak )
            break;
    }

    rArrangeIndex = nArrangeIndex;

    Point aPosOut = pW->AbsoluteScreenToOutputPixel( aPos );

    // store a cliprect that can be used to clip the common edge of the itemrect and the floating window
    if( pFloatingWindow && pFloatingWindow->mpImplData->mpBox )
    {
        pFloatingWindow->mpImplData->maItemEdgeClipRect =
            AbsoluteScreenPixelRectangle( e1, e2 );
    }

    if (bLOKActive && pLOKTwipsPos)
    {
        if (pW->IsMapModeEnabled() || pW->GetMapMode().GetMapUnit() == MapUnit::MapPixel)
        {
            // if we use pW->LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip)),
            // for pixel conversions when map mode is not enabled, we get
            // a 20 twips per pixel conversion since LogicToLogic uses
            // a fixed 72 dpi value, instead of a correctly computed output
            // device dpi or at least the most commonly used 96 dpi value;
            // and anyway the following is what we already do in
            // ScGridWindow::LogicInvalidate when map mode is not enabled.

            *pLOKTwipsPos = pW->PixelToLogic(aPosOut, MapMode(MapUnit::MapTwip));
        }
        else
        {
            *pLOKTwipsPos = OutputDevice::LogicToLogic(aPosOut, pW->GetMapMode(), MapMode(MapUnit::MapTwip));
        }
    }

    // caller expects coordinates relative to top-level win
    return pW->OutputToScreenPixel( aPosOut );
}

AbsoluteScreenPixelPoint FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const Point& rPos)
{
    const OutputDevice *pWindowOutDev = pReference->GetOutDev();

    // compare coordinates in absolute screen coordinates
    if ( pWindowOutDev->HasMirroredGraphics() && !comphelper::LibreOfficeKit::isActive() )
    {
        Point aTmp(rPos);
        if(!pReference->IsRTLEnabled() )
            pWindowOutDev->ReMirror( aTmp );

        tools::Rectangle aRect( pReference->ScreenToOutputPixel(aTmp), Size(1,1) ) ;
        aRect = tools::Rectangle(pReference->ImplOutputToUnmirroredAbsoluteScreenPixel( aRect ));
        return AbsoluteScreenPixelPoint(aRect.TopLeft());
    }
    else
        return pReference->OutputToAbsoluteScreenPixel(
            pReference->ScreenToOutputPixel(rPos) );
}

AbsoluteScreenPixelRectangle FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const tools::Rectangle& rRect)
{
    AbsoluteScreenPixelRectangle aFloatRect;

    const OutputDevice *pParentWinOutDev = pReference->GetOutDev();

    // compare coordinates in absolute screen coordinates
    // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509
    if( pParentWinOutDev->HasMirroredGraphics() && !comphelper::LibreOfficeKit::isActive() )
    {
        tools::Rectangle aScreenRect(rRect);
        if(!pReference->IsRTLEnabled() )
            pParentWinOutDev->ReMirror(aScreenRect);

        tools::Rectangle aOutRect(pReference->ScreenToOutputPixel(aScreenRect.TopLeft()), aScreenRect.GetSize());
        aFloatRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel(aOutRect);
    }
    else
        aFloatRect = AbsoluteScreenPixelRectangle(
            pReference->OutputToAbsoluteScreenPixel(pReference->ScreenToOutputPixel(rRect.TopLeft())),
            rRect.GetSize());

    return aFloatRect;
}

tools::Rectangle FloatingWindow::ImplConvertToRelPos(vcl::Window* pReference, const AbsoluteScreenPixelRectangle& rRect)
{
    tools::Rectangle aFloatRect;

    const OutputDevice *pParentWinOutDev = pReference->GetOutDev();

    // compare coordinates in absolute screen coordinates
    // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509
    if( pParentWinOutDev->HasMirroredGraphics()  )
    {
        aFloatRect = pReference->ImplUnmirroredAbsoluteScreenToOutputPixel(rRect);
        aFloatRect.SetPos(pReference->OutputToScreenPixel(aFloatRect.TopLeft()));

        if(!pReference->IsRTLEnabled() )
            pParentWinOutDev->ReMirror(aFloatRect);
    }
    else
        aFloatRect = tools::Rectangle(pReference->OutputToScreenPixel(pReference->AbsoluteScreenToOutputPixel(rRect.TopLeft())),
                                      rRect.GetSize());

    return aFloatRect;
}

FloatingWindow* FloatingWindow::ImplFloatHitTest( vcl::Window* pReference, const Point& rPos, bool& rbHitTestInsideRect )
{
    FloatingWindow* pWin = this;
    rbHitTestInsideRect = false;

    AbsoluteScreenPixelPoint aAbsolute(FloatingWindow::ImplConvertToAbsPos(pReference, rPos));

    do
    {
        // compute the floating window's size in absolute screen coordinates

        // use the border window to have the exact position
        vcl::Window *pBorderWin = pWin->GetWindow( GetWindowType::Border );
        if (!pBorderWin)
            break;

        // the top-left corner in output coordinates ie (0,0)
        AbsoluteScreenPixelRectangle devRect( pBorderWin->ImplOutputToUnmirroredAbsoluteScreenPixel( tools::Rectangle( Point(), pBorderWin->GetSizePixel()) ) ) ;
        if ( devRect.Contains( aAbsolute ) )
        {
            // inside the window
            return pWin;
        }

        // test, if mouse is in rectangle, (this is typically the rect of the active
        // toolbox item or similar)
        // note: maFloatRect is set in FloatingWindow::StartPopupMode() and
        //       is already in absolute device coordinates
        if ( pWin->maFloatRect.Contains( aAbsolute ) )
        {
            rbHitTestInsideRect = true;
            return pWin;
        }

        pWin = pWin->mpNextFloat;
    }
    while ( pWin );

    return nullptr;
}

FloatingWindow* FloatingWindow::ImplFindLastLevelFloat()
{
    FloatingWindow* pWin = this;
    FloatingWindow* pLastFoundWin = pWin;

    do
    {
        if ( pWin->GetPopupModeFlags() & FloatWinPopupFlags::NewLevel )
            pLastFoundWin = pWin;

        pWin = pWin->mpNextFloat;
    }
    while ( pWin );

    return pLastFoundWin;
}

bool FloatingWindow::ImplIsFloatPopupModeWindow( const vcl::Window* pWindow )
{
    FloatingWindow* pWin = this;

    do
    {
        if ( pWin->mpFirstPopupModeWin == pWindow )
            return true;

        pWin = pWin->mpNextFloat;
    }
    while ( pWin );

    return false;
}

IMPL_LINK_NOARG(FloatingWindow, ImplEndPopupModeHdl, void*, void)
{
    VclPtr<FloatingWindow> pThis(this);
    mnPostId            = nullptr;
    mnPopupModeFlags    = FloatWinPopupFlags::NONE;
    mbPopupMode         = false;
    PopupModeEnd();
}

bool FloatingWindow::EventNotify( NotifyEvent& rNEvt )
{
    // call Base Class first for tab control
    bool bRet = SystemWindow::EventNotify( rNEvt );
    if ( !bRet )
    {
        if ( rNEvt.GetType() == NotifyEventType::KEYINPUT )
        {
            const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
            vcl::KeyCode    aKeyCode = pKEvt->GetKeyCode();
            sal_uInt16      nKeyCode = aKeyCode.GetCode();

            if ( (nKeyCode == KEY_ESCAPE) && (GetStyle() & WB_CLOSEABLE) )
            {
                Close();
                return true;
            }
        }
    }

    return bRet;
}

void FloatingWindow::PixelInvalidate(const tools::Rectangle* /*pRectangle*/)
{
    if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier())
    {
        const tools::Rectangle aRect(Point(0,0), Size(GetSizePixel().Width()+1, GetSizePixel().Height()+1));
        std::vector<vcl::LOKPayloadItem> aPayload
        {
            std::make_pair("rectangle"_ostr, aRect.toString())
        };
        const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
        pNotifier->notifyWindow(GetLOKWindowId(), u"invalidate"_ustr, aPayload);
    }
}

void FloatingWindow::StateChanged( StateChangedType nType )
{
    if (nType == StateChangedType::InitShow)
    {
        DoInitialLayout();
    }

    SystemWindow::StateChanged( nType );

    VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
    if (pParent)
    {
        if (nType == StateChangedType::InitShow)
        {
            std::vector<vcl::LOKPayloadItem> aItems;
            if (pParent == this)
            {
                // we are a toplevel window, let's so far pretend to be a
                // dialog - but maybe we'll need a separate type for this
                // later
                if (mbInPopupMode)
                    aItems.emplace_back("type", "dropdown");
                else
                    aItems.emplace_back("type", "dialog");
                aItems.emplace_back("position", mpImplData->maLOKTwipsPos.toString()); // twips
            }
            else
            {
                SetLOKNotifier(pParent->GetLOKNotifier());
                if (dynamic_cast<HelpTextWindow*>(this))
                    aItems.emplace_back("type", "tooltip");
                else
                    aItems.emplace_back("type", "child");

                aItems.emplace_back("parentId", OString::number(pParent->GetLOKWindowId()));
                if (mbInPopupMode)
                    aItems.emplace_back("position", mpImplData->maPos.toString()); // pixels
                else // mpImplData->maPos is not set
                    aItems.emplace_back("position", GetPosPixel().toString());

            }
            aItems.emplace_back("size", GetSizePixel().toString());
            GetLOKNotifier()->notifyWindow(GetLOKWindowId(), u"created"_ustr, aItems);
        }
        else if (!IsVisible() && nType == StateChangedType::Visible)
        {
            if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier())
            {
                pNotifier->notifyWindow(GetLOKWindowId(), u"close"_ustr);
                ReleaseLOKNotifier();
            }
        }
    }

    if ( nType == StateChangedType::ControlBackground )
    {
        ImplInitSettings();
        Invalidate();
    }
}

void FloatingWindow::DataChanged( const DataChangedEvent& rDCEvt )
{
    SystemWindow::DataChanged( rDCEvt );

    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
         (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
    {
        ImplInitSettings();
        Invalidate();
    }
}

void FloatingWindow::ImplCallPopupModeEnd()
{
    // PopupMode is finished
    mbInPopupMode = false;

    // call Handler asynchronously.
    if ( mpImplData && !mnPostId )
        mnPostId = Application::PostUserEvent(LINK(this, FloatingWindow, ImplEndPopupModeHdl));
}

void FloatingWindow::PopupModeEnd()
{
    maPopupModeEndHdl.Call( this );
}

void FloatingWindow::SetTitleType( FloatWinTitleType nTitle )
{
    if ( (mnTitle == nTitle) || !mpWindowImpl->mpBorderWindow )
        return;

    mnTitle = nTitle;
    Size aOutSize = GetOutputSizePixel();
    BorderWindowTitleType nTitleStyle;
    if ( nTitle == FloatWinTitleType::Normal )
        nTitleStyle = BorderWindowTitleType::Small;
    else if ( nTitle == FloatWinTitleType::TearOff )
        nTitleStyle = BorderWindowTitleType::Tearoff;
    else if ( nTitle == FloatWinTitleType::Popup )
        nTitleStyle = BorderWindowTitleType::Popup;
    else // nTitle == FloatWinTitleType::NONE
        nTitleStyle = BorderWindowTitleType::NONE;
    static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetTitleType( nTitleStyle, aOutSize );
    static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder );
}

void FloatingWindow::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags )
{
    // remove title
    mnOldTitle = mnTitle;
    if ( ( mpWindowImpl->mnStyle & WB_POPUP ) && !GetText().isEmpty() )
        SetTitleType( FloatWinTitleType::Popup );
    else if ( nFlags & FloatWinPopupFlags::AllowTearOff )
        SetTitleType( FloatWinTitleType::TearOff );
    else
        SetTitleType( FloatWinTitleType::NONE );

    // avoid close on focus change for decorated floating windows only
    if( mpWindowImpl->mbFrame && (GetStyle() & WB_MOVEABLE) )
        nFlags |= FloatWinPopupFlags::NoAppFocusClose;

    // compute window position according to flags and arrangement
    sal_uInt16 nArrangeIndex;
    DoInitialLayout();
    mpImplData->maPos = ImplCalcPos(this, rRect, nFlags, nArrangeIndex, &mpImplData->maLOKTwipsPos);
    SetPosPixel( mpImplData->maPos );
    ImplGetFrame()->PositionByToolkit(rRect, nFlags);

    // set data and display window
    // convert maFloatRect to absolute device coordinates
    // so they can be compared across different frames
    // !!! rRect is expected to be in screen coordinates of the parent frame window !!!
    maFloatRect = FloatingWindow::ImplConvertToAbsPos(GetParent(), rRect);

    maFloatRect.AdjustLeft( -2 );
    maFloatRect.AdjustTop( -2 );
    maFloatRect.AdjustRight(2 );
    maFloatRect.AdjustBottom(2 );
    mnPopupModeFlags        = nFlags;
    mbInPopupMode           = true;
    mbPopupMode             = true;
    mbPopupModeCanceled     = false;
    mbPopupModeTearOff      = false;
    mbMouseDown             = false;

    // add FloatingWindow to list of windows that are in popup mode
    ImplSVData* pSVData = ImplGetSVData();
    mpNextFloat = pSVData->mpWinData->mpFirstFloat;
    pSVData->mpWinData->mpFirstFloat = this;
    bool bGrabFocus(nFlags & FloatWinPopupFlags::GrabFocus);
    if (bGrabFocus)
    {
        // force key input even without focus (useful for menus)
        mbGrabFocus = true;
        mxPrevFocusWin = Window::SaveFocus();
        mpWindowImpl->mpFrameData->mbHasFocus = true;
    }
    Show( true, ShowFlags::NoActivate );
    if (bGrabFocus)
        GrabFocus();
}

void FloatingWindow::StartPopupMode( ToolBox* pBox, FloatWinPopupFlags nFlags )
{
    mpImplData->mpBox = pBox;

    // get selected button
    ToolBoxItemId nItemId = pBox->GetDownItemId();

    if ( nItemId )
        pBox->ImplFloatControl( true, this );

    // retrieve some data from the ToolBox
    tools::Rectangle aRect = nItemId ? pBox->GetItemRect( nItemId ) : pBox->GetOverflowRect();

    // convert to parent's screen coordinates
    mpImplData->maPos = GetParent()->OutputToScreenPixel( GetParent()->AbsoluteScreenToOutputPixel( pBox->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) ) );
    aRect.SetPos( mpImplData->maPos );

    nFlags |=
        FloatWinPopupFlags::AllMouseButtonClose |
        FloatWinPopupFlags::NoMouseUpClose;

    // set Flags for positioning
    if ( !(nFlags & (FloatWinPopupFlags::Down | FloatWinPopupFlags::Up |
                     FloatWinPopupFlags::Left | FloatWinPopupFlags::Right)) )
    {
         if ( pBox->IsHorizontal() )
             nFlags |= FloatWinPopupFlags::Down;
         else
             nFlags |= FloatWinPopupFlags::Right;
    }

    // start FloatingMode
    StartPopupMode( aRect, nFlags );
}

void FloatingWindow::ImplEndPopupMode( FloatWinPopupEndFlags nFlags, const VclPtr<vcl::Window>& xFocusId )
{
    if ( !mbInPopupMode )
        return;

    ImplSVData* pSVData = ImplGetSVData();

    mbInCleanUp = true; // prevent killing this window due to focus change while working with it

    if (!(nFlags & FloatWinPopupEndFlags::NoCloseChildren))
    {
        // stop the PopupMode also for all PopupMode windows created after us
        std::vector<VclPtr<FloatingWindow>> aCancelFloats;
        // stop the PopupMode also for all following PopupMode windows
        for (auto pFloat = pSVData->mpWinData->mpFirstFloat;
             pFloat != nullptr && pFloat != this;
             pFloat = pFloat->mpNextFloat)
            aCancelFloats.push_back(pFloat);
        for (auto & it : aCancelFloats)
            it->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::NoCloseChildren);
    }

    // delete window from the list
    pSVData->mpWinData->mpFirstFloat = mpNextFloat;
    mpNextFloat = nullptr;

    FloatWinPopupFlags nPopupModeFlags = mnPopupModeFlags;
    mbPopupModeTearOff = nFlags & FloatWinPopupEndFlags::TearOff &&
                         nPopupModeFlags & FloatWinPopupFlags::AllowTearOff;

    // hide window again if it was not deleted
    if (!mbPopupModeTearOff)
        Show( false, ShowFlags::NoFocusChange );

    if (HasChildPathFocus() && xFocusId != nullptr)
    {
        // restore focus to previous focus window if we still have the focus
        Window::EndSaveFocus(xFocusId);
    }
    else if ( pSVData->mpWinData->mpFocusWin && pSVData->mpWinData->mpFirstFloat &&
              ImplIsWindowOrChild( pSVData->mpWinData->mpFocusWin ) )
    {
        // maybe pass focus on to a suitable FloatingWindow
        pSVData->mpWinData->mpFirstFloat->GrabFocus();
    }

    mbPopupModeCanceled = bool(nFlags & FloatWinPopupEndFlags::Cancel);

    // redo title
    SetTitleType( mnOldTitle );

    // set ToolBox again to normal
    if (mpImplData && mpImplData->mpBox)
    {
        mpImplData->mpBox->ImplFloatControl( false, this );
        // if the parent ToolBox is in popup mode, it should be closed too.
        if ( GetDockingManager()->IsInPopupMode( mpImplData->mpBox ) )
            nFlags |= FloatWinPopupEndFlags::CloseAll;

        mpImplData->mpBox = nullptr;
    }

    // call PopupModeEnd-Handler depending on parameter
    if ( !(nFlags & FloatWinPopupEndFlags::DontCallHdl) )
        ImplCallPopupModeEnd();

    // close all other windows depending on parameter
    if ( nFlags & FloatWinPopupEndFlags::CloseAll )
    {
        if ( !(nPopupModeFlags & FloatWinPopupFlags::NewLevel) )
        {
            if (pSVData->mpWinData->mpFirstFloat)
            {
                FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat();
                pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll );
            }
        }
    }

    mbInCleanUp = false;
}

void FloatingWindow::EndPopupMode( FloatWinPopupEndFlags nFlags )
{
    ImplEndPopupMode(nFlags, mxPrevFocusWin);
}

void FloatingWindow::AddPopupModeWindow(vcl::Window* pWindow)
{
    // !!! up-to-now only 1 window and not yet a list
    mpFirstPopupModeWin = pWindow;
}

bool SystemWindow::UpdatePositionData()
{
    // tdf#164337 don't update position data when waiting for a system resize
    // When entering and exiting LibreOffice's internal full screen mode,
    // updating position data causes the "exit full screen" floating
    // toolbar to migrate after cycle.
    if (mpWindowImpl->mbWaitSystemResize)
        return false;

    auto pWin = ImplGetParent();
    if (pWin)
    {
        // Simulate Move, so the relative position of the floating window will be recalculated
        pWin->ImplCallMove();
        return true;
    }

    return false;
}

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