/* -*- 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 <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <tools/debug.hxx>
#include <svtools/brwbox.hxx>
#include <svtools/brwhead.hxx>
#include <svtools/colorcfg.hxx>
#include <svtools/scrolladaptor.hxx>
#include "datwin.hxx"
#include <vcl/commandevent.hxx>
#include <vcl/help.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/settings.hxx>
 
#include <tools/multisel.hxx>
#include <tools/fract.hxx>
#include <algorithm>
#include <memory>
 
using namespace ::com::sun::star::datatransfer;
 
 
void BrowseBox::StartDrag( sal_Int8 /* _nAction */, const Point& /* _rPosPixel */ )
{
    // not interested in this event
}
 
 
sal_Int8 BrowseBox::AcceptDrop( const AcceptDropEvent& _rEvt )
{
    AcceptDropEvent aTransformed( _rEvt );
    aTransformed.maPosPixel = pDataWin->ScreenToOutputPixel( OutputToScreenPixel( _rEvt.maPosPixel ) );
    return pDataWin->AcceptDrop( aTransformed );
}
 
 
sal_Int8 BrowseBox::ExecuteDrop( const ExecuteDropEvent& _rEvt )
{
    ExecuteDropEvent aTransformed( _rEvt );
    aTransformed.maPosPixel = pDataWin->ScreenToOutputPixel( OutputToScreenPixel( _rEvt.maPosPixel ) );
    return pDataWin->ExecuteDrop( aTransformed );
}
 
 
sal_Int8 BrowseBox::AcceptDrop( const BrowserAcceptDropEvent& )
{
    // not interested in this event
    return DND_ACTION_NONE;
}
 
 
sal_Int8 BrowseBox::ExecuteDrop( const BrowserExecuteDropEvent& )
{
    // not interested in this event
    return DND_ACTION_NONE;
}
 
 
const DataFlavorExVector& BrowseBox::GetDataFlavors() const
{
    if (pDataWin->bCallingDropCallback)
        return pDataWin->GetDataFlavorExVector();
    return GetDataFlavorExVector();
}
 
 
bool BrowseBox::IsDropFormatSupported( SotClipboardFormatId _nFormat ) const
{
    if ( pDataWin->bCallingDropCallback )
        return pDataWin->IsDropFormatSupported( _nFormat );
 
    return DropTargetHelper::IsDropFormatSupported( _nFormat );
}
 
 
void BrowseBox::Command( const CommandEvent& rEvt )
{
    if ( !pDataWin->bInCommand )
        Control::Command( rEvt );
}
 
 
void BrowseBox::StateChanged( StateChangedType nStateChange )
{
    Control::StateChanged( nStateChange );
 
    if ( StateChangedType::Mirroring == nStateChange )
    {
        pDataWin->EnableRTL( IsRTLEnabled() );
 
        HeaderBar* pHeaderBar = pDataWin->pHeaderBar;
        if ( pHeaderBar )
            pHeaderBar->EnableRTL( IsRTLEnabled() );
        aHScroll->EnableRTL( IsRTLEnabled() );
        if( pVScroll )
            pVScroll->EnableRTL( IsRTLEnabled() );
        Resize();
    }
    else if ( StateChangedType::InitShow == nStateChange )
    {
        bBootstrapped = true; // must be set first!
 
        Resize();
        if ( bMultiSelection )
            uRow.pSel->SetTotalRange( Range( 0, nRowCount - 1 ) );
        if ( nRowCount == 0 )
            nCurRow = BROWSER_ENDOFSELECTION;
        else if ( nCurRow == BROWSER_ENDOFSELECTION )
            nCurRow = 0;
 
 
        if ( HasFocus() )
        {
            bSelectionIsVisible = true;
            bHasFocus = true;
        }
        UpdateScrollbars();
        AutoSizeLastColumn();
        CursorMoved();
    }
    else if (StateChangedType::Zoom == nStateChange)
    {
        pDataWin->SetZoom(GetZoom());
        HeaderBar* pHeaderBar = pDataWin->pHeaderBar;
        if (pHeaderBar)
            pHeaderBar->SetZoom(GetZoom());
 
        // let the columns calculate their new widths and adjust the header bar
        for (auto & pCol : mvCols)
        {
            pCol->ZoomChanged(GetZoom());
            if ( pHeaderBar )
                pHeaderBar->SetItemSize( pCol->GetId(), pCol->Width() );
        }
 
        // all our controls have to be repositioned
        Resize();
    }
    else if (StateChangedType::Enable == nStateChange)
    {
        // do we have a handle column?
        bool bHandleCol = !mvCols.empty() && (0 == mvCols[ 0 ]->GetId());
        // do we have a header bar?
        bool bHeaderBar(pDataWin->pHeaderBar);
 
        if  (   nTitleLines
            &&  (   !bHeaderBar
                ||  bHandleCol
                )
            )
            // we draw the text in our header bar in a color dependent on the enabled state. So if this state changed
            // -> redraw
            Invalidate(tools::Rectangle(Point(0, 0), Size(GetOutputSizePixel().Width(), GetTitleHeight() - 1)));
    }
}
 
 
void BrowseBox::Select()
{
}
 
 
void BrowseBox::DoubleClick( const BrowserMouseEvent & )
{
}
 
 
tools::Long BrowseBox::QueryMinimumRowHeight()
{
    return CalcZoom( 5 );
}
 
 
void BrowseBox::ImplStartTracking()
{
}
 
 
void BrowseBox::ImplEndTracking()
{
}
 
 
void BrowseBox::RowHeightChanged()
{
}
 
 
void BrowseBox::ColumnResized( sal_uInt16 )
{
}
 
 
void BrowseBox::ColumnMoved( sal_uInt16 )
{
}
 
 
void BrowseBox::StartScroll()
{
    DoHideCursor();
}
 
 
void BrowseBox::EndScroll()
{
    UpdateScrollbars();
    AutoSizeLastColumn();
    DoShowCursor();
}
 
 
void BrowseBox::ToggleSelection()
{
 
    // selection highlight-toggling allowed?
    if ( bHideSelect )
        return;
    if ( bNotToggleSel || !IsUpdateMode() || !bSelectionIsVisible )
        return;
 
    // only highlight painted areas!
    bNotToggleSel = true;
 
    // accumulate areas of rows to highlight
    std::vector<tools::Rectangle> aHighlightList;
    sal_Int32 nLastRowInRect = 0; // for the CFront
 
    // don't highlight handle column
    BrowserColumn *pFirstCol = mvCols.empty() ? nullptr : mvCols[ 0 ].get();
    tools::Long nOfsX = (!pFirstCol || pFirstCol->GetId()) ? 0 : pFirstCol->Width();
 
    // accumulate old row selection
    sal_Int32 nBottomRow = nTopRow +
        pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight();
    if ( nBottomRow > GetRowCount() && GetRowCount() )
        nBottomRow = GetRowCount();
    for ( sal_Int32 nRow = bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel;
          nRow != BROWSER_ENDOFSELECTION && nRow <= nBottomRow;
          nRow = bMultiSelection ? uRow.pSel->NextSelected() : BROWSER_ENDOFSELECTION )
    {
        if ( nRow < nTopRow )
            continue;
 
        tools::Rectangle aAddRect(
            Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ),
            Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) );
        if ( !aHighlightList.empty() && nLastRowInRect == ( nRow - 1 ) )
            aHighlightList[ 0 ].Union( aAddRect );
        else
            aHighlightList.emplace( aHighlightList.begin(), aAddRect );
        nLastRowInRect = nRow;
    }
 
    // unhighlight the old selection (if any)
    while ( !aHighlightList.empty() )
    {
        pDataWin->Invalidate( aHighlightList.back() );
        aHighlightList.pop_back();
    }
 
    // unhighlight old column selection (if any)
    for ( tools::Long nColId = pColSel ? pColSel->FirstSelected() : BROWSER_ENDOFSELECTION;
          nColId != BROWSER_ENDOFSELECTION;
          nColId = pColSel->NextSelected() )
    {
        tools::Rectangle aRect( GetFieldRectPixel(nCurRow,
                                           mvCols[ nColId ]->GetId(),
                                           false ) );
        aRect.AdjustLeft( -(MIN_COLUMNWIDTH) );
        aRect.AdjustRight(MIN_COLUMNWIDTH );
        aRect.SetTop( 0 );
        aRect.SetBottom( pDataWin->GetOutputSizePixel().Height() );
        pDataWin->Invalidate( aRect );
    }
 
    bNotToggleSel = false;
}
 
 
void BrowseBox::DrawCursor()
{
    bool bReallyHide = false;
    if ( bHideCursor == TRISTATE_INDET )
    {
        if ( !GetSelectRowCount() && !GetSelectColumnCount() )
            bReallyHide = true;
    }
    else if ( bHideCursor == TRISTATE_TRUE )
    {
        bReallyHide = true;
    }
 
    bReallyHide |= !bSelectionIsVisible || !IsUpdateMode() || bScrolling || nCurRow < 0;
 
    if (PaintCursorIfHiddenOnce())
        bReallyHide |= ( GetCursorHideCount() > 1 );
    else
        bReallyHide |= ( GetCursorHideCount() > 0 );
 
    // no cursor on handle column
    if ( nCurColId == HandleColumnId )
        nCurColId = GetColumnId(1);
 
    // calculate cursor rectangle
    tools::Rectangle aCursor;
    if ( bColumnCursor )
    {
        aCursor = GetFieldRectPixel( nCurRow, nCurColId, false );
        aCursor.AdjustLeft( -(MIN_COLUMNWIDTH) );
        aCursor.AdjustRight(1 );
        aCursor.AdjustBottom(1 );
    }
    else
        aCursor = tools::Rectangle(
            Point( ( !mvCols.empty() && mvCols[ 0 ]->GetId() == 0 ) ?
                        mvCols[ 0 ]->Width() : 0,
                        (nCurRow - nTopRow) * GetDataRowHeight() + 1 ),
            Size( pDataWin->GetOutputSizePixel().Width() + 1,
                  GetDataRowHeight() - 2 ) );
    if ( bHLines )
    {
        if ( !bMultiSelection )
            aCursor.AdjustTop( -1 );
        aCursor.AdjustBottom( -1 );
    }
 
    if (m_aCursorColor == COL_TRANSPARENT)
    {
        // on these platforms, the StarView focus works correctly
        if ( bReallyHide )
            static_cast<Control*>(pDataWin.get())->HideFocus();
        else
            static_cast<Control*>(pDataWin.get())->ShowFocus( aCursor );
    }
    else
    {
        Color rCol = bReallyHide ? pDataWin->GetOutDev()->GetFillColor() : m_aCursorColor;
        Color aOldFillColor = pDataWin->GetOutDev()->GetFillColor();
        Color aOldLineColor = pDataWin->GetOutDev()->GetLineColor();
        pDataWin->GetOutDev()->SetFillColor();
        pDataWin->GetOutDev()->SetLineColor( rCol );
        pDataWin->GetOutDev()->DrawRect( aCursor );
        pDataWin->GetOutDev()->SetLineColor( aOldLineColor );
        pDataWin->GetOutDev()->SetFillColor( aOldFillColor );
    }
}
 
 
tools::Long BrowseBox::GetColumnWidth( sal_uInt16 nId ) const
{
 
    sal_uInt16 nItemPos = GetColumnPos( nId );
    if ( nItemPos >= mvCols.size() )
        return 0;
    return mvCols[ nItemPos ]->Width();
}
 
 
sal_uInt16 BrowseBox::GetColumnId( sal_uInt16 nPos ) const
{
 
    if ( nPos >= mvCols.size() )
        return BROWSER_INVALIDID;
    return mvCols[ nPos ]->GetId();
}
 
 
sal_uInt16 BrowseBox::GetColumnPos( sal_uInt16 nId ) const
{
    for ( size_t nPos = 0; nPos < mvCols.size(); ++nPos )
        if ( mvCols[ nPos ]->GetId() == nId )
            return nPos;
    return BROWSER_INVALIDID;
}
 
 
bool BrowseBox::IsFrozen( sal_uInt16 nColumnId ) const
{
    for (auto const & pCol : mvCols)
        if ( pCol->GetId() == nColumnId )
            return pCol->IsFrozen();
    return false;
}
 
 
void BrowseBox::ExpandRowSelection( const BrowserMouseEvent& rEvt )
{
    DoHideCursor();
 
    // expand the last selection
    if ( bMultiSelection )
    {
        Range aJustifiedRange( aSelRange );
        aJustifiedRange.Normalize();
 
        bool bSelectThis = ( bSelect != aJustifiedRange.Contains( rEvt.GetRow() ) );
 
        if ( aJustifiedRange.Contains( rEvt.GetRow() ) )
        {
            // down and up
            while ( rEvt.GetRow() < aSelRange.Max() )
            {   // ZTC/Mac bug - don't put these statements together!
                SelectRow( aSelRange.Max(), bSelectThis );
                --aSelRange.Max();
            }
            while ( rEvt.GetRow() > aSelRange.Max() )
            {   // ZTC/Mac bug - don't put these statements together!
                SelectRow( aSelRange.Max(), bSelectThis );
                ++aSelRange.Max();
            }
        }
        else
        {
            // up and down
            bool bOldSelecting = bSelecting;
            bSelecting = true;
            while ( rEvt.GetRow() < aSelRange.Max() )
            {   // ZTC/Mac bug - don't put these statements together!
                --aSelRange.Max();
                if ( !IsRowSelected( aSelRange.Max() ) )
                {
                    SelectRow( aSelRange.Max(), bSelectThis );
                    bSelect = true;
                }
            }
            while ( rEvt.GetRow() > aSelRange.Max() )
            {   // ZTC/Mac bug - don't put these statements together!
                ++aSelRange.Max();
                if ( !IsRowSelected( aSelRange.Max() ) )
                {
                    SelectRow( aSelRange.Max(), bSelectThis );
                    bSelect = true;
                }
            }
            bSelecting = bOldSelecting;
            if ( bSelect )
                Select();
        }
    }
    else
        if (!IsRowSelected(rEvt.GetRow()))
            SelectRow( rEvt.GetRow() );
 
    GoToRow( rEvt.GetRow(), false );
    DoShowCursor();
}
 
 
void BrowseBox::Resize()
{
    if ( !bBootstrapped && IsReallyVisible() )
        BrowseBox::StateChanged( StateChangedType::InitShow );
    if ( mvCols.empty() )
    {
        pDataWin->bResizeOnPaint = true;
        return;
    }
    pDataWin->bResizeOnPaint = false;
 
    // calc the size of the scrollbars
    tools::Long nSBHeight = GetBarHeight();
    tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
    if (IsZoom())
    {
        nSBHeight = static_cast<tools::Long>(nSBHeight * static_cast<double>(GetZoom()));
        nSBWidth = static_cast<tools::Long>(nSBWidth * static_cast<double>(GetZoom()));
    }
 
    DoHideCursor();
    sal_uInt16 nOldVisibleRows = 0;
    //fdo#42694, post #i111125# GetDataRowHeight() can be 0
    if (GetDataRowHeight())
        nOldVisibleRows = static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1);
 
    // did we need a horizontal scroll bar or is there a Control Area?
    if ( !pDataWin->bNoHScroll &&
         ( ( mvCols.size() - FrozenColCount() ) > 1 ) )
        aHScroll->Show();
    else
        aHScroll->Hide();
 
    // calculate the size of the data window
    tools::Long nDataHeight = GetOutputSizePixel().Height() - GetTitleHeight();
    if ( aHScroll->IsVisible() || ( nControlAreaWidth != USHRT_MAX ) )
        nDataHeight -= nSBHeight;
 
    tools::Long nDataWidth = GetOutputSizePixel().Width();
    if ( pVScroll->IsVisible() )
        nDataWidth -= nSBWidth;
 
    // adjust position and size of data window
    pDataWin->SetPosSizePixel(
        Point( 0, GetTitleHeight() ),
        Size( nDataWidth, nDataHeight ) );
 
    sal_uInt16 nVisibleRows = 0;
 
    if (GetDataRowHeight())
        nVisibleRows = static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1);
 
    // TopRow is unchanged, but the number of visible lines has changed.
    if ( nVisibleRows != nOldVisibleRows )
        VisibleRowsChanged(nTopRow, nVisibleRows);
 
    UpdateScrollbars();
 
    // Control-Area
    tools::Rectangle aInvalidArea( GetControlArea() );
    aInvalidArea.SetRight( GetOutputSizePixel().Width() );
    aInvalidArea.SetLeft( 0 );
    Invalidate( aInvalidArea );
 
    // external header-bar
    HeaderBar* pHeaderBar = pDataWin->pHeaderBar;
    if ( pHeaderBar )
    {
        // take the handle column into account
        BrowserColumn *pFirstCol = mvCols[ 0 ].get();
        tools::Long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width();
        pHeaderBar->SetPosSizePixel( Point( nOfsX, 0 ), Size( GetOutputSizePixel().Width() - nOfsX, GetTitleHeight() ) );
    }
 
    AutoSizeLastColumn(); // adjust last column width
    DoShowCursor();
}
 
 
void BrowseBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    // initializations
    if (!bBootstrapped && IsReallyVisible())
        BrowseBox::StateChanged(StateChangedType::InitShow);
    if (mvCols.empty())
        return;
 
    BrowserColumn *pFirstCol = mvCols[ 0 ].get();
    bool bHandleCol = pFirstCol && pFirstCol->GetId() == 0;
    bool bHeaderBar(pDataWin->pHeaderBar);
 
    // draw delimitational lines
    if (!pDataWin->bNoHScroll)
        rRenderContext.DrawLine(Point(0, aHScroll->GetPosPixel().Y()),
                                Point(GetOutputSizePixel().Width(),
                                      aHScroll->GetPosPixel().Y()));
 
    if (nTitleLines)
    {
        if (!bHeaderBar)
        {
            rRenderContext.DrawLine(Point(0, GetTitleHeight() - 1),
                                    Point(GetOutputSizePixel().Width(), GetTitleHeight() - 1));
        }
        else if (bHandleCol)
        {
            rRenderContext.DrawLine(Point(0, GetTitleHeight() - 1),
                                    Point(pFirstCol->Width(), GetTitleHeight() - 1));
        }
    }
 
    // Title Bar
    // If there is a handle column and if the  header bar is available, only
    // take the HandleColumn into account
    if (!(nTitleLines && (!bHeaderBar || bHandleCol)))
        return;
 
    // iterate through columns to redraw
    tools::Long nX = 0;
    size_t nCol;
    for (nCol = 0; nCol < mvCols.size() && nX < rRect.Right(); ++nCol)
    {
        // skip invisible columns between frozen and scrollable area
        if (nCol < nFirstCol && !mvCols[nCol]->IsFrozen())
            nCol = nFirstCol;
 
        // only the handle column?
        if (bHeaderBar && bHandleCol && nCol > 0)
            break;
 
        BrowserColumn* pCol = mvCols[nCol].get();
 
        // draw the column and increment position
        if ( pCol->Width() > 4 )
        {
            ButtonFrame aButtonFrame( Point( nX, 0 ),
                Size( pCol->Width()-1, GetTitleHeight()-1 ),
                pCol->Title(), !IsEnabled());
            aButtonFrame.Draw(rRenderContext);
            rRenderContext.DrawLine(Point(nX + pCol->Width() - 1, 0),
                                    Point(nX + pCol->Width() - 1, GetTitleHeight() - 1));
        }
        else
        {
            rRenderContext.Push(vcl::PushFlags::FILLCOLOR);
            rRenderContext.SetFillColor(COL_BLACK);
            rRenderContext.DrawRect(tools::Rectangle(Point(nX, 0), Size(pCol->Width(), GetTitleHeight() - 1)));
            rRenderContext.Pop();
        }
 
        // skip column
        nX += pCol->Width();
    }
 
    // retouching
    if ( !bHeaderBar && nCol == mvCols.size() )
    {
        const StyleSettings &rSettings = rRenderContext.GetSettings().GetStyleSettings();
        Color aColFace(rSettings.GetFaceColor());
        rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
        rRenderContext.SetFillColor(aColFace);
        rRenderContext.SetLineColor(aColFace);
        rRenderContext.DrawRect(tools::Rectangle(Point(nX, 0),
                                          Point(rRect.Right(), GetTitleHeight() - 2 )));
        rRenderContext.Pop();
    }
 
    if (m_nActualCornerWidth)
    {
        const StyleSettings &rSettings = rRenderContext.GetSettings().GetStyleSettings();
        Color aColFace(rSettings.GetFaceColor());
        rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
        rRenderContext.SetFillColor(aColFace);
        rRenderContext.SetLineColor(aColFace);
        rRenderContext.DrawRect(tools::Rectangle(Point(GetOutputSizePixel().Width() - m_nActualCornerWidth, aHScroll->GetPosPixel().Y()),
                                                 Size(m_nActualCornerWidth, m_nCornerHeight)));
        rRenderContext.Pop();
    }
}
 
void BrowseBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
{
    // we need pixel coordinates
    Size aRealSize = GetSizePixel();
    Point aRealPos = pDev->LogicToPixel(rPos);
 
    if ((aRealSize.Width() < 3) || (aRealSize.Height() < 3))
        // we want to have two pixels frame ...
        return;
 
    vcl::Font aFont = pDataWin->GetDrawPixelFont( pDev );
        // the 'normal' painting uses always the data window as device to output to, so we have to calc the new font
        // relative to the data wins current settings
 
    pDev->Push();
    pDev->SetMapMode();
    pDev->SetFont( aFont );
    if (nFlags & SystemTextColorFlags::Mono)
        pDev->SetTextColor(COL_BLACK);
    else
        pDev->SetTextColor(pDataWin->GetTextColor());
 
    // draw a frame
    const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
    pDev->SetLineColor(rStyleSettings.GetDarkShadowColor());
    pDev->DrawLine(Point(aRealPos.X(), aRealPos.Y()),
                   Point(aRealPos.X(), aRealPos.Y() + aRealSize.Height() - 1));
    pDev->DrawLine(Point(aRealPos.X(), aRealPos.Y()),
                   Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y()));
    pDev->SetLineColor(rStyleSettings.GetShadowColor());
    pDev->DrawLine(Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + 1),
                   Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + aRealSize.Height() - 1));
    pDev->DrawLine(Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + aRealSize.Height() - 1),
                   Point(aRealPos.X() + 1, aRealPos.Y() + aRealSize.Height() - 1));
 
    HeaderBar* pBar = pDataWin->pHeaderBar;
 
    // we're drawing onto a foreign device, so we have to fake the DataRowHeight for the subsequent ImplPaintData
    // (as it is based on the settings of our data window, not the foreign device)
    if (!m_nDataRowHeight)
        ImpGetDataRowHeight();
    tools::Long nHeightLogic = PixelToLogic(Size(0, m_nDataRowHeight), MapMode(MapUnit::Map10thMM)).Height();
    tools::Long nForeignHeightPixel = pDev->LogicToPixel(Size(0, nHeightLogic), MapMode(MapUnit::Map10thMM)).Height();
 
    tools::Long nOriginalHeight = m_nDataRowHeight;
    m_nDataRowHeight = nForeignHeightPixel;
 
    // this counts for the column widths, too
    size_t nPos;
    for ( nPos = 0; nPos < mvCols.size(); ++nPos )
    {
        BrowserColumn* pCurrent = mvCols[ nPos ].get();
 
        tools::Long nWidthLogic = PixelToLogic(Size(pCurrent->Width(), 0), MapMode(MapUnit::Map10thMM)).Width();
        tools::Long nForeignWidthPixel = pDev->LogicToPixel(Size(nWidthLogic, 0), MapMode(MapUnit::Map10thMM)).Width();
 
        pCurrent->SetWidth(nForeignWidthPixel, GetZoom());
        if ( pBar )
            pBar->SetItemSize( pCurrent->GetId(), pCurrent->Width() );
    }
 
    // a smaller area for the content
    aRealPos.AdjustX( 1 );
    aRealPos.AdjustY( 1 );
    aRealSize.AdjustWidth( -2 );
    aRealSize.AdjustHeight( -2 );
 
    // let the header bar draw itself
    if ( pBar )
    {
        // the title height with respect to the font set for the given device
        tools::Long nTitleHeight = PixelToLogic(Size(0, GetTitleHeight()), MapMode(MapUnit::Map10thMM)).Height();
        nTitleHeight = pDev->LogicToPixel(Size(0, nTitleHeight), MapMode(MapUnit::Map10thMM)).Height();
 
        BrowserColumn* pFirstCol = !mvCols.empty() ? mvCols[ 0 ].get() : nullptr;
 
        Point aHeaderPos(pFirstCol && (pFirstCol->GetId() == 0) ? pFirstCol->Width() : 0, 0);
        Size aHeaderSize(aRealSize.Width() - aHeaderPos.X(), nTitleHeight);
 
        aHeaderPos += aRealPos;
            // do this before converting to logics !
 
        // the header's draw expects logic coordinates, again
        aHeaderPos = pDev->PixelToLogic(aHeaderPos);
 
        Size aOrigSize(pBar->GetSizePixel());
        pBar->SetSizePixel(aHeaderSize);
        pBar->Draw(pDev, aHeaderPos, nFlags);
        pBar->SetSizePixel(aOrigSize);
 
        // draw the "upper left cell" (the intersection between the header bar and the handle column)
        if (pFirstCol && (pFirstCol->GetId() == 0) && (pFirstCol->Width() > 4))
        {
            ButtonFrame aButtonFrame( aRealPos,
                Size( pFirstCol->Width()-1, nTitleHeight-1 ),
                pFirstCol->Title(), !IsEnabled());
            aButtonFrame.Draw( *pDev );
 
            pDev->Push( vcl::PushFlags::LINECOLOR );
            pDev->SetLineColor( COL_BLACK );
 
            pDev->DrawLine( Point( aRealPos.X(), aRealPos.Y() + nTitleHeight-1 ),
               Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() + nTitleHeight-1 ) );
            pDev->DrawLine( Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() ),
               Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() + nTitleHeight-1 ) );
 
            pDev->Pop();
        }
 
        aRealPos.AdjustY(aHeaderSize.Height() );
        aRealSize.AdjustHeight( -(aHeaderSize.Height()) );
    }
 
    // draw our own content (with clipping)
    vcl::Region aRegion(tools::Rectangle(aRealPos, aRealSize));
    pDev->SetClipRegion( pDev->PixelToLogic( aRegion ) );
 
    // do we have to paint the background
    bool bBackground = pDataWin->IsControlBackground();
    if ( bBackground )
    {
        tools::Rectangle aRect( aRealPos, aRealSize );
        pDev->SetFillColor( pDataWin->GetControlBackground() );
        pDev->DrawRect( aRect );
    }
 
    ImplPaintData( *pDev, tools::Rectangle( aRealPos, aRealSize ), true );
 
    // restore the column widths/data row height
    m_nDataRowHeight = nOriginalHeight;
    for ( nPos = 0; nPos < mvCols.size(); ++nPos )
    {
        BrowserColumn* pCurrent = mvCols[ nPos ].get();
 
        tools::Long nForeignWidthLogic = pDev->PixelToLogic(Size(pCurrent->Width(), 0), MapMode(MapUnit::Map10thMM)).Width();
        tools::Long nWidthPixel = LogicToPixel(Size(nForeignWidthLogic, 0), MapMode(MapUnit::Map10thMM)).Width();
 
        pCurrent->SetWidth(nWidthPixel, GetZoom());
        if ( pBar )
            pBar->SetItemSize( pCurrent->GetId(), pCurrent->Width() );
    }
 
    pDev->Pop();
}
 
void BrowseBox::ImplPaintData(OutputDevice& _rOut, const tools::Rectangle& _rRect, bool _bForeignDevice)
{
    Point aOverallAreaPos = _bForeignDevice ? _rRect.TopLeft() : Point(0,0);
    Size aOverallAreaSize = _bForeignDevice ? _rRect.GetSize() : pDataWin->GetOutputSizePixel();
    Point aOverallAreaBRPos = _bForeignDevice ? _rRect.BottomRight() : Point( aOverallAreaSize.Width(), aOverallAreaSize.Height() );
 
    tools::Long nDataRowHeight = GetDataRowHeight();
 
    // compute relative rows to redraw
    sal_Int32 nRelTopRow = 0;
    sal_Int32 nRelBottomRow = aOverallAreaSize.Height();
    if (!_bForeignDevice && nDataRowHeight)
    {
        nRelTopRow = static_cast<sal_Int32>((_rRect.Top()) / nDataRowHeight);
        nRelBottomRow = static_cast<sal_Int32>((_rRect.Bottom()) / nDataRowHeight);
    }
 
    // cache frequently used values
    Point aPos( aOverallAreaPos.X(), nRelTopRow * nDataRowHeight + aOverallAreaPos.Y() );
    _rOut.SetLineColor( COL_WHITE );
    const AllSettings& rAllSets = _rOut.GetSettings();
    const StyleSettings &rSettings = rAllSets.GetStyleSettings();
    const Color &rHighlightTextColor = rSettings.GetHighlightTextColor();
    const Color &rHighlightFillColor = rSettings.GetHighlightColor();
    Color aOldTextColor = _rOut.GetTextColor();
    Color aOldFillColor = _rOut.GetFillColor();
    Color aOldLineColor = _rOut.GetLineColor();
    tools::Long nHLineX = 0 == mvCols[ 0 ]->GetId() ? mvCols[ 0 ]->Width() : 0;
    nHLineX += aOverallAreaPos.X();
 
    Color aDelimiterLineColor( ::svtools::ColorConfig().GetColorValue( ::svtools::CALCGRID ).nColor );
 
    // redraw the invalid fields
    for ( sal_Int32 nRelRow = nRelTopRow;
          nRelRow <= nRelBottomRow && nTopRow+nRelRow < nRowCount;
          ++nRelRow, aPos.AdjustY(nDataRowHeight ) )
    {
        // get row
        // check valid area, to be on the safe side:
        DBG_ASSERT( static_cast<sal_uInt16>(nTopRow+nRelRow) < nRowCount, "BrowseBox::ImplPaintData: invalid seek" );
        if ( (nTopRow+tools::Long(nRelRow)) < 0 || static_cast<sal_uInt16>(nTopRow+nRelRow) >= nRowCount )
            continue;
 
        // prepare row
        sal_Int32 nRow = nTopRow+nRelRow;
        if ( !SeekRow( nRow) ) {
            OSL_FAIL("BrowseBox::ImplPaintData: SeekRow failed");
        }
        _rOut.SetClipRegion();
        aPos.setX( aOverallAreaPos.X() );
 
 
        // #73325# don't paint the row outside the painting rectangle (DG)
        // prepare auto-highlight
        tools::Rectangle aRowRect( Point( _rRect.Left(), aPos.Y() ),
                Size( _rRect.GetSize().Width(), nDataRowHeight ) );
 
        bool bRowSelected   =   !bHideSelect
                            &&  IsRowSelected( nRow );
        if ( bRowSelected )
        {
            _rOut.SetTextColor( rHighlightTextColor );
            _rOut.SetFillColor( rHighlightFillColor );
            _rOut.SetLineColor();
            _rOut.DrawRect( aRowRect );
        }
 
        // iterate through columns to redraw
        size_t nCol;
        for ( nCol = 0; nCol < mvCols.size(); ++nCol )
        {
            // get column
            BrowserColumn *pCol = mvCols[ nCol ].get();
 
            // at end of invalid area
            if ( aPos.X() >= _rRect.Right() )
                break;
 
            // skip invisible columns between frozen and scrollable area
            if ( nCol < nFirstCol && !pCol->IsFrozen() )
            {
                nCol = nFirstCol;
                pCol = (nCol < mvCols.size() ) ? mvCols[ nCol ].get() : nullptr;
                if (!pCol)
                {   // FS - 21.05.99 - 66325
                    // actually this has been fixed elsewhere (in the right place),
                    // but let's make sure...
                    OSL_FAIL("BrowseBox::PaintData : nFirstCol is probably invalid !");
                    break;
                }
            }
 
            // prepare Column-AutoHighlight
            bool bColAutoHighlight  =   bColumnCursor
                                    &&  IsColumnSelected( pCol->GetId() );
            if ( bColAutoHighlight )
            {
                _rOut.SetClipRegion();
                _rOut.SetTextColor( rHighlightTextColor );
                _rOut.SetFillColor( rHighlightFillColor );
                _rOut.SetLineColor();
                tools::Rectangle aFieldRect( aPos,
                        Size( pCol->Width(), nDataRowHeight ) );
                _rOut.DrawRect( aFieldRect );
            }
 
            if (!m_bFocusOnlyCursor && (pCol->GetId() == GetCurColumnId()) && (nRow == GetCurRow()))
                DrawCursor();
 
            // draw a single field.
            // else something is drawn to, e.g. handle column
            if (pCol->Width())
            {
                // clip the column's output to the field area
                if (_bForeignDevice)
                {   // (not necessary if painting onto the data window)
                    Size aFieldSize(pCol->Width(), nDataRowHeight);
 
                    if (aPos.X() + aFieldSize.Width() > aOverallAreaBRPos.X())
                        aFieldSize.setWidth( aOverallAreaBRPos.X() - aPos.X() );
 
                    if (aPos.Y() + aFieldSize.Height() > aOverallAreaBRPos.Y() + 1)
                    {
                        // for non-handle cols we don't clip vertically : we just don't draw the cell if the line isn't completely visible
                        if (pCol->GetId() != 0)
                            continue;
                        aFieldSize.setHeight( aOverallAreaBRPos.Y() + 1 - aPos.Y() );
                    }
 
                    vcl::Region aClipToField(tools::Rectangle(aPos, aFieldSize));
                    _rOut.SetClipRegion(aClipToField);
                }
                pCol->Draw( *this, _rOut, aPos );
                if (_bForeignDevice)
                    _rOut.SetClipRegion();
            }
 
            // reset Column-auto-highlight
            if ( bColAutoHighlight )
            {
                _rOut.SetTextColor( aOldTextColor );
                _rOut.SetFillColor( aOldFillColor );
                _rOut.SetLineColor( aOldLineColor );
            }
 
            // skip column
            aPos.AdjustX(pCol->Width() );
        }
 
        // reset auto-highlight
        if ( bRowSelected )
        {
            _rOut.SetTextColor( aOldTextColor );
            _rOut.SetFillColor( aOldFillColor );
            _rOut.SetLineColor( aOldLineColor );
        }
 
        if ( bHLines )
        {
            // draw horizontal delimitation lines
            _rOut.SetClipRegion();
            _rOut.Push( vcl::PushFlags::LINECOLOR );
            _rOut.SetLineColor( aDelimiterLineColor );
            tools::Long nY = aPos.Y() + nDataRowHeight - 1;
            if (nY <= aOverallAreaBRPos.Y())
                _rOut.DrawLine( Point( nHLineX, nY ),
                                Point( bVLines
                                        ? std::min(tools::Long(aPos.X() - 1), aOverallAreaBRPos.X())
                                        : aOverallAreaBRPos.X(),
                                      nY ) );
            _rOut.Pop();
        }
    }
 
    if (aPos.Y() > aOverallAreaBRPos.Y() + 1)
        aPos.setY( aOverallAreaBRPos.Y() + 1 );
        // needed for some of the following drawing
 
    // retouching
    _rOut.SetClipRegion();
    aOldLineColor = _rOut.GetLineColor();
    aOldFillColor = _rOut.GetFillColor();
    _rOut.SetFillColor( rSettings.GetFaceColor() );
    if ( !mvCols.empty() && ( mvCols[ 0 ]->GetId() == 0 ) && ( aPos.Y() <= _rRect.Bottom() ) )
    {
        // fill rectangle gray below handle column
        // DG: fill it only until the end of the drawing rect and not to the end, as this may overpaint handle columns
        _rOut.SetLineColor( COL_BLACK );
        _rOut.DrawRect( tools::Rectangle(
            Point( aOverallAreaPos.X() - 1, aPos.Y() - 1 ),
            Point( aOverallAreaPos.X() + mvCols[ 0 ]->Width() - 1,
                   _rRect.Bottom() + 1) ) );
    }
    _rOut.SetFillColor( aOldFillColor );
 
    // draw vertical delimitational line between frozen and scrollable cols
    _rOut.SetLineColor( COL_BLACK );
    tools::Long nFrozenWidth = GetFrozenWidth()-1;
    _rOut.DrawLine( Point( aOverallAreaPos.X() + nFrozenWidth, aPos.Y() ),
                   Point( aOverallAreaPos.X() + nFrozenWidth, bHLines
                            ? aPos.Y() - 1
                            : aOverallAreaBRPos.Y() ) );
 
    // draw vertical delimitational lines?
    if ( bVLines )
    {
        _rOut.SetLineColor( aDelimiterLineColor );
        Point aVertPos( aOverallAreaPos.X() - 1, aOverallAreaPos.Y() );
        tools::Long nDeltaY = aOverallAreaBRPos.Y();
        for ( size_t nCol = 0; nCol < mvCols.size(); ++nCol )
        {
            // get column
            BrowserColumn *pCol = mvCols[ nCol ].get();
 
            // skip invisible columns between frozen and scrollable area
            if ( nCol < nFirstCol && !pCol->IsFrozen() )
            {
                nCol = nFirstCol;
                pCol = mvCols[ nCol ].get();
            }
 
            // skip column
            aVertPos.AdjustX(pCol->Width() );
 
            // at end of invalid area
            // invalid area is first reached when X > Right
            // and not >=
            if ( aVertPos.X() > _rRect.Right() )
                break;
 
            // draw a single line
            if ( pCol->GetId() != 0 )
                _rOut.DrawLine( aVertPos, Point( aVertPos.X(),
                               bHLines
                                ? aPos.Y() - 1
                                : aPos.Y() + nDeltaY ) );
        }
    }
 
    _rOut.SetLineColor( aOldLineColor );
}
 
void BrowseBox::PaintData( vcl::Window const & rWin, vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect )
{
    if (!bBootstrapped && IsReallyVisible())
        BrowseBox::StateChanged(StateChangedType::InitShow);
 
    // initializations
    if (mvCols.empty() || !rWin.IsUpdateMode())
        return;
    if (pDataWin->bResizeOnPaint)
        Resize();
    // MI: who was that? Window::Update();
 
    ImplPaintData(rRenderContext, rRect, false);
}
 
void BrowseBox::UpdateScrollbars()
{
 
    if ( !bBootstrapped || !IsUpdateMode() )
        return;
 
    // protect against recursion
    if ( pDataWin->bInUpdateScrollbars )
    {
        pDataWin->bHadRecursion = true;
        return;
    }
    pDataWin->bInUpdateScrollbars = true;
 
    // the size of the corner window (and the width of the VSB/height of the HSB)
    m_nCornerHeight = GetBarHeight();
    m_nCornerWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
    if (IsZoom())
    {
        m_nCornerHeight = static_cast<tools::Long>(m_nCornerHeight * static_cast<double>(GetZoom()));
        m_nCornerWidth = static_cast<tools::Long>(m_nCornerWidth * static_cast<double>(GetZoom()));
    }
 
    bool bNeedsVScroll = false;
    sal_Int32 nMaxRows = 0;
    if (GetDataRowHeight())
    {
        // needs VScroll?
        nMaxRows = (pDataWin->GetSizePixel().Height()) / GetDataRowHeight();
        bNeedsVScroll =    pDataWin->bAutoVScroll
                            ?   nTopRow || ( nRowCount > nMaxRows )
                            :   !pDataWin->bNoVScroll;
    }
    Size aDataWinSize = pDataWin->GetSizePixel();
    if ( !bNeedsVScroll )
    {
        if ( pVScroll->IsVisible() )
        {
            pVScroll->Hide();
            Size aNewSize( aDataWinSize );
            aNewSize.setWidth( GetOutputSizePixel().Width() );
            aDataWinSize = aNewSize;
        }
    }
    else if ( !pVScroll->IsVisible() )
    {
        Size aNewSize( aDataWinSize );
        aNewSize.setWidth( GetOutputSizePixel().Width() - m_nCornerWidth );
        aDataWinSize = aNewSize;
    }
 
    // needs HScroll?
    sal_uInt16 nLastCol = GetColumnAtXPosPixel( aDataWinSize.Width() - 1 );
 
    sal_uInt16 nFrozenCols = FrozenColCount();
    bool bNeedsHScroll =    pDataWin->bAutoHScroll
                        ?   ( nFirstCol > nFrozenCols ) || ( nLastCol <= mvCols.size() )
                        :   !pDataWin->bNoHScroll;
    if ( !bNeedsHScroll )
    {
        if ( aHScroll->IsVisible() )
        {
            aHScroll->Hide();
        }
        aDataWinSize.setHeight( GetOutputSizePixel().Height() - GetTitleHeight() );
        if ( nControlAreaWidth != USHRT_MAX )
            aDataWinSize.AdjustHeight( -sal_Int32(m_nCornerHeight) );
    }
    else if ( !aHScroll->IsVisible() )
    {
        Size aNewSize( aDataWinSize );
        aNewSize.setHeight( GetOutputSizePixel().Height() - GetTitleHeight() - m_nCornerHeight );
        aDataWinSize = aNewSize;
    }
 
    // adjust position and Width of horizontal scrollbar
    tools::Long nHScrX = nControlAreaWidth == USHRT_MAX
        ? 0
        : nControlAreaWidth;
 
    aHScroll->SetPosSizePixel(
        Point( nHScrX, GetOutputSizePixel().Height() - m_nCornerHeight ),
        Size( aDataWinSize.Width() - nHScrX, m_nCornerHeight ) );
 
    // total scrollable columns
    short nScrollCols = short(mvCols.size()) - static_cast<short>(nFrozenCols);
 
    // visible columns
    short nVisibleHSize = nLastCol == BROWSER_INVALIDID
        ? static_cast<short>( mvCols.size() - nFirstCol )
        : static_cast<short>( nLastCol - nFirstCol );
 
    if (nVisibleHSize)
    {
        short nRange = std::max( nScrollCols, short(0) );
        aHScroll->SetVisibleSize( nVisibleHSize );
        aHScroll->SetRange( Range( 0, nRange ));
    }
    else
    {
        // ensure scrollbar is shown as fully filled
        aHScroll->SetVisibleSize(1);
        aHScroll->SetRange(Range(0, 1));
    }
    if ( bNeedsHScroll && !aHScroll->IsVisible() )
        aHScroll->Show();
 
    // adjust position and height of vertical scrollbar
    pVScroll->SetPageSize( nMaxRows );
 
    if ( nTopRow > nRowCount )
    {
        nTopRow = nRowCount - 1;
        OSL_FAIL("BrowseBox: nTopRow > nRowCount");
    }
 
    if ( pVScroll->GetThumbPos() != nTopRow )
        pVScroll->SetThumbPos( nTopRow );
    tools::Long nVisibleSize = std::min( std::min( nRowCount, nMaxRows ), (nRowCount-nTopRow) );
    pVScroll->SetVisibleSize( nVisibleSize ? nVisibleSize : 1 );
    pVScroll->SetRange( Range( 0, nRowCount ) );
    pVScroll->SetPosSizePixel(
        Point( aDataWinSize.Width(), GetTitleHeight() ),
        Size( m_nCornerWidth, aDataWinSize.Height()) );
    tools::Long nLclDataRowHeight = GetDataRowHeight();
    if ( nLclDataRowHeight > 0 && nRowCount < tools::Long( aDataWinSize.Height() / nLclDataRowHeight ) )
        ScrollRows( -nTopRow );
    if ( bNeedsVScroll && !pVScroll->IsVisible() )
        pVScroll->Show();
 
    pDataWin->SetPosSizePixel(
        Point( 0, GetTitleHeight() ),
        aDataWinSize );
 
    // needs corner-window?
    // (do that AFTER positioning BOTH scrollbars)
    m_nActualCornerWidth = 0;
    if (aHScroll->IsVisible() && pVScroll && pVScroll->IsVisible() )
    {
        // if we have both scrollbars, the corner window fills the point of intersection of these two
        m_nActualCornerWidth = m_nCornerWidth;
    }
    else if ( !aHScroll->IsVisible() && ( nControlAreaWidth != USHRT_MAX ) )
    {
        // if we have no horizontal scrollbar, but a control area, we need the corner window to
        // fill the space between the control are and the right border
        m_nActualCornerWidth = GetOutputSizePixel().Width() - nControlAreaWidth;
    }
 
    // scroll headerbar, if necessary
    if ( pDataWin->pHeaderBar )
    {
        tools::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 );
    }
 
    pDataWin->bInUpdateScrollbars = false;
    if ( pDataWin->bHadRecursion )
    {
        pDataWin->bHadRecursion = false;
        UpdateScrollbars();
    }
}
 
 
void BrowseBox::SetUpdateMode( bool bUpdate )
{
 
    bool bWasUpdate = IsUpdateMode();
    if ( bWasUpdate == bUpdate )
        return;
 
    Control::SetUpdateMode( bUpdate );
    // If WB_CLIPCHILDREN is st at the BrowseBox (to minimize flicker),
    // the data window is not invalidated by SetUpdateMode.
    if( bUpdate )
        pDataWin->Invalidate();
    pDataWin->SetUpdateMode( bUpdate );
 
 
    if ( bUpdate )
    {
        if ( bBootstrapped )
        {
            UpdateScrollbars();
            AutoSizeLastColumn();
        }
        DoShowCursor();
    }
    else
        DoHideCursor();
}
 
 
bool BrowseBox::GetUpdateMode() const
{
 
    return pDataWin->IsUpdateMode();
}
 
 
tools::Long BrowseBox::GetFrozenWidth() const
{
 
    tools::Long nWidth = 0;
    for ( size_t nCol = 0;
          nCol < mvCols.size() && mvCols[ nCol ]->IsFrozen();
          ++nCol )
        nWidth += mvCols[ nCol ]->Width();
    return nWidth;
}
 
void BrowseBox::ColumnInserted( sal_uInt16 nPos )
{
    if ( pColSel )
        pColSel->Insert( nPos );
    UpdateScrollbars();
}
 
sal_uInt16 BrowseBox::FrozenColCount() const
{
    std::size_t nCol;
    for ( nCol = 0;
          nCol < mvCols.size() && mvCols[ nCol ]->IsFrozen();
          ++nCol )
        /* empty loop */;
    return nCol; //TODO: BrowserColumns::size_type -> sal_uInt16!
}
 
IMPL_LINK(BrowseBox, VertScrollHdl, weld::Scrollbar&, rScrollbar, void)
{
    auto nCurScrollRow = nTopRow;
    auto nPos = rScrollbar.adjustment_get_value();
    ScrollRows(nPos - nCurScrollRow);
 
    bool bShowTooltip = ((m_nCurrentMode & BrowserMode::TRACKING_TIPS) == BrowserMode::TRACKING_TIPS);
    if (bShowTooltip &&
        rScrollbar.get_scroll_type() == ScrollType::Drag &&
        Help::IsQuickHelpEnabled())
    {
        OUString aTip = OUString::number(nPos) + "/";
        if (!pDataWin->GetRealRowCount().isEmpty())
            aTip += pDataWin->GetRealRowCount();
        else
            aTip += OUString::number(rScrollbar.adjustment_get_upper());
        tools::Rectangle aRect(GetPointerPosPixel(), Size(GetTextWidth(aTip), GetTextHeight()));
        Help::ShowQuickHelp(this, aRect, aTip);
    }
}
 
IMPL_LINK(BrowseBox, HorzScrollHdl, weld::Scrollbar&, rScrollbar, void)
{
    auto nCurScrollCol = nFirstCol - FrozenColCount();
    ScrollColumns(rScrollbar.adjustment_get_value() - nCurScrollCol);
}
 
IMPL_LINK( BrowseBox, StartDragHdl, HeaderBar*, pBar, void )
{
    pBar->SetDragSize( pDataWin->GetOutputSizePixel().Height() );
}
 
// usually only the first column was resized
void BrowseBox::MouseButtonDown( const MouseEvent& rEvt )
{
 
    GrabFocus();
 
    // only mouse events in the title-line are supported
    const Point &rEvtPos = rEvt.GetPosPixel();
    if ( rEvtPos.Y() >= GetTitleHeight() )
        return;
 
    tools::Long nX = 0;
    tools::Long nWidth = GetOutputSizePixel().Width();
    for ( size_t nCol = 0; nCol < mvCols.size() && nX < nWidth; ++nCol )
    {
        // is this column visible?
        BrowserColumn *pCol = mvCols[ nCol ].get();
        if ( pCol->IsFrozen() || nCol >= nFirstCol )
        {
            // compute right end of column
            tools::Long nR = nX + pCol->Width() - 1;
 
            // at the end of a column (and not handle column)?
            if ( pCol->GetId() && std::abs( nR - rEvtPos.X() ) < 2 )
            {
                // start resizing the column
                bResizing = true;
                nResizeCol = nCol;
                nDragX = nResizeX = rEvtPos.X();
                SetPointer( PointerStyle::HSplit );
                CaptureMouse();
                pDataWin->GetOutDev()->DrawLine( Point( nDragX, 0 ),
                    Point( nDragX, pDataWin->GetSizePixel().Height() ) );
                nMinResizeX = nX + MIN_COLUMNWIDTH;
                return;
            }
            else if ( nX < rEvtPos.X() && nR > rEvtPos.X() )
            {
                MouseButtonDown( BrowserMouseEvent(
                    this, rEvt, -1, nCol, pCol->GetId(), tools::Rectangle() ) );
                return;
            }
            nX = nR + 1;
        }
    }
 
    // event occurred out of data area
    if ( rEvt.IsRight() )
        pDataWin->Command(
            CommandEvent( Point( 1, LONG_MAX ), CommandEventId::ContextMenu, true ) );
    else
        SetNoSelection();
}
 
 
void BrowseBox::MouseMove( const MouseEvent& rEvt )
{
    SAL_INFO("svtools", "BrowseBox::MouseMove( MouseEvent )" );
 
    PointerStyle aNewPointer = PointerStyle::Arrow;
 
    sal_uInt16 nX = 0;
    for ( size_t nCol = 0;
          nCol < mvCols.size() &&
            ( nX + mvCols[ nCol ]->Width() ) < GetOutputSizePixel().Width();
          ++nCol )
        // is this column visible?
        if ( mvCols[ nCol ]->IsFrozen() || nCol >= nFirstCol )
        {
            // compute right end of column
            BrowserColumn *pCol = mvCols[ nCol ].get();
            sal_uInt16 nR = static_cast<sal_uInt16>(nX + pCol->Width() - 1);
 
            // show resize-pointer?
            if ( bResizing || ( pCol->GetId() &&
                 std::abs( static_cast<tools::Long>(nR) - rEvt.GetPosPixel().X() ) < MIN_COLUMNWIDTH ) )
            {
                aNewPointer = PointerStyle::HSplit;
                if ( bResizing )
                {
                    // delete old auxiliary line
                    pDataWin->HideTracking() ;
 
                    // check allowed width and new delta
                    nDragX = std::max( rEvt.GetPosPixel().X(), nMinResizeX );
                    tools::Long nDeltaX = nDragX - nResizeX;
                    sal_uInt16 nId = GetColumnId(nResizeCol);
                    tools::Long nOldWidth = GetColumnWidth(nId);
                    nDragX = nOldWidth + nDeltaX + nResizeX - nOldWidth;
 
                    // draw new auxiliary line
                    pDataWin->ShowTracking( tools::Rectangle( Point( nDragX, 0 ),
                            Size( 1, pDataWin->GetSizePixel().Height() ) ),
                            ShowTrackFlags::Split|ShowTrackFlags::TrackWindow );
                }
 
            }
 
            nX = nR + 1;
        }
 
    SetPointer( aNewPointer );
}
 
 
void BrowseBox::MouseButtonUp( const MouseEvent & rEvt )
{
 
    if ( bResizing )
    {
        // delete auxiliary line
        pDataWin->HideTracking();
 
        // width changed?
        nDragX = std::max( rEvt.GetPosPixel().X(), nMinResizeX );
        if ( (nDragX - nResizeX) != mvCols[ nResizeCol ]->Width() )
        {
            // resize column
            tools::Long nMaxX = pDataWin->GetSizePixel().Width();
            nDragX = std::min( nDragX, nMaxX );
            tools::Long nDeltaX = nDragX - nResizeX;
            sal_uInt16 nId = GetColumnId(nResizeCol);
            SetColumnWidth( GetColumnId(nResizeCol), GetColumnWidth(nId) + nDeltaX );
            ColumnResized( nId );
        }
 
        // end action
        SetPointer( PointerStyle::Arrow );
        ReleaseMouse();
        bResizing = false;
    }
    else
        MouseButtonUp( BrowserMouseEvent( pDataWin,
                MouseEvent( Point( rEvt.GetPosPixel().X(),
                        rEvt.GetPosPixel().Y() - pDataWin->GetPosPixel().Y() ),
                    rEvt.GetClicks(), rEvt.GetMode(), rEvt.GetButtons(),
                    rEvt.GetModifier() ) ) );
}
 
 
static bool bExtendedMode = false;
static bool bFieldMode = false;
 
void BrowseBox::MouseButtonDown( const BrowserMouseEvent& rEvt )
{
 
    GrabFocus();
 
    // adjust selection while and after double-click
    if ( rEvt.GetClicks() == 2 )
    {
        SetNoSelection();
        if ( rEvt.GetRow() >= 0 )
        {
            GoToRow( rEvt.GetRow() );
            SelectRow( rEvt.GetRow(), true, false );
        }
        else
        {
            if ( bColumnCursor && rEvt.GetColumn() != 0 )
            {
                if ( rEvt.GetColumn() < mvCols.size() )
                    SelectColumnPos( rEvt.GetColumn(), true, false);
            }
        }
        DoubleClick( rEvt );
    }
    // selections
    else if ( ( rEvt.GetMode() & ( MouseEventModifiers::SELECT | MouseEventModifiers::SIMPLECLICK ) ) &&
         ( bColumnCursor || rEvt.GetRow() >= 0 ) )
    {
        if ( rEvt.GetClicks() == 1 )
        {
            // initialise flags
            bHit            = false;
 
            // selection out of range?
            if ( rEvt.GetRow() >= nRowCount ||
                 rEvt.GetColumnId() == BROWSER_INVALIDID )
            {
                SetNoSelection();
                return;
            }
 
            // while selecting, no cursor
            bSelecting = true;
            DoHideCursor();
 
            // DataRow?
            if ( rEvt.GetRow() >= 0 )
            {
                // line selection?
                if ( rEvt.GetColumnId() == HandleColumnId || !bColumnCursor )
                {
                    if ( bMultiSelection )
                    {
                        // remove column-selection, if exists
                        if ( pColSel && pColSel->GetSelectCount() )
                        {
                            ToggleSelection();
                            if ( bMultiSelection )
                                uRow.pSel->SelectAll(false);
                            else
                                uRow.nSel = BROWSER_ENDOFSELECTION;
                            if ( pColSel )
                                pColSel->SelectAll(false);
                            bSelect = true;
                        }
 
                        // expanding mode?
                        if ( rEvt.GetMode() & MouseEventModifiers::RANGESELECT )
                        {
                            // select the further touched rows too
                            bSelect = true;
                            ExpandRowSelection( rEvt );
                            return;
                        }
 
                        // click in the selected area?
                        else if ( IsRowSelected( rEvt.GetRow() ) )
                        {
                            // wait for Drag&Drop
                            bHit = true;
                            bExtendedMode = bool( rEvt.GetMode() & MouseEventModifiers::MULTISELECT );
                            return;
                        }
 
                        // extension mode?
                        else if ( rEvt.GetMode() & MouseEventModifiers::MULTISELECT )
                        {
                            // determine the new selection range
                            // and selection/deselection
                            aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() );
                            SelectRow( rEvt.GetRow(),
                                    !uRow.pSel->IsSelected( rEvt.GetRow() ) );
                            bSelect = true;
                            return;
                        }
                    }
 
                    // select directly
                    SetNoSelection();
                    GoToRow( rEvt.GetRow() );
                    SelectRow( rEvt.GetRow() );
                    aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() );
                    bSelect = true;
                }
                else // Column/Field-Selection
                {
                    // click in selected column
                    if ( IsColumnSelected( rEvt.GetColumn() ) ||
                         IsRowSelected( rEvt.GetRow() ) )
                    {
                        bHit = true;
                        bFieldMode = true;
                        return;
                    }
 
                    SetNoSelection();
                    GoToRowColumnId( rEvt.GetRow(), rEvt.GetColumnId() );
                    bSelect = true;
                }
            }
            else
            {
                if ( bMultiSelection && rEvt.GetColumnId() == HandleColumnId )
                {
                    // toggle all-selection
                    if ( uRow.pSel->GetSelectCount() > ( GetRowCount() / 2 ) )
                        SetNoSelection();
                    else
                        SelectAll();
                }
                else
                    SelectColumnPos( GetColumnPos(rEvt.GetColumnId()), true, false);
            }
 
            // turn cursor on again, if necessary
            bSelecting = false;
            DoShowCursor();
            if ( bSelect )
                Select();
        }
    }
}
 
 
void BrowseBox::MouseButtonUp( const BrowserMouseEvent &rEvt )
{
 
    // D&D was possible, but did not occur
    if ( bHit )
    {
        aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() );
        if ( bExtendedMode )
            SelectRow( rEvt.GetRow(), false );
        else
        {
            SetNoSelection();
            if ( bFieldMode )
                GoToRowColumnId( rEvt.GetRow(), rEvt.GetColumnId() );
            else
            {
                GoToRow( rEvt.GetRow() );
                SelectRow( rEvt.GetRow() );
            }
        }
        bSelect = true;
        bExtendedMode = false;
        bFieldMode = false;
        bHit = false;
    }
 
    // activate cursor
    if ( bSelecting )
    {
        bSelecting = false;
        DoShowCursor();
        if ( bSelect )
            Select();
    }
}
 
 
void BrowseBox::KeyInput( const KeyEvent& rEvt )
{
    if ( !ProcessKey( rEvt ) )
        Control::KeyInput( rEvt );
}
 
 
bool BrowseBox::ProcessKey( const KeyEvent& rEvt )
{
 
    sal_uInt16 nCode = rEvt.GetKeyCode().GetCode();
    bool       bShift = rEvt.GetKeyCode().IsShift();
    bool       bCtrl = rEvt.GetKeyCode().IsMod1();
    bool       bAlt = rEvt.GetKeyCode().IsMod2();
 
    sal_uInt16 nId = BROWSER_NONE;
 
    if ( !bAlt && !bCtrl && !bShift )
    {
        switch ( nCode )
        {
            case KEY_DOWN:          nId = BROWSER_CURSORDOWN; break;
            case KEY_UP:            nId = BROWSER_CURSORUP; break;
            case KEY_HOME:          nId = BROWSER_CURSORHOME; break;
            case KEY_END:           nId = BROWSER_CURSOREND; break;
            case KEY_TAB:
                if ( !bColumnCursor )
                    break;
                [[fallthrough]];
            case KEY_RIGHT:         nId = BROWSER_CURSORRIGHT; break;
            case KEY_LEFT:          nId = BROWSER_CURSORLEFT; break;
            case KEY_SPACE:         nId = BROWSER_SELECT; break;
        }
        if ( BROWSER_NONE != nId )
            SetNoSelection();
 
        switch ( nCode )
        {
            case KEY_PAGEDOWN:      nId = BROWSER_CURSORPAGEDOWN; break;
            case KEY_PAGEUP:        nId = BROWSER_CURSORPAGEUP; break;
        }
    }
 
    if ( !bAlt && !bCtrl && bShift )
        switch ( nCode )
        {
            case KEY_DOWN:          nId = BROWSER_SELECTDOWN; break;
            case KEY_UP:            nId = BROWSER_SELECTUP; break;
            case KEY_TAB:
                if ( !bColumnCursor )
                    break;
                                    nId = BROWSER_CURSORLEFT; break;
            case KEY_HOME:          nId = BROWSER_SELECTHOME; break;
            case KEY_END:           nId = BROWSER_SELECTEND; break;
        }
 
 
    if ( !bAlt && bCtrl && !bShift )
        switch ( nCode )
        {
            case KEY_DOWN:          nId = BROWSER_CURSORDOWN; break;
            case KEY_UP:            nId = BROWSER_CURSORUP; break;
            case KEY_PAGEDOWN:      nId = BROWSER_CURSORENDOFFILE; break;
            case KEY_PAGEUP:        nId = BROWSER_CURSORTOPOFFILE; break;
            case KEY_HOME:          nId = BROWSER_CURSORTOPOFSCREEN; break;
            case KEY_END:           nId = BROWSER_CURSORENDOFSCREEN; break;
            case KEY_SPACE:         nId = BROWSER_ENHANCESELECTION; break;
            case KEY_LEFT:          nId = BROWSER_MOVECOLUMNLEFT; break;
            case KEY_RIGHT:         nId = BROWSER_MOVECOLUMNRIGHT; break;
        }
 
    if ( nId != BROWSER_NONE )
        Dispatch( nId );
    return nId != BROWSER_NONE;
}
 
void BrowseBox::ChildFocusIn()
{
}
 
void BrowseBox::ChildFocusOut()
{
}
 
void BrowseBox::Dispatch( sal_uInt16 nId )
{
 
    tools::Long nRowsOnPage = pDataWin->GetSizePixel().Height() / GetDataRowHeight();
 
    switch ( nId )
    {
        case BROWSER_SELECTCOLUMN:
            if ( ColCount() )
                SelectColumnId( GetCurColumnId() );
            break;
 
        case BROWSER_CURSORDOWN:
            if ( ( GetCurRow() + 1 ) < nRowCount )
                GoToRow( GetCurRow() + 1, false );
            break;
        case BROWSER_CURSORUP:
            if ( GetCurRow() > 0 )
                GoToRow( GetCurRow() - 1, false );
            break;
        case BROWSER_SELECTHOME:
            if ( GetRowCount() )
            {
                DoHideCursor();
                for ( sal_Int32 nRow = GetCurRow(); nRow >= 0; --nRow )
                    SelectRow( nRow );
                GoToRow( 0, true );
                DoShowCursor();
            }
            break;
        case BROWSER_SELECTEND:
            if ( GetRowCount() )
            {
                DoHideCursor();
                sal_Int32 nRows = GetRowCount();
                for ( sal_Int32 nRow = GetCurRow(); nRow < nRows; ++nRow )
                    SelectRow( nRow );
                GoToRow( GetRowCount() - 1, true );
                DoShowCursor();
            }
            break;
        case BROWSER_SELECTDOWN:
        {
            if ( GetRowCount() && ( GetCurRow() + 1 ) < nRowCount )
            {
                // deselect the current row, if it isn't the first
                // and there is no other selected row above
                sal_Int32 nRow = GetCurRow();
                bool bLocalSelect = ( !IsRowSelected( nRow ) ||
                                 GetSelectRowCount() == 1 || IsRowSelected( nRow - 1 ) );
                SelectRow( nRow, bLocalSelect );
                bool bDone = GoToRow( GetCurRow() + 1, false );
                if ( bDone )
                    SelectRow( GetCurRow() );
            }
            else
                ScrollRows( 1 );
            break;
        }
        case BROWSER_SELECTUP:
            if ( GetRowCount() )
            {
                // deselect the current row, if it isn't the first
                // and there is no other selected row under
                sal_Int32 nRow = GetCurRow();
                bool bLocalSelect = ( !IsRowSelected( nRow ) ||
                                 GetSelectRowCount() == 1 || IsRowSelected( nRow + 1 ) );
                SelectRow( nCurRow, bLocalSelect );
                bool bDone = GoToRow( nRow - 1, false );
                if ( bDone )
                    SelectRow( GetCurRow() );
            }
            break;
        case BROWSER_CURSORPAGEDOWN:
            ScrollRows( nRowsOnPage );
            break;
        case BROWSER_CURSORPAGEUP:
            ScrollRows( -nRowsOnPage );
            break;
        case BROWSER_CURSOREND:
            if ( bColumnCursor )
            {
                sal_uInt16 nNewId = GetColumnId(ColCount() -1);
                nNewId != HandleColumnId && GoToColumnId( nNewId );
                break;
            }
            [[fallthrough]];
        case BROWSER_CURSORENDOFFILE:
            GoToRow( nRowCount - 1, false );
            break;
        case BROWSER_CURSORRIGHT:
            if ( bColumnCursor )
            {
                sal_uInt16 nNewPos = GetColumnPos( GetCurColumnId() ) + 1;
                sal_uInt16 nNewId = GetColumnId( nNewPos );
                if (nNewId != BROWSER_INVALIDID)    // At end of row ?
                    GoToColumnId( nNewId );
                else
                {
                    sal_uInt16 nColId = GetColumnId(0);
                    if ( nColId == BROWSER_INVALIDID || nColId == HandleColumnId )
                        nColId = GetColumnId(1);
                    if ( GetRowCount() )
                    {
                        if ( nCurRow < GetRowCount() - 1 )
                        {
                            GoToRowColumnId( nCurRow + 1, nColId );
                        }
                    }
                    else if ( ColCount() )
                        GoToColumnId( nColId );
                }
            }
            else
                ScrollColumns( 1 );
            break;
        case BROWSER_CURSORHOME:
            if ( bColumnCursor )
            {
                sal_uInt16 nNewId = GetColumnId(1);
                if (nNewId != HandleColumnId)
                {
                    GoToColumnId( nNewId );
                }
                break;
            }
            [[fallthrough]];
        case BROWSER_CURSORTOPOFFILE:
            GoToRow( 0, false );
            break;
        case BROWSER_CURSORLEFT:
            if ( bColumnCursor )
            {
                sal_uInt16 nNewPos = GetColumnPos( GetCurColumnId() ) - 1;
                sal_uInt16 nNewId = GetColumnId( nNewPos );
                if (nNewId != HandleColumnId)
                    GoToColumnId( nNewId );
                else
                {
                    if ( GetRowCount() )
                    {
                        if (nCurRow > 0)
                        {
                            GoToRowColumnId(nCurRow - 1, GetColumnId(ColCount() -1));
                        }
                    }
                    else if ( ColCount() )
                        GoToColumnId( GetColumnId(ColCount() -1) );
                }
            }
            else
                ScrollColumns( -1 );
            break;
        case BROWSER_ENHANCESELECTION:
            if ( GetRowCount() )
                SelectRow( GetCurRow(), !IsRowSelected( GetCurRow() ) );
            break;
        case BROWSER_SELECT:
            if ( GetRowCount() )
                SelectRow( GetCurRow(), !IsRowSelected( GetCurRow() ), false );
            break;
        case BROWSER_MOVECOLUMNLEFT:
        case BROWSER_MOVECOLUMNRIGHT:
            { // check if column moving is allowed
                BrowserHeader* pHeaderBar = pDataWin->pHeaderBar;
                if ( pHeaderBar && pHeaderBar->IsDragable() )
                {
                    sal_uInt16 nColId = GetCurColumnId();
                    bool bColumnSelected = IsColumnSelected(nColId);
                    sal_uInt16 nNewPos = GetColumnPos(nColId);
                    bool bMoveAllowed = false;
                    if ( BROWSER_MOVECOLUMNLEFT == nId && nNewPos > 1 )
                    {
                        --nNewPos;
                        bMoveAllowed = true;
                    }
                    else if ( BROWSER_MOVECOLUMNRIGHT == nId && nNewPos < (ColCount()-1) )
                    {
                        ++nNewPos;
                        bMoveAllowed = true;
                    }
 
                    if ( bMoveAllowed )
                    {
                        SetColumnPos( nColId, nNewPos );
                        ColumnMoved( nColId );
                        MakeFieldVisible(GetCurRow(), nColId);
                        if ( bColumnSelected )
                            SelectColumnId(nColId);
                    }
                }
            }
            break;
    }
}
 
 
void BrowseBox::SetCursorColor(const Color& _rCol)
{
    if (_rCol == m_aCursorColor)
        return;
 
    // ensure the cursor is hidden
    DoHideCursor();
    if (!m_bFocusOnlyCursor)
        DoHideCursor();
 
    m_aCursorColor = _rCol;
 
    if (!m_bFocusOnlyCursor)
        DoShowCursor();
    DoShowCursor();
}
 
tools::Rectangle BrowseBox::calcHeaderRect(bool _bIsColumnBar, bool _bOnScreen)
{
    vcl::Window* pParent = nullptr;
    if ( !_bOnScreen )
        pParent = GetAccessibleParentWindow();
 
    Point aTopLeft;
    tools::Long nWidth;
    tools::Long nHeight;
    if ( _bIsColumnBar )
    {
        nWidth = pDataWin->GetOutputSizePixel().Width();
        nHeight = GetDataRowHeight();
    }
    else
    {
        aTopLeft.setY( GetDataRowHeight() );
        nWidth = GetColumnWidth(0);
        if (pParent)
            nHeight = GetWindowExtentsRelative( *pParent ).GetHeight() - aTopLeft.Y() - GetControlArea().GetSize().Height();
        else
            nHeight = GetWindowExtentsAbsolute().GetHeight() - aTopLeft.Y() - GetControlArea().GetSize().Height();
    }
    if (pParent)
        aTopLeft += GetWindowExtentsRelative( *pParent ).TopLeft();
    else
        aTopLeft += Point(GetWindowExtentsAbsolute().TopLeft());
    return tools::Rectangle(aTopLeft,Size(nWidth,nHeight));
}
 
tools::Rectangle BrowseBox::calcTableRect(bool _bOnScreen)
{
    vcl::Window* pParent = nullptr;
    if ( !_bOnScreen )
        pParent = GetAccessibleParentWindow();
 
    tools::Rectangle aRect;
    if (pParent)
        aRect = GetWindowExtentsRelative( *pParent );
    else
        aRect = tools::Rectangle(GetWindowExtentsAbsolute());
    tools::Rectangle aRowBar = calcHeaderRect(false, pParent == nullptr);
 
    tools::Long nX = aRowBar.Right() - aRect.Left();
    tools::Long nY = aRowBar.Top() - aRect.Top();
    Size aSize(aRect.GetSize());
 
    return tools::Rectangle(aRowBar.TopRight(), Size(aSize.Width() - nX, aSize.Height() - nY - GetBarHeight()) );
}
 
tools::Rectangle BrowseBox::GetFieldRectPixel( sal_Int32 _nRowId, sal_uInt16 _nColId, bool /*_bIsHeader*/, bool _bOnScreen )
{
    vcl::Window* pParent = nullptr;
    if ( !_bOnScreen )
        pParent = GetAccessibleParentWindow();
 
    tools::Rectangle aRect = GetFieldRectPixel(_nRowId,_nColId,_bOnScreen);
 
    Point aTopLeft = aRect.TopLeft();
    if (pParent)
        aTopLeft += GetWindowExtentsRelative( *pParent ).TopLeft();
    else
        aTopLeft += Point(GetWindowExtentsAbsolute().TopLeft());
 
    return tools::Rectangle(aTopLeft,aRect.GetSize());
}
 
// ------------------------------------------------------------------------- EOF
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

V1048 The 'nDragX' variable was assigned the same value.