/* -*- 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 <comphelper/lok.hxx>
#include <sal/log.hxx>
 
#include <headless/svpframe.hxx>
#include <headless/svpinst.hxx>
#ifndef IOS
#include <headless/svpgdi.hxx>
#endif
#include <salsys.hxx>
 
#include <basegfx/vector/b2ivector.hxx>
 
#ifndef IOS
#include <cairo.h>
#endif
 
SvpSalFrame* SvpSalFrame::s_pFocusFrame = nullptr;
 
#ifdef IOS
#define SvpSalGraphics AquaSalGraphics
#endif
 
SvpSalFrame::SvpSalFrame( SvpSalInstance* pInstance,
                          SalFrame* pParent,
                          SalFrameStyleFlags nSalFrameStyle ) :
    m_pInstance( pInstance ),
    m_pParent( static_cast<SvpSalFrame*>(pParent) ),
    m_nStyle( nSalFrameStyle ),
    m_bVisible( false ),
#ifndef IOS
    m_pSurface( nullptr ),
#endif
    m_nMinWidth( 0 ),
    m_nMinHeight( 0 ),
    m_nMaxWidth( 0 ),
    m_nMaxHeight( 0 )
{
#if !defined(IOS) && !defined(MACOSX) && !defined(ANDROID)
    m_aSystemChildData.pSalFrame    = this;
#endif
 
    if( m_pParent )
        m_pParent->m_aChildren.push_back( this );
 
    if( m_pInstance )
        m_pInstance->registerFrame( this );
 
    SetPosSize( 0, 0, 800, 600, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}
 
SvpSalFrame::~SvpSalFrame()
{
    if( m_pInstance )
        m_pInstance->deregisterFrame( this );
 
    std::vector<SvpSalFrame*> Children = m_aChildren;
    for( auto& rChild : Children )
        rChild->SetParent( m_pParent );
    if( m_pParent )
        std::erase(m_pParent->m_aChildren, this);
 
    if( s_pFocusFrame == this )
    {
        s_pFocusFrame = nullptr;
        // call directly here, else an event for a destroyed frame would be dispatched
        CallCallback( SalEvent::LoseFocus, nullptr );
        // if the handler has not set a new focus frame
        // pass focus to another frame, preferably a document style window
        if( s_pFocusFrame == nullptr )
        {
            for (auto pSalFrame : m_pInstance->getFrames() )
            {
                SvpSalFrame* pFrame = static_cast<SvpSalFrame*>( pSalFrame );
                if( pFrame->m_bVisible        &&
                    pFrame->m_pParent == nullptr &&
                    (pFrame->m_nStyle & (SalFrameStyleFlags::MOVEABLE |
                                         SalFrameStyleFlags::SIZEABLE |
                                         SalFrameStyleFlags::CLOSEABLE) )
                    )
                {
                    pFrame->GetFocus();
                    break;
                }
            }
        }
    }
#ifndef IOS
    if (m_pSurface)
        cairo_surface_destroy(m_pSurface);
#endif
}
 
void SvpSalFrame::GetFocus()
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if( s_pFocusFrame == this )
        return;
    // FIXME: return if !m_bVisible
    // That's IMHO why CppunitTest_sd_tiledrendering crashes non-headless
 
    if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE )
    {
        if( s_pFocusFrame )
            s_pFocusFrame->LoseFocus();
        s_pFocusFrame = this;
        m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus );
    }
}
 
void SvpSalFrame::LoseFocus()
{
    if( s_pFocusFrame == this )
    {
        m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus );
        s_pFocusFrame = nullptr;
    }
}
 
basegfx::B2IVector SvpSalFrame::GetSurfaceFrameSize() const
{
    basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
    if( aFrameSize.getX() == 0 )
        aFrameSize.setX( 1 );
    if( aFrameSize.getY() == 0 )
        aFrameSize.setY( 1 );
    // Creating backing surfaces for invisible windows costs a big chunk of RAM.
    if (Application::IsHeadlessModeEnabled())
         aFrameSize = basegfx::B2IVector( 1, 1 );
    return aFrameSize;
}
 
SalGraphics* SvpSalFrame::AcquireGraphics()
{
    SvpSalGraphics* pGraphics = new SvpSalGraphics();
#ifndef IOS
    pGraphics->setSurface(m_pSurface, GetSurfaceFrameSize());
#endif
    m_aGraphics.push_back( pGraphics );
    return pGraphics;
}
 
void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
{
    SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics);
    std::erase(m_aGraphics, pSvpGraphics);
    delete pSvpGraphics;
}
 
bool SvpSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
{
    m_pInstance->PostEvent( this, pData.release(), SalEvent::UserEvent );
    return true;
}
 
void SvpSalFrame::PostPaint() const
{
    if( m_bVisible )
    {
        SalPaintEvent aPEvt(0, 0, maGeometry.width(), maGeometry.height());
        aPEvt.mbImmediateUpdate = false;
        CallCallback( SalEvent::Paint, &aPEvt );
    }
}
 
void SvpSalFrame::SetTitle(const OUString& sTitle)
{
    m_sTitle = sTitle;
}
 
void SvpSalFrame::SetIcon( sal_uInt16 )
{
}
 
void SvpSalFrame::SetMenu( SalMenu* )
{
}
 
void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle )
{
}
 
void SvpSalFrame::Show( bool bVisible, bool bNoActivate )
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if (bVisible == m_bVisible)
    {
        if (m_bVisible && !bNoActivate)
            GetFocus();
        return;
    }
 
    if (bVisible)
    {
        m_bVisible = true;
        m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
        if( ! bNoActivate )
            GetFocus();
    }
    else
    {
        m_bVisible = false;
        LoseFocus();
    }
}
 
void SvpSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
{
    m_nMinWidth = nWidth;
    m_nMinHeight = nHeight;
}
 
void SvpSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
{
    m_nMaxWidth = nWidth;
    m_nMaxHeight = nHeight;
}
 
void SvpSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
{
    if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
        maGeometry.setX(nX);
    if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 )
        maGeometry.setY(nY);
    if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
    {
        maGeometry.setWidth(nWidth);
        if (m_nMaxWidth > 0 && maGeometry.width() > m_nMaxWidth)
            maGeometry.setWidth(m_nMaxWidth);
        if (m_nMinWidth > 0 && maGeometry.width() < m_nMinWidth)
            maGeometry.setWidth(m_nMinWidth);
    }
    if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 )
    {
        maGeometry.setHeight(nHeight);
        if (m_nMaxHeight > 0 && maGeometry.height() > m_nMaxHeight)
            maGeometry.setHeight(m_nMaxHeight);
        if (m_nMinHeight > 0 && maGeometry.height() < m_nMinHeight)
            maGeometry.setHeight(m_nMinHeight);
    }
#ifndef IOS
    basegfx::B2IVector aFrameSize = GetSurfaceFrameSize();
    if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() ||
                       cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() )
    {
        if (m_pSurface)
            cairo_surface_destroy(m_pSurface);
 
        m_pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
                                                aFrameSize.getX(),
                                                aFrameSize.getY());
 
        // update device in existing graphics
        for (auto const& graphic : m_aGraphics)
        {
             graphic->setSurface(m_pSurface, aFrameSize);
        }
    }
    if( m_bVisible )
        m_pInstance->PostEvent( this, nullptr, SalEvent::Resize );
#endif
}
 
void SvpSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
{
    rWidth = maGeometry.width();
    rHeight = maGeometry.height();
}
 
void SvpSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
{
    rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ),
                       AbsoluteScreenPixelSize( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) );
}
 
SalFrame* SvpSalFrame::GetParent() const
{
    return m_pParent;
}
 
void SvpSalFrame::SetWindowState(const vcl::WindowData *pState)
{
    if (pState == nullptr)
        return;
 
    // Request for position or size change
    if (!(pState->mask() & vcl::WindowDataMask::PosSize))
        return;
 
    tools::Long nX = maGeometry.x();
    tools::Long nY = maGeometry.y();
    tools::Long nWidth = maGeometry.width();
    tools::Long nHeight = maGeometry.height();
 
    // change requested properties
    if (pState->mask() & vcl::WindowDataMask::X)
        nX = pState->x();
    if (pState->mask() & vcl::WindowDataMask::Y)
        nY = pState->y();
    if (pState->mask() & vcl::WindowDataMask::Width)
        nWidth = pState->width();
    if (pState->mask() & vcl::WindowDataMask::Height)
        nHeight = pState->height();
 
    SetPosSize( nX, nY, nWidth, nHeight,
                SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}
 
bool SvpSalFrame::GetWindowState(vcl::WindowData* pState)
{
    pState->setPosSize(maGeometry.posSize());
    pState->setState(vcl::WindowState::Normal);
    pState->setMask(vcl::WindowDataMask::PosSizeState);
    return true;
}
 
void SvpSalFrame::ShowFullScreen( bool, sal_Int32 )
{
    SetPosSize( 0, 0, VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT,
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
}
 
void SvpSalFrame::StartPresentation( bool )
{
}
 
void SvpSalFrame::SetAlwaysOnTop( bool )
{
}
 
void SvpSalFrame::ToTop(SalFrameToTop nFlags)
{
    if (m_nStyle == SalFrameStyleFlags::NONE)
        return;
    if (nFlags & SalFrameToTop::RestoreWhenMin)
        Show(true, false);
    else
        GetFocus();
}
 
void SvpSalFrame::SetPointer( PointerStyle )
{
}
 
void SvpSalFrame::CaptureMouse( bool )
{
}
 
void SvpSalFrame::SetPointerPos( tools::Long, tools::Long )
{
}
 
void SvpSalFrame::Flush()
{
}
 
void SvpSalFrame::SetInputContext( SalInputContext* )
{
}
 
void SvpSalFrame::EndExtTextInput( EndExtTextInputFlags )
{
}
 
OUString SvpSalFrame::GetKeyName(sal_uInt16 nKeyCode)
{
    // Skip key combinations with any modifiers that we don’t support
    if ((nKeyCode & KEY_MODIFIERS_MASK & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2)) != 0)
        return OUString();
 
    OUStringBuffer aResult;
 
    if ((nKeyCode & KEY_MOD1) != 0)
        aResult.append("Ctrl+");
    if ((nKeyCode & KEY_MOD2) != 0)
        aResult.append("Alt+");
    if ((nKeyCode & KEY_SHIFT) != 0)
        aResult.append("Shift+");
 
    static const struct
    {
        sal_uInt16 code;
        const char* name;
    } keyMap[] = {
        { KEY_DOWN, "Down" },
        { KEY_UP, "Up" },
        { KEY_LEFT, "Left" },
        { KEY_RIGHT, "Right" },
        { KEY_HOME, "Home" },
        { KEY_END, "End" },
        { KEY_PAGEUP, "PgUp" },
        { KEY_PAGEDOWN, "PgDown" },
        { KEY_RETURN, "Ret" },
        { KEY_ESCAPE, "Esc" },
        { KEY_TAB, "Tab" },
        { KEY_BACKSPACE, "BkSpace" },
        { KEY_SPACE, "Space" },
        { KEY_DELETE, "Del" },
        { KEY_ADD, "+" },
        { KEY_SUBTRACT, "-" },
        { KEY_DIVIDE, "/" },
        { KEY_MULTIPLY, "*" },
        { KEY_POINT, "." },
        { KEY_COMMA, "," },
        { KEY_LESS, "<" },
        { KEY_GREATER, ">" },
        { KEY_EQUAL, "=" },
        { KEY_TILDE, "~" },
        { KEY_BRACKETLEFT, "[" },
        { KEY_BRACKETRIGHT, "]" },
        { KEY_SEMICOLON, ";" },
        { KEY_QUOTERIGHT, "'" },
        { KEY_RIGHTCURLYBRACKET, "}" },
        { KEY_NUMBERSIGN, "#" },
        { KEY_COLON, ":" },
    };
 
    sal_uInt16 nUnmodifiedCode = (nKeyCode & KEY_CODE_MASK);
 
    if (nUnmodifiedCode >= KEY_A && nUnmodifiedCode <= KEY_Z)
        aResult.append(char('A' + nUnmodifiedCode - KEY_A));
    else if (nUnmodifiedCode >= KEY_F1 && nUnmodifiedCode <= KEY_F26)
        aResult.append(OUStringChar('F') + OUString::number(nUnmodifiedCode - KEY_F1 + 1));
    else if (nUnmodifiedCode >= KEY_0 && nUnmodifiedCode <= KEY_9)
        aResult.append(char('0' + nUnmodifiedCode - KEY_0));
    else if (auto it = std::find_if(keyMap, keyMap + std::size(keyMap),
                                    [=](const auto& p) { return p.code == nUnmodifiedCode; });
             it != keyMap + std::size(keyMap))
        aResult.appendAscii(it->name);
    else
        return OUString();
 
    return aResult.makeStringAndClear();
}
 
bool SvpSalFrame::MapUnicodeToKeyCode( sal_Unicode, LanguageType, vcl::KeyCode& )
{
    return false;
}
 
LanguageType SvpSalFrame::GetInputLanguage()
{
    return LANGUAGE_DONTKNOW;
}
 
void SvpSalFrame::UpdateSettings( AllSettings& rSettings )
{
    StyleSettings aStyleSettings = rSettings.GetStyleSettings();
 
    Color aBackgroundColor( 0xef, 0xef, 0xef );
    aStyleSettings.BatchSetBackgrounds( aBackgroundColor, false );
    aStyleSettings.SetMenuColor( aBackgroundColor );
    aStyleSettings.SetMenuBarColor( aBackgroundColor );
 
    if (comphelper::LibreOfficeKit::isActive()) // TODO: remove this.
    {
        vcl::Font aStdFont( FAMILY_SWISS, Size( 0, 14 ) );
        aStdFont.SetCharSet( osl_getThreadTextEncoding() );
        aStdFont.SetWeight( WEIGHT_NORMAL );
        aStdFont.SetFamilyName( u"Liberation Sans"_ustr );
        aStyleSettings.BatchSetFonts( aStdFont, aStdFont );
 
        aStdFont.SetFontSize(Size(0, 12));
        aStyleSettings.SetMenuFont(aStdFont);
 
        SvpSalGraphics* pGraphics = m_aGraphics.empty() ? nullptr : m_aGraphics.back();
        bool bFreeGraphics = false;
        if (!pGraphics)
        {
            pGraphics = dynamic_cast<SvpSalGraphics*>(AcquireGraphics());
            if (!pGraphics)
            {
                SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
                return;
            }
            bFreeGraphics = true;
        }
        rSettings.SetStyleSettings(aStyleSettings);
#ifndef IOS // For now...
        pGraphics->UpdateSettings(rSettings);
#endif
        if (bFreeGraphics)
            ReleaseGraphics(pGraphics);
    }
    else
        rSettings.SetStyleSettings(aStyleSettings);
}
 
void SvpSalFrame::Beep()
{
}
 
const SystemEnvData& SvpSalFrame::GetSystemData() const
{
    return m_aSystemChildData;
}
 
SalFrame::SalPointerState SvpSalFrame::GetPointerState()
{
    SalPointerState aState;
    aState.mnState = 0;
    return aState;
}
 
KeyIndicatorState SvpSalFrame::GetIndicatorState()
{
    return KeyIndicatorState::NONE;
}
 
void SvpSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ )
{
}
 
void SvpSalFrame::SetParent( SalFrame* pNewParent )
{
    if( m_pParent )
        std::erase(m_pParent->m_aChildren, this);
    m_pParent = static_cast<SvpSalFrame*>(pNewParent);
}
 
void SvpSalFrame::SetPluginParent( SystemParentData* )
{
}
 
void SvpSalFrame::ResetClipRegion()
{
}
 
void SvpSalFrame::BeginSetClipRegion( sal_uInt32 )
{
}
 
void SvpSalFrame::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long )
{
}
 
void SvpSalFrame::EndSetClipRegion()
{
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

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

V1053 Calling the 'SetPosSize' virtual function in the constructor may lead to unexpected result at runtime.

V547 Expression 's_pFocusFrame == nullptr' is always true.