/* -*- 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 <svtools/brwbox.hxx>
#include <svtools/brwhead.hxx>
#include <o3tl/numeric.hxx>
#include <o3tl/safeint.hxx>
#include "datwin.hxx"
#include <tools/debug.hxx>
#include <tools/fract.hxx>
#include <sal/log.hxx>
#include <vcl/scrbar.hxx>
#include <vcl/status.hxx>

#include <algorithm>
#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <tools/multisel.hxx>
#include "brwimpl.hxx"


#define SCROLL_FLAGS (ScrollFlags::Clip | ScrollFlags::NoChildren)

using namespace com::sun::star::accessibility::AccessibleEventId;
using namespace com::sun::star::accessibility::AccessibleTableModelChangeType;
using com::sun::star::accessibility::AccessibleTableModelChange;
using namespace ::com::sun::star::uno;
using namespace svt;

namespace
{
    void disposeAndClearHeaderCell(::svt::BrowseBoxImpl::THeaderCellMap& _rHeaderCell)
    {
        ::std::for_each(
                        _rHeaderCell.begin(),
                        _rHeaderCell.end(),
                        ::svt::BrowseBoxImpl::THeaderCellMapFunctorDispose()
                            );
        _rHeaderCell.clear();
    }
}

void BrowseBox::ConstructImpl( BrowserMode nMode )
{
    SAL_INFO("svtools", "BrowseBox:ConstructImpl " << this );
    bMultiSelection = false;
    pColSel = nullptr;
    pVScroll = nullptr;
    pDataWin = VclPtr<BrowserDataWin>::Create( this ).get();
    m_pImpl.reset( new ::svt::BrowseBoxImpl() );

    InitSettings_Impl( this );
    InitSettings_Impl( pDataWin );

    bBootstrapped = false;
    nDataRowHeight = 0;
    nTitleLines = 1;
    nFirstCol = 0;
    nTopRow = 0;
    nCurRow = BROWSER_ENDOFSELECTION;
    nCurColId = 0;
    bResizing = false;
    bSelect = false;
    bSelecting = false;
    bScrolling = false;
    bSelectionIsVisible = false;
    bNotToggleSel = false;
    bRowDividerDrag = false;
    bHit = false;
    mbInteractiveRowHeight = false;
    bHideSelect = false;
    bHideCursor = TRISTATE_FALSE;
    nRowCount = 0;
    m_bFocusOnlyCursor = true;
    m_aCursorColor = COL_TRANSPARENT;
    m_nCurrentMode = BrowserMode::NONE;
    nControlAreaWidth = USHRT_MAX;
    uRow.nSel = BROWSER_ENDOFSELECTION;

    aHScroll->SetLineSize(1);
    aHScroll->SetScrollHdl( LINK( this, BrowseBox, ScrollHdl ) );
    pDataWin->Show();

    SetMode( nMode );
    bSelectionIsVisible = bKeepHighlight;
    bHasFocus = HasChildPathFocus();
    pDataWin->nCursorHidden =
                ( bHasFocus ? 0 : 1 ) + ( GetUpdateMode() ? 0 : 1 );
}

BrowseBox::BrowseBox( vcl::Window* pParent, WinBits nBits, BrowserMode nMode )
    :Control( pParent, nBits | WB_3DLOOK )
    ,DragSourceHelper( this )
    ,DropTargetHelper( this )
    ,aHScroll( VclPtr<ScrollBar>::Create(this, WB_HSCROLL) )
    ,aStatusBar( VclPtr<StatusBar>::Create(this) )
{
    ConstructImpl( nMode );
}

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

void BrowseBox::dispose()
{
    SAL_INFO("svtools", "BrowseBox:dispose " << this );

    if ( m_pImpl->m_pAccessible )
    {
        disposeAndClearHeaderCell(m_pImpl->m_aColHeaderCellMap);
        disposeAndClearHeaderCell(m_pImpl->m_aRowHeaderCellMap);
        m_pImpl->m_pAccessible->dispose();
    }

    Hide();
    pDataWin->pHeaderBar.disposeAndClear();
    pDataWin->pCornerWin.disposeAndClear();
    pDataWin.disposeAndClear();
    pVScroll.disposeAndClear();
    aHScroll.disposeAndClear();
    aStatusBar.disposeAndClear();

    // free columns-space
    mvCols.clear();
    pColSel.reset();
    if ( bMultiSelection )
        delete uRow.pSel;
    DragSourceHelper::dispose();
    DropTargetHelper::dispose();
    Control::dispose();
}


short BrowseBox::GetCursorHideCount() const
{
    return pDataWin->nCursorHidden;
}


void BrowseBox::DoShowCursor( const char * )
{
    if (!pDataWin)
        return;
    short nHiddenCount = --pDataWin->nCursorHidden;
    if (PaintCursorIfHiddenOnce())
    {
        if (1 == nHiddenCount)
            DrawCursor();
    }
    else
    {
        if (0 == nHiddenCount)
            DrawCursor();
    }
}


void BrowseBox::DoHideCursor( const char * )
{
    short nHiddenCount = ++pDataWin->nCursorHidden;
    if (PaintCursorIfHiddenOnce())
    {
        if (2 == nHiddenCount)
            DrawCursor();
    }
    else
    {
        if (1 == nHiddenCount)
            DrawCursor();
    }
}


void BrowseBox::SetRealRowCount( const OUString &rRealRowCount )
{
    pDataWin->aRealRowCount = rRealRowCount;
}


void BrowseBox::SetFont( const vcl::Font& rNewFont )
{
    pDataWin->SetFont( rNewFont );
    ImpGetDataRowHeight();
}

const vcl::Font& BrowseBox::GetFont() const
{
    return pDataWin->GetFont();
}

sal_uLong BrowseBox::GetDefaultColumnWidth( const OUString& _rText ) const
{
    return pDataWin->GetTextWidth( _rText ) + pDataWin->GetTextWidth(OUString('0')) * 4;
}


void BrowseBox::InsertHandleColumn( sal_uLong nWidth )
{

#if OSL_DEBUG_LEVEL > 0
    OSL_ENSURE( ColCount() == 0 || mvCols[0]->GetId() != HandleColumnId , "BrowseBox::InsertHandleColumn: there is already a handle column" );
    {
        for (auto const & col : mvCols)
            OSL_ENSURE( col->GetId() != HandleColumnId, "BrowseBox::InsertHandleColumn: there is a non-Handle column with handle ID" );
    }
#endif

    mvCols.insert( mvCols.begin(), std::unique_ptr<BrowserColumn>(new BrowserColumn( 0, OUString(), nWidth, GetZoom() )) );
    FreezeColumn( 0 );

    // adjust headerbar
    if ( pDataWin->pHeaderBar )
    {
        pDataWin->pHeaderBar->SetPosSizePixel(
                    Point(nWidth, 0),
                    Size( GetOutputSizePixel().Width() - nWidth, GetTitleHeight() )
                    );
    }

    ColumnInserted( 0 );
}


void BrowseBox::InsertDataColumn( sal_uInt16 nItemId, const OUString& rText,
        long nWidth, HeaderBarItemBits nBits, sal_uInt16 nPos )
{

    OSL_ENSURE( nItemId != HandleColumnId, "BrowseBox::InsertDataColumn: nItemId is HandleColumnId" );
    OSL_ENSURE( nItemId != BROWSER_INVALIDID, "BrowseBox::InsertDataColumn: nItemId is reserved value BROWSER_INVALIDID" );

#if OSL_DEBUG_LEVEL > 0
    {
        for (auto const& col : mvCols)
            OSL_ENSURE( col->GetId() != nItemId, "BrowseBox::InsertDataColumn: duplicate column Id" );
    }
#endif

    if ( nPos < mvCols.size() )
    {
        mvCols.emplace( mvCols.begin() + nPos, new BrowserColumn( nItemId, rText, nWidth, GetZoom() ) );
    }
    else
    {
        mvCols.emplace_back( new BrowserColumn( nItemId, rText, nWidth, GetZoom() ) );
    }
    if ( nCurColId == 0 )
        nCurColId = nItemId;

    if ( pDataWin->pHeaderBar )
    {
        // Handle column not in the header bar
        sal_uInt16 nHeaderPos = nPos;
        if (nHeaderPos != HEADERBAR_APPEND && GetColumnId(0) == HandleColumnId )
            nHeaderPos--;
        pDataWin->pHeaderBar->InsertItem(
                nItemId, rText, nWidth, nBits, nHeaderPos );
    }
    ColumnInserted( nPos );
}

sal_uInt16 BrowseBox::ToggleSelectedColumn()
{
    sal_uInt16 nSelectedColId = BROWSER_INVALIDID;
    if ( pColSel && pColSel->GetSelectCount() )
    {
        DoHideCursor( "ToggleSelectedColumn" );
        ToggleSelection();
        long nSelected = pColSel->FirstSelected();
        if (nSelected != static_cast<long>(SFX_ENDOFSELECTION))
            nSelectedColId = mvCols[nSelected]->GetId();
        pColSel->SelectAll(false);
    }
    return nSelectedColId;
}

void BrowseBox::SetToggledSelectedColumn(sal_uInt16 _nSelectedColumnId)
{
    if ( pColSel && _nSelectedColumnId != BROWSER_INVALIDID )
    {
        pColSel->Select( GetColumnPos( _nSelectedColumnId ) );
        ToggleSelection();
        SAL_INFO("svtools", "BrowseBox::SetToggledSelectedColumn " << this );
        DoShowCursor( "SetToggledSelectedColumn" );
    }
}

void BrowseBox::FreezeColumn( sal_uInt16 nItemId )
{
    // get the position in the current array
    size_t nItemPos = GetColumnPos( nItemId );
    if ( nItemPos >= mvCols.size() )
        // not available!
        return;

    // doesn't the state change?
    if ( mvCols[ nItemPos ]->IsFrozen() )
        return;

    // remark the column selection
    sal_uInt16 nSelectedColId = ToggleSelectedColumn();

    // to be moved?
    if ( nItemPos != 0 && !mvCols[ nItemPos-1 ]->IsFrozen() )
    {
        // move to the right of the last frozen column
        sal_uInt16 nFirstScrollable = FrozenColCount();
        std::unique_ptr<BrowserColumn> pColumn = std::move(mvCols[ nItemPos ]);
        mvCols.erase( mvCols.begin() + nItemPos );
        nItemPos = nFirstScrollable;
        mvCols.insert( mvCols.begin() + nItemPos, std::move(pColumn) );
    }

    // adjust the number of the first scrollable and visible column
    if ( nFirstCol <= nItemPos )
        nFirstCol = nItemPos + 1;

    // toggle the freeze-state of the column
    mvCols[ nItemPos ]->Freeze();

    // align the scrollbar-range
    UpdateScrollbars();

    // repaint
    Control::Invalidate();
    pDataWin->Invalidate();

    // remember the column selection
    SetToggledSelectedColumn(nSelectedColId);
}


void BrowseBox::SetColumnPos( sal_uInt16 nColumnId, sal_uInt16 nPos )
{
    // never set pos of the handle column
    if ( nColumnId == HandleColumnId )
        return;

    // get the position in the current array
    sal_uInt16 nOldPos = GetColumnPos( nColumnId );
    if ( nOldPos >= mvCols.size() )
        // not available!
        return;

    // does the state change?
    if (nOldPos == nPos)
        return;

    // remark the column selection
    sal_uInt16 nSelectedColId = ToggleSelectedColumn();

    // determine old column area
    Size aDataWinSize( pDataWin->GetSizePixel() );
    if ( pDataWin->pHeaderBar )
        aDataWinSize.AdjustHeight(pDataWin->pHeaderBar->GetSizePixel().Height() );

    tools::Rectangle aFromRect( GetFieldRect( nColumnId) );
    aFromRect.AdjustRight(2*MIN_COLUMNWIDTH );

    sal_uInt16 nNextPos = nOldPos + 1;
    if ( nOldPos > nPos )
        nNextPos = nOldPos - 1;

    BrowserColumn *pNextCol = mvCols[ nNextPos ].get();
    tools::Rectangle aNextRect(GetFieldRect( pNextCol->GetId() ));

    // move column internally
    {
        std::unique_ptr<BrowserColumn> pTemp = std::move(mvCols[nOldPos]);
        mvCols.erase( mvCols.begin() + nOldPos );
        mvCols.insert( mvCols.begin() + nPos, std::move(pTemp) );
    }

    // determine new column area
    tools::Rectangle aToRect( GetFieldRect( nColumnId ) );
    aToRect.AdjustRight(2*MIN_COLUMNWIDTH );

    // do scroll, let redraw
    if( pDataWin->GetBackground().IsScrollable() )
    {
        long nScroll = -aFromRect.GetWidth();
        tools::Rectangle aScrollArea;
        if ( nOldPos > nPos )
        {
            long nFrozenWidth = GetFrozenWidth();
            if ( aToRect.Left() < nFrozenWidth )
                aToRect.SetLeft( nFrozenWidth );
            aScrollArea = tools::Rectangle(Point(aToRect.Left(),0),
                                    Point(aNextRect.Right(),aDataWinSize.Height()));
            nScroll *= -1; // reverse direction
        }
        else
            aScrollArea = tools::Rectangle(Point(aNextRect.Left(),0),
                                    Point(aToRect.Right(),aDataWinSize.Height()));

        pDataWin->Scroll( nScroll, 0, aScrollArea );
        aToRect.SetTop( 0 );
        aToRect.SetBottom( aScrollArea.Bottom() );
        Invalidate( aToRect );
    }
    else
        pDataWin->Window::Invalidate( InvalidateFlags::NoChildren );

    // adjust header bar positions
    if ( pDataWin->pHeaderBar )
    {
        sal_uInt16 nNewPos = nPos;
        if ( GetColumnId(0) == HandleColumnId )
            --nNewPos;
        pDataWin->pHeaderBar->MoveItem(nColumnId,nNewPos);
    }
    // remember the column selection
    SetToggledSelectedColumn(nSelectedColId);

    if ( !isAccessibleAlive() )
        return;

    commitTableEvent(
        TABLE_MODEL_CHANGED,
        makeAny( AccessibleTableModelChange(
                    DELETE,
                    0,
                    GetRowCount(),
                    nOldPos,
                    nOldPos
                )
        ),
        Any()
    );

    commitTableEvent(
        TABLE_MODEL_CHANGED,
        makeAny( AccessibleTableModelChange(
                    INSERT,
                    0,
                    GetRowCount(),
                    nPos,
                    nPos
                )
        ),
        Any()
    );

}


void BrowseBox::SetColumnTitle( sal_uInt16 nItemId, const OUString& rTitle )
{

    // never set title of the handle-column
    if ( nItemId == HandleColumnId )
        return;

    // get the position in the current array
    sal_uInt16 nItemPos = GetColumnPos( nItemId );
    if ( nItemPos >= mvCols.size() )
        // not available!
        return;

    // does the state change?
    BrowserColumn *pCol = mvCols[ nItemPos ].get();
    if ( pCol->Title() == rTitle )
        return;

    OUString sOld(pCol->Title());

    pCol->Title() = rTitle;

    // adjust headerbar column
    if ( pDataWin->pHeaderBar )
        pDataWin->pHeaderBar->SetItemText( nItemId, rTitle );
    else
    {
        // redraw visible columns
        if ( GetUpdateMode() && ( pCol->IsFrozen() || nItemPos > nFirstCol ) )
            Invalidate( tools::Rectangle( Point(0,0),
                Size( GetOutputSizePixel().Width(), GetTitleHeight() ) ) );
    }

    if ( isAccessibleAlive() )
    {
        commitTableEvent(   TABLE_COLUMN_DESCRIPTION_CHANGED,
            makeAny( rTitle ),
            makeAny( sOld )
        );
    }
}


void BrowseBox::SetColumnWidth( sal_uInt16 nItemId, sal_uLong nWidth )
{

    // get the position in the current array
    size_t nItemPos = GetColumnPos( nItemId );
    if ( nItemPos >= mvCols.size() )
        return;

    // does the state change?
    if ( !(nWidth >= LONG_MAX || mvCols[ nItemPos ]->Width() != nWidth) )
        return;

    long nOldWidth = mvCols[ nItemPos ]->Width();

    // adjust last column, if necessary
    if ( IsVisible() && nItemPos == mvCols.size() - 1 )
    {
        long nMaxWidth = pDataWin->GetSizePixel().Width();
        nMaxWidth -= pDataWin->bAutoSizeLastCol
                ? GetFieldRect(nItemId).Left()
                : GetFrozenWidth();
        if ( pDataWin->bAutoSizeLastCol || nWidth > o3tl::make_unsigned(nMaxWidth) )
        {
            nWidth = nMaxWidth > 16 ? nMaxWidth : nOldWidth;
        }
    }

    // OV
    // In AutoSizeLastColumn(), we call SetColumnWidth with nWidth==0xffff.
    // Thus, check here, if the width has actually changed.
    if( static_cast<sal_uLong>(nOldWidth) == nWidth )
        return;

    // do we want to display the change immediately?
    bool bUpdate = GetUpdateMode() &&
                   ( mvCols[ nItemPos ]->IsFrozen() || nItemPos >= nFirstCol );

    if ( bUpdate )
    {
        // Selection hidden
        DoHideCursor( "SetColumnWidth" );
        ToggleSelection();
        //!pDataWin->Update();
        //!Control::Update();
    }

    // set width
    mvCols[ nItemPos ]->SetWidth(nWidth, GetZoom());

    // scroll and invalidate
    if ( bUpdate )
    {
        // get X-Pos of the column changed
        long nX = 0;
        for ( size_t nCol = 0; nCol < nItemPos; ++nCol )
        {
            BrowserColumn *pCol = mvCols[ nCol ].get();
            if ( pCol->IsFrozen() || nCol >= nFirstCol )
                nX += pCol->Width();
        }

        // actually scroll+invalidate
        pDataWin->SetClipRegion();
        bool bSelVis = bSelectionIsVisible;
        bSelectionIsVisible = false;
        if( GetBackground().IsScrollable() )
        {

            tools::Rectangle aScrRect( nX + std::min( static_cast<sal_uLong>(nOldWidth), nWidth ), 0,
                                GetSizePixel().Width() , // the header is longer than the datawin
                                pDataWin->GetPosPixel().Y() - 1 );
            Control::Scroll( nWidth-nOldWidth, 0, aScrRect, SCROLL_FLAGS );
            aScrRect.SetBottom( pDataWin->GetSizePixel().Height() );
            pDataWin->Scroll( nWidth-nOldWidth, 0, aScrRect, SCROLL_FLAGS );
            tools::Rectangle aInvRect( nX, 0, nX + std::max( nWidth, static_cast<sal_uLong>(nOldWidth) ), USHRT_MAX );
            Control::Invalidate( aInvRect, InvalidateFlags::NoChildren );
            pDataWin->Invalidate( aInvRect );
        }
        else
        {
            Control::Invalidate( InvalidateFlags::NoChildren );
            pDataWin->Window::Invalidate( InvalidateFlags::NoChildren );
        }


        //!pDataWin->Update();
        //!Control::Update();
        bSelectionIsVisible = bSelVis;
        ToggleSelection();
        DoShowCursor( "SetColumnWidth" );
    }
    UpdateScrollbars();

    // adjust headerbar column
    if ( pDataWin->pHeaderBar )
        pDataWin->pHeaderBar->SetItemSize(
                nItemId ? nItemId : USHRT_MAX - 1, nWidth );

    // adjust last column
    if ( nItemPos != mvCols.size() - 1 )
        AutoSizeLastColumn();
}


void BrowseBox::AutoSizeLastColumn()
{
    if ( pDataWin->bAutoSizeLastCol &&
         pDataWin->GetUpdateMode() )
    {
        sal_uInt16 nId = GetColumnId( static_cast<sal_uInt16>(mvCols.size()) - 1 );
        SetColumnWidth( nId, LONG_MAX );
        ColumnResized( nId );
    }
}


void BrowseBox::RemoveColumn( sal_uInt16 nItemId )
{

    // get column position
    sal_uInt16 nPos = GetColumnPos(nItemId);
    if ( nPos >= ColCount() )
        // not available
        return;

    // correct column selection
    if ( pColSel )
        pColSel->Remove( nPos );

    // correct column cursor
    if ( nCurColId == nItemId )
        nCurColId = 0;

    // delete column
    mvCols.erase( mvCols.begin() + nPos );
    if ( nFirstCol >= nPos && nFirstCol > FrozenColCount() )
    {
        OSL_ENSURE(nFirstCol > 0,"FirstCol must be greater zero!");
        --nFirstCol;
    }

    // handlecolumn not in headerbar
    if (nItemId)
    {
        if ( pDataWin->pHeaderBar )
            pDataWin->pHeaderBar->RemoveItem( nItemId );
    }
    else
    {
        // adjust headerbar
        if ( pDataWin->pHeaderBar )
        {
            pDataWin->pHeaderBar->SetPosSizePixel(
                        Point(0, 0),
                        Size( GetOutputSizePixel().Width(), GetTitleHeight() )
                        );
        }
    }

    // correct vertical scrollbar
    UpdateScrollbars();

    // trigger repaint, if necessary
    if ( GetUpdateMode() )
    {
        pDataWin->Invalidate();
        Control::Invalidate();
        if ( pDataWin->bAutoSizeLastCol && nPos ==ColCount() )
            SetColumnWidth( GetColumnId( nPos - 1 ), LONG_MAX );
    }

    if ( !isAccessibleAlive() )
        return;

    commitTableEvent(
        TABLE_MODEL_CHANGED,
        makeAny( AccessibleTableModelChange(    DELETE,
                                                0,
                                                GetRowCount(),
                                                nPos,
                                                nPos
                                           )
        ),
        Any()
    );

    commitHeaderBarEvent(
        CHILD,
        Any(),
        makeAny( CreateAccessibleColumnHeader( nPos ) ),
        true
    );
}


void BrowseBox::RemoveColumns()
{
    size_t nOldCount = mvCols.size();

    // remove all columns
    mvCols.clear();

    // correct column selection
    if ( pColSel )
    {
        pColSel->SelectAll(false);
        pColSel->SetTotalRange( Range( 0, 0 ) );
    }

    // correct column cursor
    nCurColId = 0;
    nFirstCol = 0;

    if ( pDataWin->pHeaderBar )
        pDataWin->pHeaderBar->Clear( );

    // correct vertical scrollbar
    UpdateScrollbars();

    // trigger repaint if necessary
    if ( GetUpdateMode() )
    {
        pDataWin->Invalidate();
        Control::Invalidate();
    }

    if ( !isAccessibleAlive() )
        return;

    if ( mvCols.size() == nOldCount )
        return;

    // all columns should be removed, so we remove the column header bar and append it again
    // to avoid to notify every column remove
    commitBrowseBoxEvent(
        CHILD,
        Any(),
        makeAny(m_pImpl->getAccessibleHeaderBar(vcl::BBTYPE_COLUMNHEADERBAR))
    );

    // and now append it again
    commitBrowseBoxEvent(
        CHILD,
        makeAny(m_pImpl->getAccessibleHeaderBar(vcl::BBTYPE_COLUMNHEADERBAR)),
        Any()
    );

    // notify a table model change
    commitTableEvent(
        TABLE_MODEL_CHANGED,
        makeAny ( AccessibleTableModelChange( DELETE,
                        0,
                        GetRowCount(),
                        0,
                        nOldCount
                    )
                ),
        Any()
    );
}


OUString BrowseBox::GetColumnTitle( sal_uInt16 nId ) const
{

    sal_uInt16 nItemPos = GetColumnPos( nId );
    if ( nItemPos >= mvCols.size() )
        return OUString();
    return mvCols[ nItemPos ]->Title();
}


long BrowseBox::GetRowCount() const
{
    return nRowCount;
}


sal_uInt16 BrowseBox::ColCount() const
{

    return static_cast<sal_uInt16>(mvCols.size());
}


long BrowseBox::ImpGetDataRowHeight() const
{

    BrowseBox *pThis = const_cast<BrowseBox*>(this);
    pThis->nDataRowHeight = pThis->CalcReverseZoom(pDataWin->GetTextHeight() + 2);
    pThis->Resize();
    pDataWin->Invalidate();
    return nDataRowHeight;
}


void BrowseBox::SetDataRowHeight( long nPixel )
{

    nDataRowHeight = CalcReverseZoom(nPixel);
    Resize();
    pDataWin->Invalidate();
}


void BrowseBox::SetTitleLines( sal_uInt16 nLines )
{

    nTitleLines = nLines;
}


long BrowseBox::ScrollColumns( long nCols )
{

    if ( nFirstCol + nCols < 0 ||
         nFirstCol + nCols >= static_cast<long>(mvCols.size()) )
        return 0;

    // implicitly hides cursor while scrolling
    StartScroll();
    bScrolling = true;
    bool bScrollable = pDataWin->GetBackground().IsScrollable();
    bool bInvalidateView = false;

    // scrolling one column to the right?
    if ( nCols == 1 )
    {
        // update internal value and scrollbar
        ++nFirstCol;
        aHScroll->SetThumbPos( nFirstCol - FrozenColCount() );

        if ( !bScrollable )
        {
            bInvalidateView = true;
        }
        else
        {
            long nDelta = mvCols[ nFirstCol-1 ]->Width();
            long nFrozenWidth = GetFrozenWidth();

            tools::Rectangle aScrollRect(  Point( nFrozenWidth + nDelta, 0 ),
                                    Size ( GetOutputSizePixel().Width() - nFrozenWidth - nDelta,
                                           GetTitleHeight() - 1
                                         ) );

            // scroll the header bar area (if there is no dedicated HeaderBar control)
            if ( !pDataWin->pHeaderBar && nTitleLines )
            {
                // actually scroll
                Scroll( -nDelta, 0, aScrollRect, SCROLL_FLAGS );

                // invalidate the area of the column which was scrolled out to the left hand side
                tools::Rectangle aInvalidateRect( aScrollRect );
                aInvalidateRect.SetLeft( nFrozenWidth );
                aInvalidateRect.SetRight( nFrozenWidth + nDelta - 1 );
                Invalidate( aInvalidateRect );
            }

            // scroll the data-area
            aScrollRect.SetBottom( pDataWin->GetOutputSizePixel().Height() );

            // actually scroll
            pDataWin->Scroll( -nDelta, 0, aScrollRect, SCROLL_FLAGS );

            // invalidate the area of the column which was scrolled out to the left hand side
            aScrollRect.SetLeft( nFrozenWidth );
            aScrollRect.SetRight( nFrozenWidth + nDelta - 1 );
            pDataWin->Invalidate( aScrollRect );
        }
    }

    // scrolling one column to the left?
    else if ( nCols == -1 )
    {
        --nFirstCol;
        aHScroll->SetThumbPos( nFirstCol - FrozenColCount() );

        if ( !bScrollable )
        {
            bInvalidateView = true;
        }
        else
        {
            long nDelta = mvCols[ nFirstCol ]->Width();
            long nFrozenWidth = GetFrozenWidth();

            tools::Rectangle aScrollRect(  Point(  nFrozenWidth, 0 ),
                                    Size (  GetOutputSizePixel().Width() - nFrozenWidth,
                                            GetTitleHeight() - 1
                                         ) );

            // scroll the header bar area (if there is no dedicated HeaderBar control)
            if ( !pDataWin->pHeaderBar && nTitleLines )
            {
                Scroll( nDelta, 0, aScrollRect, SCROLL_FLAGS );
            }

            // scroll the data-area
            aScrollRect.SetBottom( pDataWin->GetOutputSizePixel().Height() );
            pDataWin->Scroll( nDelta, 0, aScrollRect, SCROLL_FLAGS );
        }
    }
    else
    {
        if ( GetUpdateMode() )
        {
            Invalidate( tools::Rectangle(
                Point( GetFrozenWidth(), 0 ),
                Size( GetOutputSizePixel().Width(), GetTitleHeight() ) ) );
            pDataWin->Invalidate( tools::Rectangle(
                Point( GetFrozenWidth(), 0 ),
                pDataWin->GetSizePixel() ) );
        }

        nFirstCol = nFirstCol + static_cast<sal_uInt16>(nCols);
        aHScroll->SetThumbPos( nFirstCol - FrozenColCount() );
    }

    // adjust external headerbar, if necessary
    if ( pDataWin->pHeaderBar )
    {
        long nWidth = 0;
        for ( size_t nCol = 0;
              nCol < mvCols.size() && nCol < nFirstCol;
              ++nCol )
        {
            // not the handle column
            if ( mvCols[ nCol ]->GetId() )
                nWidth += mvCols[ nCol ]->Width();
        }

        pDataWin->pHeaderBar->SetOffset( nWidth );
    }

    if( bInvalidateView )
    {
        Control::Invalidate( InvalidateFlags::NoChildren );
        pDataWin->Window::Invalidate( InvalidateFlags::NoChildren );
    }

    // implicitly show cursor after scrolling
    if ( nCols )
    {
        pDataWin->Update();
        PaintImmediately();
    }
    bScrolling = false;
    EndScroll();

    return nCols;
}


long BrowseBox::ScrollRows( long nRows )
{
    // compute new top row
    long nTmpMin = std::min( static_cast<long>(nTopRow + nRows), static_cast<long>(nRowCount - 1) );

    long nNewTopRow = std::max<long>( nTmpMin, 0 );

    if ( nNewTopRow == nTopRow )
        return 0;

    sal_uInt16 nVisibleRows =
        static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1);

    VisibleRowsChanged(nNewTopRow, nVisibleRows);

    // compute new top row again (nTopRow might have changed!)
    nTmpMin = std::min( static_cast<long>(nTopRow + nRows), static_cast<long>(nRowCount - 1) );

    nNewTopRow = std::max<long>( nTmpMin, 0 );

    StartScroll();

    // scroll area on screen and/or repaint
    long nDeltaY = GetDataRowHeight() * ( nNewTopRow - nTopRow );
    long nOldTopRow = nTopRow;
    nTopRow = nNewTopRow;

    if ( GetUpdateMode() )
    {
        pVScroll->SetRange( Range( 0L, nRowCount ) );
        pVScroll->SetThumbPos( nTopRow );

        if( pDataWin->GetBackground().IsScrollable() &&
            std::abs( nDeltaY ) > 0 &&
            std::abs( nDeltaY ) < pDataWin->GetSizePixel().Height() )
        {
            pDataWin->Scroll( 0, static_cast<short>(-nDeltaY), SCROLL_FLAGS );
        }
        else
            pDataWin->Invalidate();

        if ( nTopRow - nOldTopRow )
            pDataWin->Update();
    }

    EndScroll();

    return nTopRow - nOldTopRow;
}


void BrowseBox::RowModified( long nRow, sal_uInt16 nColId )
{

    if ( !GetUpdateMode() )
        return;

    tools::Rectangle aRect;
    if ( nColId == BROWSER_INVALIDID )
        // invalidate the whole row
        aRect = tools::Rectangle( Point( 0, (nRow-nTopRow) * GetDataRowHeight() ),
                    Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) );
    else
    {
        // invalidate the specific field
        aRect = GetFieldRectPixel( nRow, nColId, false );
    }
    pDataWin->Invalidate( aRect );
}


void BrowseBox::Clear()
{

    // adjust the total number of rows
    DoHideCursor( "Clear" );
    long nOldRowCount = nRowCount;
    nRowCount = 0;
    if(bMultiSelection)
    {
        assert(uRow.pSel);
        uRow.pSel->Reset();
    }
    else
        uRow.nSel = BROWSER_ENDOFSELECTION;
    nCurRow = BROWSER_ENDOFSELECTION;
    nTopRow = 0;
    nCurColId = 0;

    // nFirstCol may not be reset, else the scrolling code will become confused.
    // nFirstCol may only be changed when adding or deleting columns
    // nFirstCol = 0; -> wrong!
    aHScroll->SetThumbPos( 0 );
    pVScroll->SetThumbPos( 0 );

    Invalidate();
    UpdateScrollbars();
    SetNoSelection();
    DoShowCursor( "Clear" );
    CursorMoved();

    if ( !isAccessibleAlive() )
        return;

    // all rows should be removed, so we remove the row header bar and append it again
    // to avoid to notify every row remove
    if ( nOldRowCount == nRowCount )
        return;

    commitBrowseBoxEvent(
        CHILD,
        Any(),
        makeAny( m_pImpl->getAccessibleHeaderBar( vcl::BBTYPE_ROWHEADERBAR ) )
    );

    // and now append it again
    commitBrowseBoxEvent(
        CHILD,
        makeAny( m_pImpl->getAccessibleHeaderBar( vcl::BBTYPE_ROWHEADERBAR ) ),
        Any()
    );

    // notify a table model change
    commitTableEvent(
        TABLE_MODEL_CHANGED,
        makeAny( AccessibleTableModelChange( DELETE,
            0,
            nOldRowCount,
            0,
            GetColumnCount())
        ),
        Any()
    );
}

void BrowseBox::RowInserted( long nRow, long nNumRows, bool bDoPaint, bool bKeepSelection )
{

    if (nRow < 0)
        nRow = 0;
    else if (nRow > nRowCount) // maximal = nRowCount
        nRow = nRowCount;

    if ( nNumRows <= 0 )
        return;

    // adjust total row count
    bool bLastRow = nRow >= nRowCount;
    nRowCount += nNumRows;

    DoHideCursor( "RowInserted" );

    // must we paint the new rows?
    long nOldCurRow = nCurRow;
    Size aSz = pDataWin->GetOutputSizePixel();
    if ( bDoPaint && nRow >= nTopRow &&
         nRow <= nTopRow + aSz.Height() / GetDataRowHeight() )
    {
        long nY = (nRow-nTopRow) * GetDataRowHeight();
        if ( !bLastRow )
        {
            // scroll down the rows behind the new row
            pDataWin->SetClipRegion();
            if( pDataWin->GetBackground().IsScrollable() )
            {
                pDataWin->Scroll( 0, GetDataRowHeight() * nNumRows,
                                tools::Rectangle( Point( 0, nY ),
                                        Size( aSz.Width(), aSz.Height() - nY ) ),
                                SCROLL_FLAGS );
            }
            else
                pDataWin->Window::Invalidate( InvalidateFlags::NoChildren );
        }
        else
            // scroll would cause a repaint, so we must explicitly invalidate
            pDataWin->Invalidate( tools::Rectangle( Point( 0, nY ),
                         Size( aSz.Width(), nNumRows * GetDataRowHeight() ) ) );
    }

    // correct top row if necessary
    if ( nRow < nTopRow )
        nTopRow += nNumRows;

    // adjust the selection
    if ( bMultiSelection )
        uRow.pSel->Insert( nRow, nNumRows );
    else if ( uRow.nSel != BROWSER_ENDOFSELECTION && nRow <= uRow.nSel )
        uRow.nSel += nNumRows;

    // adjust the cursor
    if ( nCurRow == BROWSER_ENDOFSELECTION )
        GoToRow( 0, false, bKeepSelection );
    else if ( nRow <= nCurRow )
    {
        nCurRow += nNumRows;
        GoToRow( nCurRow, false, bKeepSelection );
    }

    // adjust the vertical scrollbar
    if ( bDoPaint )
    {
        UpdateScrollbars();
        AutoSizeLastColumn();
    }

    DoShowCursor( "RowInserted" );
    // notify accessible that rows were inserted
    if ( isAccessibleAlive() )
    {
        commitTableEvent(
            TABLE_MODEL_CHANGED,
            makeAny( AccessibleTableModelChange(
                        INSERT,
                        nRow,
                        nRow + nNumRows,
                        0,
                        GetColumnCount()
                    )
            ),
            Any()
        );

        for (long i = nRow+1 ; i <= nRowCount ; ++i)
        {
            commitHeaderBarEvent(
                CHILD,
                makeAny( CreateAccessibleRowHeader( i ) ),
                Any(),
                false
            );
        }
    }

    if ( nCurRow != nOldCurRow )
        CursorMoved();

    DBG_ASSERT(nRowCount > 0,"BrowseBox: nRowCount <= 0");
    DBG_ASSERT(nCurRow >= 0,"BrowseBox: nCurRow < 0");
    DBG_ASSERT(nCurRow < nRowCount,"nCurRow >= nRowCount");
}


void BrowseBox::RowRemoved( long nRow, long nNumRows, bool bDoPaint )
{

    if ( nRow < 0 )
        nRow = 0;
    else if ( nRow >= nRowCount )
        nRow = nRowCount - 1;

    if ( nNumRows <= 0 )
        return;

    if ( nRowCount <= 0 )
        return;

    if ( bDoPaint )
    {
        // hide cursor and selection
        SAL_INFO("svtools", "BrowseBox::HideCursor " << this );
        ToggleSelection();
        DoHideCursor( "RowRemoved" );
    }

    // adjust total row count
    nRowCount -= nNumRows;
    if (nRowCount < 0) nRowCount = 0;
    long nOldCurRow = nCurRow;

    // adjust the selection
    if ( bMultiSelection )
        // uRow.pSel->Remove( nRow, nNumRows );
        for ( long i = 0; i < nNumRows; i++ )
            uRow.pSel->Remove( nRow );
    else if ( nRow < uRow.nSel && uRow.nSel >= nNumRows )
        uRow.nSel -= nNumRows;
    else if ( nRow <= uRow.nSel )
        uRow.nSel = BROWSER_ENDOFSELECTION;

    // adjust the cursor
    if ( nRowCount == 0 )   // don't compare nRowCount with nNumRows as nNumRows already was subtracted from nRowCount
        nCurRow = BROWSER_ENDOFSELECTION;
    else if ( nRow < nCurRow )
    {
        nCurRow -= std::min( nCurRow - nRow, nNumRows );
        // with the above nCurRow points a) to the first row after the removed block or b) to the same line
        // as before, but moved up nNumRows
        // case a) needs an additional correction if the last n lines were deleted, as 'the first row after the
        // removed block' is an invalid position then
        // FS - 09/28/99 - 68429
        if (nCurRow == nRowCount)
            --nCurRow;
    }
    else if( nRow == nCurRow && nCurRow == nRowCount )
        nCurRow = nRowCount-1;

    // is the deleted row visible?
    Size aSz = pDataWin->GetOutputSizePixel();
    if ( nRow >= nTopRow &&
         nRow <= nTopRow + aSz.Height() / GetDataRowHeight() )
    {
        if ( bDoPaint )
        {
            // scroll up the rows behind the deleted row
            // if there are Rows behind
            if (nRow < nRowCount)
            {
                long nY = (nRow-nTopRow) * GetDataRowHeight();
                pDataWin->SetClipRegion();
                if( pDataWin->GetBackground().IsScrollable() )
                {
                    pDataWin->Scroll( 0, - static_cast<short>(GetDataRowHeight()) * nNumRows,
                        tools::Rectangle( Point( 0, nY ), Size( aSz.Width(),
                            aSz.Height() - nY + nNumRows*GetDataRowHeight() ) ),
                            SCROLL_FLAGS );
                }
                else
                    pDataWin->Window::Invalidate( InvalidateFlags::NoChildren );
            }
            else
            {
                // Repaint the Rect of the deleted row
                tools::Rectangle aRect(
                        Point( 0, (nRow-nTopRow)*GetDataRowHeight() ),
                        Size( pDataWin->GetSizePixel().Width(),
                              nNumRows * GetDataRowHeight() ) );
                pDataWin->Invalidate( aRect );
            }
        }
    }
    // is the deleted row above of the visible area?
    else if ( nRow < nTopRow )
        nTopRow = nTopRow >= nNumRows ? nTopRow-nNumRows : 0;

    if ( bDoPaint )
    {
        // reshow cursor and selection
        ToggleSelection();
        SAL_INFO("svtools", "BrowseBox::ShowCursor " << this );
        DoShowCursor( "RowRemoved" );

        // adjust the vertical scrollbar
        UpdateScrollbars();
        AutoSizeLastColumn();
    }

    if ( isAccessibleAlive() )
    {
        if ( nRowCount == 0 )
        {
            // all columns should be removed, so we remove the column header bar and append it again
            // to avoid to notify every column remove
            commitBrowseBoxEvent(
                CHILD,
                Any(),
                makeAny( m_pImpl->getAccessibleHeaderBar( vcl::BBTYPE_ROWHEADERBAR ) )
            );

            // and now append it again
            commitBrowseBoxEvent(
                CHILD,
                makeAny(m_pImpl->getAccessibleHeaderBar(vcl::BBTYPE_ROWHEADERBAR)),
                Any()
            );
            commitBrowseBoxEvent(
                CHILD,
                Any(),
                makeAny( m_pImpl->getAccessibleTable() )
            );

            // and now append it again
            commitBrowseBoxEvent(
                CHILD,
                makeAny( m_pImpl->getAccessibleTable() ),
                Any()
            );
        }
        else
        {
            commitTableEvent(
                TABLE_MODEL_CHANGED,
                makeAny( AccessibleTableModelChange(
                            DELETE,
                            nRow,
                            nRow + nNumRows,
                            0,
                            GetColumnCount()
                            )
                ),
                Any()
            );

            for (long i = nRow+1 ; i <= (nRow+nNumRows) ; ++i)
            {
                commitHeaderBarEvent(
                    CHILD,
                    Any(),
                    makeAny( CreateAccessibleRowHeader( i ) ),
                    false
                );
            }
        }
    }

    if ( nOldCurRow != nCurRow )
        CursorMoved();

    DBG_ASSERT(nRowCount >= 0,"BrowseBox: nRowCount < 0");
    DBG_ASSERT(nCurRow >= 0 || nRowCount == 0,"BrowseBox: nCurRow < 0 && nRowCount != 0");
    DBG_ASSERT(nCurRow < nRowCount,"nCurRow >= nRowCount");
}


bool BrowseBox::GoToRow( long nRow)
{
    return GoToRow(nRow, false);
}


bool BrowseBox::GoToRow( long nRow, bool bRowColMove, bool bKeepSelection )
{

    long nOldCurRow = nCurRow;

    // nothing to do?
    if ( nRow == nCurRow && ( bMultiSelection || uRow.nSel == nRow ) )
        return true;

    // out of range?
    if ( nRow < 0 || nRow >= nRowCount )
        return false;

    // not allowed?
    if ( !bRowColMove && !IsCursorMoveAllowed( nRow, nCurColId ) )
        return false;

    // compute the last visible row
    Size aSz( pDataWin->GetSizePixel() );
    sal_uInt16 nVisibleRows = sal_uInt16( aSz.Height() / GetDataRowHeight() - 1 );
    long nLastRow = nTopRow + nVisibleRows;

    // suspend Updates
    pDataWin->EnterUpdateLock();

    // remove old highlight, if necessary
    if ( !bMultiSelection && !bKeepSelection )
        ToggleSelection();
    DoHideCursor( "GoToRow" );

    // must we scroll?
    bool bWasVisible = bSelectionIsVisible;
    if (! bMultiSelection)
    {
        if( !bKeepSelection )
            bSelectionIsVisible = false;
    }
    if ( nRow < nTopRow )
        ScrollRows( nRow - nTopRow );
    else if ( nRow > nLastRow )
        ScrollRows( nRow - nLastRow );
    bSelectionIsVisible = bWasVisible;

    // adjust cursor (selection) and thumb
    if ( GetUpdateMode() )
        pVScroll->SetThumbPos( nTopRow );

    // relative positioning (because nCurRow might have changed in the meantime)!
    if (nCurRow != BROWSER_ENDOFSELECTION )
        nCurRow = nCurRow + (nRow - nOldCurRow);

    // make sure that the current position is valid
    if (nCurRow == BROWSER_ENDOFSELECTION && nRowCount > 0)
        nCurRow = 0;
    else if ( nCurRow >= nRowCount )
        nCurRow = nRowCount - 1;
    aSelRange = Range( nCurRow, nCurRow );

    // display new highlight if necessary
    if ( !bMultiSelection && !bKeepSelection )
        uRow.nSel = nRow;

    // resume Updates
    pDataWin->LeaveUpdateLock();

    // Cursor+Highlight
    if ( !bMultiSelection && !bKeepSelection)
        ToggleSelection();
    DoShowCursor( "GoToRow" );
    if ( !bRowColMove  && nOldCurRow != nCurRow )
        CursorMoved();

    if ( !bMultiSelection && !bKeepSelection )
    {
        if ( !bSelecting )
            Select();
        else
            bSelect = true;
    }
    return true;
}


bool BrowseBox::GoToColumnId( sal_uInt16 nColId)
{
    return GoToColumnId(nColId, true);
}


bool BrowseBox::GoToColumnId( sal_uInt16 nColId, bool bMakeVisible, bool bRowColMove)
{
    if (!bColumnCursor)
        return false;

    // allowed?
    if (!bRowColMove && !IsCursorMoveAllowed( nCurRow, nColId ) )
        return false;

    if ( nColId != nCurColId || (bMakeVisible && !IsFieldVisible(nCurRow, nColId, true)))
    {
        sal_uInt16 nNewPos = GetColumnPos(nColId);
        BrowserColumn* pColumn = (nNewPos < mvCols.size()) ? mvCols[ nNewPos ].get() : nullptr;
        DBG_ASSERT( pColumn, "no column object - invalid id?" );
        if ( !pColumn )
            return false;

        DoHideCursor( "GoToColumnId" );
        nCurColId = nColId;

        bool bScrolled = false;

        sal_uInt16 nFirstPos = nFirstCol;
        sal_uInt16 nWidth = static_cast<sal_uInt16>(pColumn->Width());
        sal_uInt16 nLastPos = GetColumnAtXPosPixel(
                            pDataWin->GetSizePixel().Width()-nWidth );
        sal_uInt16 nFrozen = FrozenColCount();
        if ( bMakeVisible && nLastPos &&
             nNewPos >= nFrozen && ( nNewPos < nFirstPos || nNewPos > nLastPos ) )
        {
            if ( nNewPos < nFirstPos )
                ScrollColumns( nNewPos-nFirstPos );
            else if ( nNewPos > nLastPos )
                ScrollColumns( nNewPos-nLastPos );
            bScrolled = true;
        }

        DoShowCursor( "GoToColumnId" );
        if (!bRowColMove)
        {
            //try to move to nCurRow, nColId
            CursorMoveAttempt aAttempt(nCurRow, nColId, bScrolled);
            //Detect if we are already in a call to BrowseBox::GoToColumnId
            //but the attempt is impossible and we are simply recursing
            //into BrowseBox::GoToColumnId with the same impossible to
            //fulfill conditions
            if (m_aGotoStack.empty() || aAttempt != m_aGotoStack.top())
            {
                m_aGotoStack.push(aAttempt);
                CursorMoved();
                m_aGotoStack.pop();
            }
        }
        return true;
    }
    return true;
}


bool BrowseBox::GoToRowColumnId( long nRow, sal_uInt16 nColId )
{

    // out of range?
    if ( nRow < 0 || nRow >= nRowCount )
        return false;

    if (!bColumnCursor)
        return false;

    // nothing to do ?
    if ( nRow == nCurRow && ( bMultiSelection || uRow.nSel == nRow ) &&
         nColId == nCurColId && IsFieldVisible(nCurRow, nColId, true))
        return true;

    // allowed?
    if (!IsCursorMoveAllowed(nRow, nColId))
        return false;

    DoHideCursor( "GoToRowColumnId" );
    bool bMoved = GoToRow(nRow, true) && GoToColumnId(nColId, true, true);
    DoShowCursor( "GoToRowColumnId" );

    if (bMoved)
        CursorMoved();

    return bMoved;
}


void BrowseBox::SetNoSelection()
{

    // is there no selection
    if ( ( !pColSel || !pColSel->GetSelectCount() ) &&
         ( ( !bMultiSelection && uRow.nSel == BROWSER_ENDOFSELECTION ) ||
           ( bMultiSelection && !uRow.pSel->GetSelectCount() ) ) )
        // nothing to do
        return;

    SAL_INFO("svtools", "BrowseBox::HideCursor " << this );
    ToggleSelection();

    // unselect all
    if ( bMultiSelection )
        uRow.pSel->SelectAll(false);
    else
        uRow.nSel = BROWSER_ENDOFSELECTION;
    if ( pColSel )
        pColSel->SelectAll(false);
    if ( !bSelecting )
        Select();
    else
        bSelect = true;

    // restore screen
    SAL_INFO("svtools", "BrowseBox::ShowCursor " << this );

    if ( isAccessibleAlive() )
    {
        commitTableEvent(
            SELECTION_CHANGED,
            Any(),
            Any()
        );
    }
}


void BrowseBox::SelectAll()
{

    if ( !bMultiSelection )
        return;

    SAL_INFO("svtools", "BrowseBox::HideCursor " << this );
    ToggleSelection();

    // select all rows
    if ( pColSel )
        pColSel->SelectAll(false);
    uRow.pSel->SelectAll();

    // don't highlight handle column
    BrowserColumn *pFirstCol = mvCols[ 0 ].get();
    long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width();

    // highlight the row selection
    if ( !bHideSelect )
    {
        tools::Rectangle aHighlightRect;
        sal_uInt16 nVisibleRows =
            static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1);
        for ( long nRow = std::max<long>( nTopRow, uRow.pSel->FirstSelected() );
              nRow != BROWSER_ENDOFSELECTION && nRow < nTopRow + nVisibleRows;
              nRow = uRow.pSel->NextSelected() )
            aHighlightRect.Union( tools::Rectangle(
                Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ),
                Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) ) );
        pDataWin->Invalidate( aHighlightRect );
    }

    if ( !bSelecting )
        Select();
    else
        bSelect = true;

    // restore screen
    SAL_INFO("svtools", "BrowseBox::ShowCursor " << this );

    if ( !isAccessibleAlive() )
        return;

    commitTableEvent(
        SELECTION_CHANGED,
        Any(),
        Any()
    );
    commitHeaderBarEvent(
        SELECTION_CHANGED,
        Any(),
        Any(),
        true
    ); // column header event

    commitHeaderBarEvent(
        SELECTION_CHANGED,
        Any(),
        Any(),
        false
    ); // row header event
}


void BrowseBox::SelectRow( long nRow, bool _bSelect, bool bExpand )
{

    if ( !bMultiSelection )
    {
        // deselecting is impossible, selecting via cursor
        if ( _bSelect )
            GoToRow(nRow, false);
        return;
    }

    SAL_INFO("svtools", "BrowseBox::HideCursor " << this );

    // remove old selection?
    if ( !bExpand || !bMultiSelection )
    {
        ToggleSelection();
        if ( bMultiSelection )
            uRow.pSel->SelectAll(false);
        else
            uRow.nSel = BROWSER_ENDOFSELECTION;
        if ( pColSel )
            pColSel->SelectAll(false);
    }

    // set new selection
    if  (   !bHideSelect
        &&  (   (   bMultiSelection
                &&  uRow.pSel->GetTotalRange().Max() >= nRow
                &&  uRow.pSel->Select( nRow, _bSelect )
                )
            ||  (   !bMultiSelection
                &&  ( uRow.nSel = nRow ) != BROWSER_ENDOFSELECTION )
                )
            )
    {
        // don't highlight handle column
        BrowserColumn *pFirstCol = mvCols[ 0 ].get();
        long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width();

        // highlight only newly selected part
        tools::Rectangle aRect(
            Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ),
            Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) );
        pDataWin->Invalidate( aRect );
    }

    if ( !bSelecting )
        Select();
    else
        bSelect = true;

    // restore screen
    SAL_INFO("svtools", "BrowseBox::ShowCursor " << this );

    if ( !isAccessibleAlive() )
        return;

    commitTableEvent(
        SELECTION_CHANGED,
        Any(),
        Any()
    );
    commitHeaderBarEvent(
        SELECTION_CHANGED,
        Any(),
        Any(),
        false
    ); // row header event
}


long BrowseBox::GetSelectRowCount() const
{

    return bMultiSelection ? uRow.pSel->GetSelectCount() :
           uRow.nSel == BROWSER_ENDOFSELECTION ? 0 : 1;
}


void BrowseBox::SelectColumnPos( sal_uInt16 nNewColPos, bool _bSelect, bool bMakeVisible )
{

    if ( !bColumnCursor || nNewColPos == BROWSER_INVALIDID )
        return;

    if ( !bMultiSelection )
    {
        if ( _bSelect )
            GoToColumnId( mvCols[ nNewColPos ]->GetId(), bMakeVisible );
        return;
    }
    else
    {
        if ( !GoToColumnId( mvCols[ nNewColPos ]->GetId(), bMakeVisible ) )
            return;
    }

    SAL_INFO("svtools", "BrowseBox::HideCursor " << this );
    ToggleSelection();
    if ( bMultiSelection )
        uRow.pSel->SelectAll(false);
    else
        uRow.nSel = BROWSER_ENDOFSELECTION;
    pColSel->SelectAll(false);

    if ( pColSel->Select( nNewColPos, _bSelect ) )
    {
        // GoToColumnId( mvCols->GetObject(nNewColPos)->GetId(), bMakeVisible );

        // only highlight painted areas
        pDataWin->Update();
        tools::Rectangle aFieldRectPix( GetFieldRectPixel( nCurRow, nCurColId, false ) );
        tools::Rectangle aRect(
            Point( aFieldRectPix.Left() - MIN_COLUMNWIDTH, 0 ),
            Size( mvCols[ nNewColPos ]->Width(),
                  pDataWin->GetOutputSizePixel().Height() ) );
        pDataWin->Invalidate( aRect );
        if ( !bSelecting )
            Select();
        else
            bSelect = true;

        if ( isAccessibleAlive() )
        {
            commitTableEvent(
                SELECTION_CHANGED,
                Any(),
                Any()
            );
            commitHeaderBarEvent(
                SELECTION_CHANGED,
                Any(),
                Any(),
                true
            ); // column header event
        }
    }

    // restore screen
    SAL_INFO("svtools", "BrowseBox::ShowCursor " << this );
}


sal_uInt16 BrowseBox::GetSelectColumnCount() const
{

    // while bAutoSelect (==!pColSel), 1 if any rows (yes rows!) else none
    return pColSel ? static_cast<sal_uInt16>(pColSel->GetSelectCount()) :
           nCurRow >= 0 ? 1 : 0;
}


long BrowseBox::FirstSelectedColumn( ) const
{
    return pColSel ? pColSel->FirstSelected() : BROWSER_ENDOFSELECTION;
}


long BrowseBox::FirstSelectedRow()
{

    return bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel;
}


long BrowseBox::NextSelectedRow()
{

    return bMultiSelection ? uRow.pSel->NextSelected() : BROWSER_ENDOFSELECTION;
}


long BrowseBox::LastSelectedRow()
{

    return bMultiSelection ? uRow.pSel->LastSelected() : uRow.nSel;
}


bool BrowseBox::IsRowSelected( long nRow ) const
{

    return bMultiSelection ? uRow.pSel->IsSelected(nRow) : nRow == uRow.nSel;
}


bool BrowseBox::IsColumnSelected( sal_uInt16 nColumnId ) const
{

    return pColSel ? pColSel->IsSelected( GetColumnPos(nColumnId) ) :
                     nCurColId == nColumnId;
}


void BrowseBox::MakeFieldVisible
(
    long    nRow,       // line number of the field (starting with 0)
    sal_uInt16  nColId     // column ID of the field
)

/*  [Description]

    Makes visible the field described in 'nRow' and 'nColId' by scrolling
    accordingly.

*/

{
    if (!pDataWin)
        return;

    Size aTestSize = pDataWin->GetSizePixel();

    if ( !bBootstrapped || aTestSize.IsEmpty() )
        return;

    // is it visible already?
    bool bVisible = IsFieldVisible( nRow, nColId, true/*bComplete*/ );
    if ( bVisible )
        return;

    // calculate column position, field rectangle and painting area
    sal_uInt16 nColPos = GetColumnPos( nColId );
    tools::Rectangle aFieldRect = GetFieldRectPixel( nRow, nColId, false );
    tools::Rectangle aDataRect( Point(0, 0), pDataWin->GetSizePixel() );

    // positioned outside on the left?
    if ( nColPos >= FrozenColCount() && nColPos < nFirstCol )
        // => scroll to the right
        ScrollColumns( nColPos - nFirstCol );

    // while outside on the right
    while ( aDataRect.Right() < aFieldRect.Right() )
    {
        // => scroll to the left
        if ( ScrollColumns( 1 ) != 1 )
            // no more need to scroll
            break;
        aFieldRect = GetFieldRectPixel( nRow, nColId, false );
    }

    // positioned outside above?
    if ( nRow < nTopRow )
        // scroll further to the bottom
        ScrollRows( nRow - nTopRow );

    // positioned outside below?
    long nBottomRow = nTopRow + GetVisibleRows();
    // decrement nBottomRow to make it the number of the last visible line
    // (count starts with 0!).
    // Example: BrowseBox contains exactly one entry. nBottomRow := 0 + 1 - 1
    if( nBottomRow )
        nBottomRow--;

    if ( nRow > nBottomRow )
        // scroll further to the top
        ScrollRows( nRow - nBottomRow );
}


bool BrowseBox::IsFieldVisible( long nRow, sal_uInt16 nColumnId,
                                bool bCompletely ) const
{

    // hidden by frozen column?
    sal_uInt16 nColPos = GetColumnPos( nColumnId );
    if ( nColPos >= FrozenColCount() && nColPos < nFirstCol )
        return false;

    tools::Rectangle aRect( ImplFieldRectPixel( nRow, nColumnId ) );
    if ( aRect.IsEmpty() )
        return false;

    // get the visible area
    tools::Rectangle aOutRect( Point(0, 0), pDataWin->GetOutputSizePixel() );

    if ( bCompletely )
        // test if the field is completely visible
        return aOutRect.IsInside( aRect );
    else
        // test if the field is partly of completely visible
        return !aOutRect.Intersection( aRect ).IsEmpty();
}


tools::Rectangle BrowseBox::GetFieldRectPixel( long nRow, sal_uInt16 nColumnId,
                                        bool bRelToBrowser) const
{

    // get the rectangle relative to DataWin
    tools::Rectangle aRect( ImplFieldRectPixel( nRow, nColumnId ) );
    if ( aRect.IsEmpty() )
        return aRect;

    // adjust relative to BrowseBox's output area
    Point aTopLeft( aRect.TopLeft() );
    if ( bRelToBrowser )
    {
        aTopLeft = pDataWin->OutputToScreenPixel( aTopLeft );
        aTopLeft = ScreenToOutputPixel( aTopLeft );
    }

    return tools::Rectangle( aTopLeft, aRect.GetSize() );
}


tools::Rectangle BrowseBox::GetRowRectPixel( long nRow  ) const
{

    // get the rectangle relative to DataWin
    tools::Rectangle aRect;
    if ( nTopRow > nRow )
        // row is above visible area
        return aRect;
    aRect = tools::Rectangle(
        Point( 0, GetDataRowHeight() * (nRow-nTopRow) ),
        Size( pDataWin->GetOutputSizePixel().Width(), GetDataRowHeight() ) );
    if ( aRect.TopLeft().Y() > pDataWin->GetOutputSizePixel().Height() )
        // row is below visible area
        return aRect;

    // adjust relative to BrowseBox's output area
    Point aTopLeft( aRect.TopLeft() );
    aTopLeft = pDataWin->OutputToScreenPixel( aTopLeft );
    aTopLeft = ScreenToOutputPixel( aTopLeft );

    return tools::Rectangle( aTopLeft, aRect.GetSize() );
}


tools::Rectangle BrowseBox::ImplFieldRectPixel( long nRow, sal_uInt16 nColumnId ) const
{

    // compute the X-coordinate relative to DataWin by accumulation
    long nColX = 0;
    sal_uInt16 nFrozenCols = FrozenColCount();
    size_t nCol;
    for ( nCol = 0;
          nCol < mvCols.size() && mvCols[ nCol ]->GetId() != nColumnId;
          ++nCol )
        if ( mvCols[ nCol ]->IsFrozen() || nCol >= nFirstCol )
            nColX += mvCols[ nCol ]->Width();

    if ( nCol >= mvCols.size() || ( nCol >= nFrozenCols && nCol < nFirstCol ) )
        return tools::Rectangle();

    // compute the Y-coordinate relative to DataWin
    long nRowY = GetDataRowHeight();
    if ( nRow != BROWSER_ENDOFSELECTION ) // #105497# OJ
        nRowY = ( nRow - nTopRow ) * GetDataRowHeight();

    // assemble the Rectangle relative to DataWin
    return tools::Rectangle(
        Point( nColX + MIN_COLUMNWIDTH, nRowY ),
        Size( (mvCols[nCol]->Width() == LONG_MAX
               ? LONG_MAX - (nColX + MIN_COLUMNWIDTH) : mvCols[ nCol ]->Width() - 2*MIN_COLUMNWIDTH),
              GetDataRowHeight() - 1 ) );
}


long BrowseBox::GetRowAtYPosPixel( long nY, bool bRelToBrowser ) const
{

    // compute the Y-coordinate
    if ( bRelToBrowser )
    {
        Point aDataTopLeft = pDataWin->OutputToScreenPixel( Point(0, 0) );
        Point aTopLeft = OutputToScreenPixel( Point(0, 0) );
        nY -= aDataTopLeft.Y() - aTopLeft.Y();
    }

    // no row there (e.g. in the header)
    if ( nY < 0 || nY >= pDataWin->GetOutputSizePixel().Height() )
        return -1;

    return nY / GetDataRowHeight() + nTopRow;
}


tools::Rectangle BrowseBox::GetFieldRect( sal_uInt16 nColumnId ) const
{

    return GetFieldRectPixel( nCurRow, nColumnId );
}


sal_uInt16 BrowseBox::GetColumnAtXPosPixel( long nX ) const
{

    // accumulate the widths of the visible columns
    long nColX = 0;
    for ( size_t nCol = 0; nCol < mvCols.size(); ++nCol )
    {
        BrowserColumn *pCol = mvCols[ nCol ].get();
        if ( pCol->IsFrozen() || nCol >= nFirstCol )
            nColX += pCol->Width();

        if ( nColX > nX )
            return nCol;
    }

    return BROWSER_INVALIDID;
}

bool BrowseBox::ReserveControlArea(sal_uInt16 nWidth)
{
    if (nWidth != nControlAreaWidth)
    {
        OSL_ENSURE(nWidth,"Control area of 0 is not allowed, Use USHRT_MAX instead!");
        nControlAreaWidth = nWidth;
        UpdateScrollbars();
        return true;
    }
    return false;
}

tools::Rectangle BrowseBox::GetControlArea() const
{
    auto nHeight = aHScroll->GetSizePixel().Height();
    return tools::Rectangle(
        Point( 0, GetOutputSizePixel().Height() - nHeight ),
        Size( GetOutputSizePixel().Width() - aHScroll->GetSizePixel().Width(),
             nHeight ) );
}

void BrowseBox::SetMode( BrowserMode nMode )
{

    pDataWin->bAutoHScroll = BrowserMode::AUTO_HSCROLL == ( nMode & BrowserMode::AUTO_HSCROLL );
    pDataWin->bAutoVScroll = BrowserMode::AUTO_VSCROLL == ( nMode & BrowserMode::AUTO_VSCROLL );
    pDataWin->bNoHScroll   = BrowserMode::NO_HSCROLL   == ( nMode & BrowserMode::NO_HSCROLL );
    pDataWin->bNoVScroll   = BrowserMode::NO_VSCROLL   == ( nMode & BrowserMode::NO_VSCROLL );

    DBG_ASSERT( !( pDataWin->bAutoHScroll && pDataWin->bNoHScroll ),
        "BrowseBox::SetMode: AutoHScroll *and* NoHScroll?" );
    DBG_ASSERT( !( pDataWin->bAutoVScroll && pDataWin->bNoVScroll ),
        "BrowseBox::SetMode: AutoVScroll *and* NoVScroll?" );
    if ( pDataWin->bAutoHScroll )
        pDataWin->bNoHScroll = false;
    if ( pDataWin->bAutoVScroll )
        pDataWin->bNoVScroll = false;

    if ( pDataWin->bNoHScroll )
        aHScroll->Hide();

    nControlAreaWidth = USHRT_MAX;

    long nOldRowSel = bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel;
    MultiSelection *pOldRowSel = bMultiSelection ? uRow.pSel : nullptr;

    pVScroll.disposeAndClear();

    bMultiSelection = bool( nMode & BrowserMode::MULTISELECTION );
    bColumnCursor = bool( nMode & BrowserMode::COLUMNSELECTION );
    bKeepHighlight = bool( nMode & BrowserMode::KEEPHIGHLIGHT );

    bHideSelect = ((nMode & BrowserMode::HIDESELECT) == BrowserMode::HIDESELECT);
    // default: do not hide the cursor at all (untaken scrolling and such)
    bHideCursor = TRISTATE_FALSE;

    if ( BrowserMode::HIDECURSOR == ( nMode & BrowserMode::HIDECURSOR ) )
    {
        bHideCursor = TRISTATE_TRUE;
    }

    m_bFocusOnlyCursor = ((nMode & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::NONE);

    bHLines = ( nMode & BrowserMode::HLINES ) == BrowserMode::HLINES;
    bVLines = ( nMode & BrowserMode::VLINES ) == BrowserMode::VLINES;

    constexpr WinBits nVScrollWinBits = WB_VSCROLL;
    pVScroll = ( nMode & BrowserMode::TRACKING_TIPS ) == BrowserMode::TRACKING_TIPS
                ? VclPtr<BrowserScrollBar>::Create( this, nVScrollWinBits, pDataWin.get() )
                : VclPtr<ScrollBar>::Create( this, nVScrollWinBits );
    pVScroll->SetLineSize( 1 );
    pVScroll->SetPageSize(1);
    pVScroll->SetScrollHdl( LINK( this, BrowseBox, ScrollHdl ) );

    pDataWin->bAutoSizeLastCol =
            BrowserMode::AUTOSIZE_LASTCOL == ( nMode & BrowserMode::AUTOSIZE_LASTCOL );

    // create a headerbar. what happens, if a headerbar has to be created and
    // there already are columns?
    if ( BrowserMode::HEADERBAR_NEW == ( nMode & BrowserMode::HEADERBAR_NEW ) )
    {
        if (!pDataWin->pHeaderBar)
            pDataWin->pHeaderBar = CreateHeaderBar( this );
    }
    else
    {
        pDataWin->pHeaderBar.disposeAndClear();
    }

    if ( bColumnCursor )
    {
        if (!pColSel)
            pColSel.reset(new MultiSelection);
        pColSel->SetTotalRange( Range( 0, mvCols.size()-1 ) );
    }
    else
    {
        pColSel.reset();
    }

    if ( bMultiSelection )
    {
        if ( pOldRowSel )
            uRow.pSel = pOldRowSel;
        else
            uRow.pSel = new MultiSelection;
    }
    else
    {
        uRow.nSel = nOldRowSel;
        delete pOldRowSel;
    }

    if ( bBootstrapped )
    {
        StateChanged( StateChangedType::InitShow );
        if ( bMultiSelection && !pOldRowSel &&
             nOldRowSel != BROWSER_ENDOFSELECTION )
            uRow.pSel->Select( nOldRowSel );
    }

    if ( pDataWin )
        pDataWin->Invalidate();

    // no cursor on handle column
    if ( nCurColId == HandleColumnId )
        nCurColId = GetColumnId( 1 );

    m_nCurrentMode = nMode;
}


void BrowseBox::VisibleRowsChanged( long, sal_uInt16 )
{

    // old behavior: automatically correct NumRows:
    if ( nRowCount < GetRowCount() )
    {
        RowInserted(nRowCount,GetRowCount() - nRowCount, false);
    }
    else if ( nRowCount > GetRowCount() )
    {
        RowRemoved(nRowCount-(nRowCount - GetRowCount()),nRowCount - GetRowCount(), false);
    }
}


bool BrowseBox::IsCursorMoveAllowed( long, sal_uInt16 ) const

/*  [Description]

    This virtual method is always called before the cursor is moved directly.
    By means of 'return false', we avoid doing this if e.g. a record
    contradicts any rules.

    This method is not called, if the cursor movement results from removing or
    deleting a row/column (thus, in cases where only a "cursor correction" happens).

    The base implementation currently always returns true.
*/

{
    return true;
}


long BrowseBox::GetDataRowHeight() const
{
    return CalcZoom(nDataRowHeight ? nDataRowHeight : ImpGetDataRowHeight());
}


VclPtr<BrowserHeader> BrowseBox::CreateHeaderBar( BrowseBox* pParent )
{
    VclPtr<BrowserHeader> pNewBar = VclPtr<BrowserHeader>::Create( pParent );
    pNewBar->SetStartDragHdl( LINK( this, BrowseBox, StartDragHdl ) );
    return pNewBar;
}

void BrowseBox::SetHeaderBar( BrowserHeader* pHeaderBar )
{
    pDataWin->pHeaderBar.disposeAndClear();
    pDataWin->pHeaderBar = pHeaderBar;
    pDataWin->pHeaderBar->SetStartDragHdl( LINK( this, BrowseBox, StartDragHdl ) );
}

long BrowseBox::GetTitleHeight() const
{
    long nHeight;
    // ask the header bar for the text height (if possible), as the header bar's font is adjusted with
    // our (and the header's) zoom factor
    HeaderBar* pHeaderBar = pDataWin->pHeaderBar;
    if ( pHeaderBar )
        nHeight = pHeaderBar->GetTextHeight();
    else
        nHeight = GetTextHeight();

    return nTitleLines ? nTitleLines * nHeight + 4 : 0;
}

long BrowseBox::CalcReverseZoom(long nVal)
{
    if (IsZoom())
    {
        const Fraction& rZoom = GetZoom();
        double n = static_cast<double>(nVal);
        n *= static_cast<double>(rZoom.GetDenominator());
        if (!rZoom.GetNumerator())
            throw o3tl::divide_by_zero();
        n /= static_cast<double>(rZoom.GetNumerator());
        nVal = n>0 ? static_cast<long>(n + 0.5) : -static_cast<long>(-n + 0.5);
    }

    return nVal;
}

void BrowseBox::CursorMoved()
{
    // before implementing more here, please adjust the EditBrowseBox

    if ( isAccessibleAlive() && HasFocus() )
        commitTableEvent(
            ACTIVE_DESCENDANT_CHANGED,
            makeAny( CreateAccessibleCell( GetCurRow(),GetColumnPos( GetCurColumnId() ) ) ),
            Any()
        );
}

void BrowseBox::LoseFocus()
{
    SAL_INFO("svtools", "BrowseBox::LoseFocus " << this );

    if ( bHasFocus )
    {
        SAL_INFO("svtools", "BrowseBox::HideCursor " << this );
        DoHideCursor( "LoseFocus" );

        if ( !bKeepHighlight )
        {
            ToggleSelection();
            bSelectionIsVisible = false;
        }

        bHasFocus = false;
    }
    Control::LoseFocus();
}


void BrowseBox::GetFocus()
{
    SAL_INFO("svtools", "BrowseBox::GetFocus " << this );

    if ( !bHasFocus )
    {
        if ( !bSelectionIsVisible )
        {
            bSelectionIsVisible = true;
            if ( bBootstrapped )
                ToggleSelection();
        }

        bHasFocus = true;
        DoShowCursor( "GetFocus" );
    }
    Control::GetFocus();
}


sal_uInt16 BrowseBox::GetVisibleRows() const
{
    return static_cast<sal_uInt16>((pDataWin->GetOutputSizePixel().Height() - 1 )/ GetDataRowHeight() + 1);
}

vcl::Window& BrowseBox::GetDataWindow() const
{
    return *pDataWin;
}

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