/* -*- 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 .
 */

#ifdef __sun
#include <ctime>
#endif

#include <unotools/viewoptions.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>

#include <vcl/dialoghelper.hxx>
#include <vcl/event.hxx>
#include <vcl/timer.hxx>
#include <vcl/svapp.hxx>

#include <splitwin.hxx>
#include <workwin.hxx>
#include <sfx2/dockwin.hxx>
#include <o3tl/string_view.hxx>

#include <cassert>
#include <memory>
#include <vector>
#include <utility>

using namespace ::com::sun::star::uno;

#define VERSION 1
#define nPixel  30L
constexpr OUString USERITEM_NAME = u"UserItem"_ustr;

namespace {
    // helper class to deactivate UpdateMode, if needed, for the life time of an instance
    class DeactivateUpdateMode
    {
    public:
        explicit DeactivateUpdateMode( SfxSplitWindow& rSplitWindow )
            : mrSplitWindow( rSplitWindow )
            , mbUpdateMode( rSplitWindow.IsUpdateMode() )
        {
            if ( mbUpdateMode )
            {
                mrSplitWindow.SetUpdateMode( false );
            }
        }

        ~DeactivateUpdateMode()
        {
            if ( mbUpdateMode )
            {
                mrSplitWindow.SetUpdateMode( true );
            }
        }

    private:
        SfxSplitWindow& mrSplitWindow;
        const bool mbUpdateMode;
    };
}

class SfxEmptySplitWin_Impl : public SplitWindow
{
/*  [Description]

    The SfxEmptySplitWin_Impldow is an empty SplitWindow, that replaces the
    SfxSplitWindow AutoHide mode. It only serves as a placeholder to receive
    mouse moves and if possible blend in the true SplitWindow display.
*/
friend class SfxSplitWindow;

    VclPtr<SfxSplitWindow>  pOwner;
    bool                    bFadeIn;
    bool                    bAutoHide;
    bool                    bSplit;
    bool                    bEndAutoHide;
    Timer                   aTimer;
    Point                   aLastPos;
    sal_uInt16              nState;

public:
   explicit             SfxEmptySplitWin_Impl( SfxSplitWindow *pParent )
                            : SplitWindow( pParent->GetParent(), WinBits( WB_BORDER | WB_3DLOOK ) )
                            , pOwner( pParent )
                            , bFadeIn( false )
                            , bAutoHide( false )
                            , bSplit( false )
                            , bEndAutoHide( false )
                            , aTimer("sfx2 SfxEmptySplitWin_Impl aTimer")
                            , nState( 1 )
                        {
                            aTimer.SetInvokeHandler(
                                LINK(pOwner, SfxSplitWindow, TimerHdl ) );
                            aTimer.SetTimeout( 200 );
                            SetAlign( pOwner->GetAlign() );
                            Actualize();
                            ShowFadeInHideButton();
                        }

                        virtual ~SfxEmptySplitWin_Impl() override
                        { disposeOnce(); }
   virtual void         dispose() override
                        {
                            aTimer.Stop();
                            pOwner.reset();
                            SplitWindow::dispose();
                        }

    virtual void        FadeIn() override;
    void                Actualize();
};

void SfxEmptySplitWin_Impl::Actualize()
{
    Size aSize( pOwner->GetSizePixel() );
    switch ( pOwner->GetAlign() )
    {
        case WindowAlign::Left:
        case WindowAlign::Right:
            aSize.setWidth( GetFadeInSize() );
            break;
        case WindowAlign::Top:
        case WindowAlign::Bottom:
            aSize.setHeight( GetFadeInSize() );
            break;
    }

    SetSizePixel( aSize );
}

void SfxEmptySplitWin_Impl::FadeIn()
{
    if (!bAutoHide )
        bAutoHide = IsFadeNoButtonMode();
    pOwner->SetFadeIn_Impl( true );
    if ( bAutoHide )
    {
        // Set Timer to close; the caller has to ensure themselves that the
        // Window is not closed instantly (eg by setting the focus or a modal
        // mode.
        aLastPos = GetPointerPosPixel();
        aTimer.Start();
    }
    else
        pOwner->SaveConfig_Impl();
}


void SfxSplitWindow::MouseButtonDown( const MouseEvent& rMEvt )
{
    if ( rMEvt.GetClicks() != 2 )
        SplitWindow::MouseButtonDown( rMEvt );
}

SfxSplitWindow::SfxSplitWindow( vcl::Window* pParent, SfxChildAlignment eAl,
        SfxWorkWindow *pW, bool bWithButtons )

/*  [Description]

    A SfxSplitWindow brings the recursive structure of the SV-SplitWindows to
    the outside by simulating a table-like structure with rows and columns
    (maximum recursion depth 2). Furthermore, it ensures the persistence of
    the arrangement of the SfxDockingWindows.
*/

:   SplitWindow ( pParent, WB_BORDER | WB_SIZEABLE | WB_3DLOOK | WB_HIDE ),
    eAlign(eAl),
    pWorkWin(pW),
    bPinned(true),
    pEmptyWin(nullptr),
    pActive(nullptr)
{
    if (bWithButtons)
    {
        ShowFadeOutButton();
    }

    // Set SV-Alignment
    WindowAlign eTbxAlign;
    switch ( eAlign )
    {
        case SfxChildAlignment::LEFT:
            eTbxAlign = WindowAlign::Left;
            break;
        case SfxChildAlignment::RIGHT:
            eTbxAlign = WindowAlign::Right;
            break;
        case SfxChildAlignment::TOP:
            eTbxAlign = WindowAlign::Top;
            break;
        case SfxChildAlignment::BOTTOM:
            eTbxAlign = WindowAlign::Bottom;
            bPinned = true;
            break;
        default:
            eTbxAlign = WindowAlign::Top;  // some sort of default...
            break;  // -Wall lots not handled...
    }

    SetAlign (eTbxAlign);
    pEmptyWin = VclPtr<SfxEmptySplitWin_Impl>::Create( this );
    if ( bPinned )
    {
        pEmptyWin->bFadeIn = true;
        pEmptyWin->nState = 2;
    }

    if ( bWithButtons )
    {
        //  Read Configuration
        const OUString aWindowId{ "SplitWindow" + OUString::number(static_cast<sal_Int32>(eTbxAlign)) };
        SvtViewOptions aWinOpt( EViewType::Window, aWindowId );
        OUString aWinData;
        Any aUserItem = aWinOpt.GetUserItem( USERITEM_NAME );
        OUString aTemp;
        if ( aUserItem >>= aTemp )
            aWinData = aTemp;
        if ( aWinData.startsWith("V") )
        {
            sal_Int32 nIdx{ 0 };
            pEmptyWin->nState = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 1, ',', nIdx )));
            if ( pEmptyWin->nState & 2 )
                pEmptyWin->bFadeIn = true;
            bPinned = true; // always assume pinned - floating mode not used anymore

            const sal_Int32 nCount{ o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx)) };
            for ( sal_Int32 n=0; n<nCount; ++n )
            {
                std::unique_ptr<SfxDock_Impl> pDock(new SfxDock_Impl);
                pDock->pWin = nullptr;
                pDock->bNewLine = false;
                pDock->bHide = true;
                pDock->nType = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx)));
                if ( !pDock->nType )
                {
                    // could mean NewLine
                    pDock->nType = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx)));
                    if ( !pDock->nType )
                    {
                        // Read error
                        break;
                    }
                    else
                        pDock->bNewLine = true;
                }

                maDockArr.insert(maDockArr.begin() + n, std::move(pDock));
            }
        }
    }
    else
    {
        bPinned = true;
        pEmptyWin->bFadeIn = true;
        pEmptyWin->nState = 2;
    }
}


SfxSplitWindow::~SfxSplitWindow()
{
    disposeOnce();
}

void SfxSplitWindow::dispose()
{
    SaveConfig_Impl();

    if ( pEmptyWin )
    {
        // Set pOwner to NULL, otherwise try to delete pEmptyWin once more. The
        // window that is just being docked is always deleted from the outside.
        pEmptyWin->pOwner = nullptr;
    }
    pEmptyWin.disposeAndClear();

    maDockArr.clear();
    pActive.reset();
    SplitWindow::dispose();
}

void SfxSplitWindow::SaveConfig_Impl()
{
    // Save configuration
    OUStringBuffer aWinData =
        "V"
        + OUString::number(static_cast<sal_Int32>(VERSION))
        + ","
        + OUString::number(static_cast<sal_Int32>(pEmptyWin->nState))
        + ",";

    sal_uInt16 nCount = 0;
    for ( auto const & rDock: maDockArr )
    {
        if ( rDock->bHide || rDock->pWin )
            nCount++;
    }

    aWinData.append(static_cast<sal_Int32>(nCount));

    for ( auto const & rDock: maDockArr )
    {
        if ( !rDock->bHide && !rDock->pWin )
            continue;
        if ( rDock->bNewLine )
            aWinData.append(",0");
        aWinData.append("," + OUString::number(static_cast<sal_Int32>(rDock->nType)));
    }

    const OUString aWindowId{ "SplitWindow" + OUString::number(static_cast<sal_Int32>(GetAlign())) };
    SvtViewOptions aWinOpt( EViewType::Window, aWindowId );
    aWinOpt.SetUserItem( USERITEM_NAME, Any( aWinData.makeStringAndClear() ) );
}


void SfxSplitWindow::StartSplit()
{
    tools::Long nSize = 0;
    Size aSize = GetSizePixel();

    if ( pEmptyWin )
    {
        pEmptyWin->bFadeIn = true;
        pEmptyWin->bSplit = true;
    }

    tools::Rectangle aRect = pWorkWin->GetFreeArea( !bPinned );
    switch ( GetAlign() )
    {
        case WindowAlign::Left:
        case WindowAlign::Right:
            nSize = aSize.Width() + aRect.GetWidth();
            break;
        case WindowAlign::Top:
        case WindowAlign::Bottom:
            nSize = aSize.Height() + aRect.GetHeight();
            break;
    }

    SetMaxSizePixel( nSize );
}


void SfxSplitWindow::SplitResize()
{
    if ( bPinned )
    {
        pWorkWin->ArrangeChildren_Impl();
        pWorkWin->ShowChildren_Impl();
    }
    else
        pWorkWin->ArrangeAutoHideWindows( this );
}


void SfxSplitWindow::Split()
{
    if ( pEmptyWin )
        pEmptyWin->bSplit = false;

    SplitWindow::Split();

    std::vector< std::pair< sal_uInt16, tools::Long > > aNewOrgSizes;

    sal_uInt16 nCount = maDockArr.size();
    for ( sal_uInt16 n=0; n<nCount; n++ )
    {
        const SfxDock_Impl& rD = *maDockArr[n];
        if ( rD.pWin )
        {
            const sal_uInt16 nId = rD.nType;
            const tools::Long nSize    = GetItemSize( nId, SplitWindowItemFlags::Fixed );
            const tools::Long nSetSize = GetItemSize( GetSet( nId ) );
            Size aSize;

            if ( IsHorizontal() )
            {
                aSize.setWidth( nSize );
                aSize.setHeight( nSetSize );
            }
            else
            {
                aSize.setWidth( nSetSize );
                aSize.setHeight( nSize );
            }

            rD.pWin->SetItemSize_Impl( aSize );

            aNewOrgSizes.emplace_back( nId, nSize );
        }
    }

    // workaround insufficiency of <SplitWindow> regarding dock layouting:
    // apply FIXED item size as 'original' item size to improve layouting of undock-dock-cycle of a window
    {
        DeactivateUpdateMode aDeactivateUpdateMode( *this );
        for (const std::pair< sal_uInt16, tools::Long > & rNewOrgSize : aNewOrgSizes)
        {
            SetItemSize( rNewOrgSize.first, rNewOrgSize.second );
        }
    }

    SaveConfig_Impl();
}


void SfxSplitWindow::InsertWindow( SfxDockingWindow* pDockWin, const Size& rSize)

/*
    To insert SfxDockingWindows just pass no position. The SfxSplitWindow
    searches the last marked one to the passed SfxDockingWindow or appends a
    new one at the end.
*/
{
    short nLine = -1;  // so that the first window cab set nline to 0
    sal_uInt16 nPos = 0;
    bool bNewLine = true;
    bool bSaveConfig = false;
    SfxDock_Impl *pFoundDock=nullptr;
    sal_uInt16 nCount = maDockArr.size();
    for ( sal_uInt16 n=0; n<nCount; n++ )
    {
        SfxDock_Impl& rDock = *maDockArr[n];
        if ( rDock.bNewLine )
        {
            // The window opens a new line
            if ( pFoundDock )
                // But after the just inserted window
                break;

            // New line
            nPos = 0;
            bNewLine = true;
        }

        if ( rDock.pWin )
        {
            // Does there exist a window now at this position
            if ( bNewLine && !pFoundDock )
            {
                // Not known until now in which real line it is located
                sal_uInt16 nL = 0;
                [[maybe_unused]] auto const ok = GetWindowPos( rDock.pWin, nL, nPos );
                assert(ok);
                nLine = static_cast<short>(nL);
            }

            if ( !pFoundDock )
            {
                // The window is located before the inserted one
                nPos++;
            }

            // Line is opened
            bNewLine = false;
            if ( pFoundDock )
                break;
        }

        if ( rDock.nType == pDockWin->GetType() )
        {
            DBG_ASSERT( !pFoundDock && !rDock.pWin, "Window already exists!");
            pFoundDock = &rDock;
            if ( !bNewLine )
                break;
            else
            {
                // A new line has been created but no window was found there;
                // continue searching for a window in this line in-order to set
                // bNewLine correctly. While doing so nline or nPos are not
                // to be changed!
                nLine++;
            }
        }
    }

    if ( !pFoundDock )
    {
        // Not found, insert at end
        pFoundDock = new SfxDock_Impl;
        pFoundDock->bHide = true;
        maDockArr.push_back( std::unique_ptr<SfxDock_Impl>(pFoundDock) );
        pFoundDock->nType = pDockWin->GetType();
        nLine++;
        nPos = 0;
        bNewLine = true;
        pFoundDock->bNewLine = bNewLine;
        bSaveConfig = true;
    }

    pFoundDock->pWin = pDockWin;
    pFoundDock->bHide = false;
    InsertWindow_Impl( pFoundDock, rSize, nLine, nPos, bNewLine );
    if ( bSaveConfig )
        SaveConfig_Impl();
}


void SfxSplitWindow::ReleaseWindow_Impl(SfxDockingWindow const *pDockWin, bool bSave)
{
//  The docking window is no longer stored in the internal data.
    sal_uInt16 nCount = maDockArr.size();
    for ( sal_uInt16 n=0; n<nCount; n++ )
    {
        const SfxDock_Impl& rDock = *maDockArr[n];
        if ( rDock.nType == pDockWin->GetType() )
        {
            if ( rDock.bNewLine && n<nCount-1 )
                maDockArr[n+1]->bNewLine = true;

            // Window has a position, this we forget
            maDockArr.erase(maDockArr.begin() + n);
            break;
        }
    }

    if ( bSave )
        SaveConfig_Impl();
}


void SfxSplitWindow::MoveWindow( SfxDockingWindow* pDockWin, const Size& rSize,
                        sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine)

/*  [Description]

    The docking window is moved within the SplitWindows.
*/

{
    sal_uInt16 nL = 0;
    sal_uInt16 nP = 0;
    [[maybe_unused]] auto const ok = GetWindowPos( pDockWin, nL, nP );
    assert(ok);

    if ( nLine > nL && GetItemCount( GetItemId( nL ) ) == 1 )
    {
        // If the last window is removed from its line, then everything slips
        // one line to the front!
        nLine--;
    }
    RemoveWindow( pDockWin );
    InsertWindow( pDockWin, rSize, nLine, nPos, bNewLine );
}


void SfxSplitWindow::InsertWindow( SfxDockingWindow* pDockWin, const Size& rSize,
                        sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine)

/*  [Description]

    The DockingWindow that is pushed on this SplitWindow and shall hold the
    given position and size.
*/
{
    ReleaseWindow_Impl( pDockWin, false );
    SfxDock_Impl *pDock = new SfxDock_Impl;
    pDock->bHide = false;
    pDock->nType = pDockWin->GetType();
    pDock->bNewLine = bNewLine;
    pDock->pWin = pDockWin;

    DBG_ASSERT( nPos==0 || !bNewLine, "Wrong Parameter!");
    if ( bNewLine )
        nPos = 0;

    // The window must be inserted before the first window so that it has the
    // same or a greater position than pDockWin.
    sal_uInt16 nCount = maDockArr.size();
    sal_uInt16 nLastWindowIdx(0);

    // If no window is found, a first window is inserted
    sal_uInt16 nInsertPos = 0;
    for ( sal_uInt16 n=0; n<nCount; n++ )
    {
        SfxDock_Impl& rD = *maDockArr[n];

        if (rD.pWin)
        {
            // A docked window has been found. If no suitable window behind
            // the desired insertion point s found, then insertion is done at
            // the end.
            nInsertPos = nCount;
            nLastWindowIdx = n;
            sal_uInt16 nL=0, nP=0;
            GetWindowPos( rD.pWin, nL, nP );

            if ( (nL == nLine && nP == nPos) || nL > nLine )
            {
                DBG_ASSERT( nL == nLine || bNewLine || nPos > 0, "Wrong Parameter!" );
                if ( nL == nLine && nPos == 0 && !bNewLine )
                {
                    DBG_ASSERT(rD.bNewLine, "No new line?");

                    // The position is pushed to nPos==0
                    rD.bNewLine = false;
                    pDock->bNewLine = true;
                }

                nInsertPos = n != 0 ? nLastWindowIdx + 1 : 0;    // ignore all non-windows after the last window
                break;
            }
        }
    }
    if (nCount != 0 && nInsertPos == nCount && nLastWindowIdx != nCount - 1)
    {
        nInsertPos = nLastWindowIdx + 1;    // ignore all non-windows after the last window
    }

    maDockArr.insert(maDockArr.begin() + nInsertPos, std::unique_ptr<SfxDock_Impl>(pDock));
    InsertWindow_Impl( pDock, rSize, nLine, nPos, bNewLine );
    SaveConfig_Impl();
}


void SfxSplitWindow::InsertWindow_Impl( SfxDock_Impl const * pDock,
                        const Size& rSize,
                        sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine)

/*  [Description]

    Adds a DockingWindow, and causes the recalculation of the size of
    the SplitWindows.
*/

{
    SfxDockingWindow* pDockWin = pDock->pWin;

    SplitWindowItemFlags nItemBits = SplitWindowItemFlags::NONE;

    tools::Long nWinSize, nSetSize;
    if ( IsHorizontal() )
    {
        nWinSize = rSize.Width();
        nSetSize = rSize.Height();
    }
    else
    {
        nSetSize = rSize.Width();
        nWinSize = rSize.Height();
    }

    std::unique_ptr<DeactivateUpdateMode> pDeactivateUpdateMode(new DeactivateUpdateMode( *this ));

    if ( bNewLine || nLine == GetItemCount() )
    {
        // An existing row should not be inserted, instead a new one
        // will be created

        sal_uInt16 nId = 1;
        for ( sal_uInt16 n=0; n<GetItemCount(); n++ )
        {
            if ( GetItemId(n) >= nId )
                nId = GetItemId(n)+1;
        }

        // Create a new nLine:th line
        SplitWindowItemFlags nBits = nItemBits;
        if ( GetAlign() == WindowAlign::Top || GetAlign() == WindowAlign::Bottom )
            nBits |= SplitWindowItemFlags::ColSet;
        InsertItem( nId, nSetSize, nLine, 0, nBits );
    }

    // Insert the window at line with the position nline. ItemWindowSize set to
    // "percentage" share since the SV then does the re-sizing as expected,
    // "pixel" actually only makes sense if also items with percentage or
    // relative sizes are present.
    nItemBits |= SplitWindowItemFlags::PercentSize;
    sal_uInt16 nSet = GetItemId( nLine );
    InsertItem( pDockWin->GetType(), pDockWin, nWinSize, nPos, nSet, nItemBits );

    // SplitWindows are once created in SFX and when inserting the first
    // DockingWindows is made visible.
    if ( GetItemCount() == 1 && GetItemCount( 1 ) == 1 )
    {
        // The Rearranging in WorkWindow and a Show() on the SplitWindow is
        // caused by SfxDockingwindow (->SfxWorkWindow::ConfigChild_Impl)
        if ( !bPinned && !IsFloatingMode() )
        {
            bPinned = true;
            bool bFadeIn = ( pEmptyWin->nState & 2 ) != 0;
            pEmptyWin->bFadeIn = false;
            SetPinned_Impl( false );
            pEmptyWin->Actualize();
            SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering empty Splitwindow" );
            pWorkWin->RegisterChild_Impl( *GetSplitWindow(), eAlign )->nVisible = SfxChildVisibility::VISIBLE;
            // tdf#113539 FadeIn will call ArrangeChildren_Impl() for us, and avoiding extra calls to that
            // can make a different to load times because it avoids extra accessibility calcs
            if ( bFadeIn )
                FadeIn();
            else
                pWorkWin->ArrangeChildren_Impl();
        }
        else
        {
            bool bFadeIn = ( pEmptyWin->nState & 2 ) != 0;
            pEmptyWin->bFadeIn = false;
            pEmptyWin->Actualize();
            if ( !bPinned || !pEmptyWin->bFadeIn )
            {
                SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering empty Splitwindow" );
            }
            else
            {
                SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering real Splitwindow" );
            }
            pWorkWin->RegisterChild_Impl( *GetSplitWindow(), eAlign )->nVisible = SfxChildVisibility::VISIBLE;
            // tdf#113539 FadeIn will call ArrangeChildren_Impl() for us, and avoiding extra calls to that
            // can make a different to load times because it avoids extra accessibility calcs
            if ( bFadeIn )
                FadeIn();
            else
                pWorkWin->ArrangeChildren_Impl();
        }

        pWorkWin->ShowChildren_Impl();
    }

    pDeactivateUpdateMode.reset();

    // workaround insufficiency of <SplitWindow> regarding dock layouting:
    // apply FIXED item size as 'original' item size to improve layouting of undock-dock-cycle of a window
    {
        std::vector< std::pair< sal_uInt16, tools::Long > > aNewOrgSizes;
        // get FIXED item sizes
        sal_uInt16 nCount = maDockArr.size();
        for ( sal_uInt16 n=0; n<nCount; ++n )
        {
            const SfxDock_Impl& rD = *maDockArr[n];
            if ( rD.pWin )
            {
                const sal_uInt16 nId = rD.nType;
                const tools::Long nSize    = GetItemSize( nId, SplitWindowItemFlags::Fixed );
                aNewOrgSizes.emplace_back( nId, nSize );
            }
        }
        // apply new item sizes
        DeactivateUpdateMode aDeactivateUpdateMode( *this );
        for (const std::pair< sal_uInt16, tools::Long > & rNewOrgSize : aNewOrgSizes)
        {
            SetItemSize( rNewOrgSize.first, rNewOrgSize.second );
        }
    }
}


void SfxSplitWindow::RemoveWindow( SfxDockingWindow const * pDockWin, bool bHide )

/*  [Description]

    Removes a DockingWindow. If it was the last one, then the SplitWindow is
    being hidden.
*/
{
    sal_uInt16 nSet = GetSet( pDockWin->GetType() );

    // SplitWindows are once created in SFX and is made invisible after
    // removing the last DockingWindows.
    if ( GetItemCount( nSet ) == 1 && GetItemCount() == 1 )
    {
        // The Rearranging in WorkWindow is caused by SfxDockingwindow
        Hide();
        pEmptyWin->aTimer.Stop();
        sal_uInt16 nRealState = pEmptyWin->nState;
        FadeOut_Impl();
        pEmptyWin->Hide();
#ifdef DBG_UTIL
        if ( !bPinned || !pEmptyWin->bFadeIn )
        {
            SAL_INFO("sfx", "SfxSplitWindow::RemoveWindow - releasing empty Splitwindow" );
        }
        else
        {
            SAL_INFO("sfx", "SfxSplitWindow::RemoveWindow - releasing real Splitwindow" );
        }
#endif
        pWorkWin->ReleaseChild_Impl( *GetSplitWindow() );
        pEmptyWin->nState = nRealState;
        pWorkWin->ArrangeAutoHideWindows( this );
    }

    sal_uInt16 nCount = maDockArr.size();
    for ( sal_uInt16 n=0; n<nCount; n++ )
    {
        SfxDock_Impl& rDock = *maDockArr[n];
        if ( rDock.nType == pDockWin->GetType() )
        {
            rDock.pWin = nullptr;
            rDock.bHide = bHide;
            break;
        }
    }

    // Remove Windows, and if it was the last of the line, then also remove
    // the line (line = itemset)
    DeactivateUpdateMode aDeactivateUpdateMode( *this );

    RemoveItem( pDockWin->GetType() );

    if ( nSet && !GetItemCount( nSet ) )
        RemoveItem( nSet );
};


bool SfxSplitWindow::GetWindowPos( const SfxDockingWindow* pWindow,
                                        sal_uInt16& rLine, sal_uInt16& rPos ) const
/*  [Description]

    Returns the ID of the item sets and items for the DockingWindow in
    the position passed on the old row / column-name.
*/

{
    sal_uInt16 nSet = GetSet ( pWindow->GetType() );
    if ( nSet == SPLITWINDOW_ITEM_NOTFOUND )
        return false;

    rPos  = GetItemPos( pWindow->GetType(), nSet );
    rLine = GetItemPos( nSet );
    return true;
}


bool SfxSplitWindow::GetWindowPos( const Point& rTestPos,
                                      sal_uInt16& rLine, sal_uInt16& rPos ) const
/*  [Description]

    Returns the ID of the item sets and items for the DockingWindow in
    the position passed on the old row / column-name.
*/

{
    sal_uInt16 nId = GetItemId( rTestPos );
    if ( nId == 0 )
        return false;

    sal_uInt16 nSet = GetSet ( nId );
    rPos  = GetItemPos( nId, nSet );
    rLine = GetItemPos( nSet );
    return true;
}


sal_uInt16 SfxSplitWindow::GetLineCount() const

/*  [Description]

    Returns the number of rows = number of sub-itemsets in the root set.
*/
{
    return GetItemCount();
}


tools::Long SfxSplitWindow::GetLineSize( sal_uInt16 nLine ) const

/*  [Description]

    Returns the Row Height of nline itemset.
*/
{
    sal_uInt16 nId = GetItemId( nLine );
    return GetItemSize( nId );
}


sal_uInt16 SfxSplitWindow::GetWindowCount( sal_uInt16 nLine ) const

/*  [Description]

    Returns the total number of windows
*/
{
    sal_uInt16 nId = GetItemId( nLine );
    return GetItemCount( nId );
}


sal_uInt16 SfxSplitWindow::GetWindowCount() const

/*  [Description]

    Returns the total number of windows
*/
{
    return GetItemCount();
}


IMPL_LINK( SfxSplitWindow, TimerHdl, Timer*, pTimer, void)
{
    if ( pTimer )
        pTimer->Stop();

    if ( CursorIsOverRect() || !pTimer )
    {
        // If the cursor is within the window, display the SplitWindow and set
        // up the timer for close
        pEmptyWin->bAutoHide = true;
        if ( !IsVisible() )
            pEmptyWin->FadeIn();

        pEmptyWin->aLastPos = GetPointerPosPixel();
        pEmptyWin->aTimer.Start();
    }
    else if ( pEmptyWin->bAutoHide )
    {
        if ( GetPointerPosPixel() != pEmptyWin->aLastPos )
        {
            // The mouse has moved within the running time of the timer, thus
            // do nothing
            pEmptyWin->aLastPos = GetPointerPosPixel();
            pEmptyWin->aTimer.Start();
            return;
        }

        // Especially for TF_AUTOSHOW_ON_MOUSEMOVE :
        // If the window is not visible, there is nothing to do
        // (user has simply moved the mouse over pEmptyWin)
        if ( IsVisible() )
        {
            pEmptyWin->bEndAutoHide = false;
            if ( !Application::IsInModalMode() &&
                  !vcl::IsInPopupMenuExecute() &&
                  !pEmptyWin->bSplit && !HasChildPathFocus( true ) )
            {
                // While a modal dialog or a popup menu is open or while the
                // Splitting is done, in any case, do not close. Even as long
                // as one of the Children has the focus, the window remains
                // open.
                pEmptyWin->bEndAutoHide = true;
            }

            if ( pEmptyWin->bEndAutoHide )
            {
               // As far as I am concerned this can be the end of AutoShow
               // But maybe some other SfxSplitWindow will remain open,
               // then all others remain open too.
                if ( !pWorkWin->IsAutoHideMode( this ) )
                {
                    FadeOut_Impl();
                    pWorkWin->ArrangeAutoHideWindows( this );
                }
                else
                {
                    pEmptyWin->aLastPos = GetPointerPosPixel();
                    pEmptyWin->aTimer.Start();
                }
            }
            else
            {
                pEmptyWin->aLastPos = GetPointerPosPixel();
                pEmptyWin->aTimer.Start();
            }
        }
    }
}


bool SfxSplitWindow::CursorIsOverRect() const
{
    bool bVisible = IsVisible();

    // Also, take the collapsed SplitWindow into account
    Point aPos = pEmptyWin->GetParent()->OutputToScreenPixel( pEmptyWin->GetPosPixel() );
    Size aSize = pEmptyWin->GetSizePixel();

    tools::Rectangle aRect( aPos, aSize );

    if ( bVisible )
    {
        Point aVisPos = GetPosPixel();
        Size aVisSize = GetSizePixel();

        // Extend with +/- a few pixels, otherwise it is too nervous
        aVisPos.AdjustX( -(nPixel) );
        aVisPos.AdjustY( -(nPixel) );
        aVisSize.AdjustWidth(2 * nPixel );
        aVisSize.AdjustHeight(2 * nPixel );

        tools::Rectangle aVisRect( aVisPos, aVisSize );
        aRect = aRect.GetUnion( aVisRect );
    }

    return aRect.Contains( OutputToScreenPixel( static_cast<vcl::Window*>(const_cast<SfxSplitWindow *>(this))->GetPointerPosPixel() ) );
}


SplitWindow* SfxSplitWindow::GetSplitWindow()
{
    if ( !bPinned || !pEmptyWin->bFadeIn )
        return pEmptyWin;
    return this;
}


bool SfxSplitWindow::IsFadeIn() const
{
    return pEmptyWin->bFadeIn;
}

bool SfxSplitWindow::IsAutoHide( bool bSelf ) const
{
    return bSelf ? pEmptyWin->bAutoHide && !pEmptyWin->bEndAutoHide : pEmptyWin->bAutoHide;
}


void SfxSplitWindow::SetPinned_Impl( bool bOn )
{
    if ( bPinned == bOn )
        return;

    bPinned = bOn;
    if ( GetItemCount() == 0 )
        return;

    if ( !bOn )
    {
        pEmptyWin->nState |= 1;
        if ( pEmptyWin->bFadeIn )
        {
            // Unregister replacement windows
            SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - releasing real Splitwindow" );
            pWorkWin->ReleaseChild_Impl( *this );
            Hide();
            pEmptyWin->Actualize();
            SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - registering empty Splitwindow" );
            pWorkWin->RegisterChild_Impl( *pEmptyWin, eAlign )->nVisible = SfxChildVisibility::VISIBLE;
        }

        Point aPos( GetPosPixel() );
        aPos = GetParent()->OutputToScreenPixel( aPos );
        SetFloatingPos( aPos );
        SetFloatingMode( true );
        GetFloatingWindow()->SetOutputSizePixel( GetOutputSizePixel() );

        if ( pEmptyWin->bFadeIn )
            Show();
    }
    else
    {
        pEmptyWin->nState &= ~1;
        SetOutputSizePixel( GetFloatingWindow()->GetOutputSizePixel() );
        SetFloatingMode(false);

        if ( pEmptyWin->bFadeIn )
        {
            // Unregister replacement windows
            SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - releasing empty Splitwindow" );
            pWorkWin->ReleaseChild_Impl( *pEmptyWin );
            pEmptyWin->Hide();
            SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - registering real Splitwindow" );
            pWorkWin->RegisterChild_Impl( *this, eAlign )->nVisible = SfxChildVisibility::VISIBLE;
        }
    }
}

void SfxSplitWindow::SetFadeIn_Impl( bool bOn )
{
    if ( bOn == pEmptyWin->bFadeIn )
        return;

    if ( GetItemCount() == 0 )
        return;

    pEmptyWin->bFadeIn = bOn;
    if ( bOn )
    {
        pEmptyWin->nState |= 2;
        if ( IsFloatingMode() )
        {
            // FloatingWindow is not visible, thus display it
            pWorkWin->ArrangeAutoHideWindows( this );
            Show();
        }
        else
        {
            SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - releasing empty Splitwindow" );
            pWorkWin->ReleaseChild_Impl( *pEmptyWin );
            pEmptyWin->Hide();
            SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - registering real Splitwindow" );
            pWorkWin->RegisterChild_Impl( *this, eAlign )->nVisible = SfxChildVisibility::VISIBLE;
            pWorkWin->ArrangeChildren_Impl();
            pWorkWin->ShowChildren_Impl();
        }
    }
    else
    {
        pEmptyWin->bAutoHide = false;
        pEmptyWin->nState &= ~2;
        if ( !IsFloatingMode() )
        {
            // The window is not "floating", should be hidden
            SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - releasing real Splitwindow" );
            pWorkWin->ReleaseChild_Impl( *this );
            Hide();
            pEmptyWin->Actualize();
            SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - registering empty Splitwindow" );
            pWorkWin->RegisterChild_Impl( *pEmptyWin, eAlign )->nVisible = SfxChildVisibility::VISIBLE;
            pWorkWin->ArrangeChildren_Impl();
            pWorkWin->ShowChildren_Impl();
            pWorkWin->ArrangeAutoHideWindows( this );
        }
        else
        {
            Hide();
            pWorkWin->ArrangeAutoHideWindows( this );
        }
    }
}

void SfxSplitWindow::FadeOut_Impl()
{
    if ( pEmptyWin->aTimer.IsActive() )
    {
        pEmptyWin->bAutoHide = false;
        pEmptyWin->aTimer.Stop();
    }

    SetFadeIn_Impl( false );
}

void SfxSplitWindow::FadeOut()
{
    FadeOut_Impl();
    SaveConfig_Impl();
}

void SfxSplitWindow::FadeIn()
{
    SetFadeIn_Impl( true );
}

void SfxSplitWindow::SetActiveWindow_Impl( SfxDockingWindow* pWin )
{
    pActive = pWin;
    pWorkWin->SetActiveChild_Impl( this );
}


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