/* -*- 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 <sfx2/dispatch.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/help.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/settings.hxx>
#include <svtools/colorcfg.hxx>
#include <osl/diagnose.h>
 
#include <tabvwsh.hxx>
#include <hdrcont.hxx>
#include <dbdata.hxx>
#include <scmod.hxx>
#include <inputopt.hxx>
#include <gridmerg.hxx>
#include <document.hxx>
#include <markdata.hxx>
#include <tabview.hxx>
#include <viewdata.hxx>
#include <columnspanset.hxx>
#include <SheetViewManager.hxx>
#include <officecfg/Office/Common.hxx>
#include <o3tl/enumrange.hxx>
 
#define SC_DRAG_MIN     2
 
//  passes in paint
//  (selection left/right must be first because the continuous lines
//  are partly overwritten later)
 
namespace
{
 
enum class HeaderPaintPass
{
    SelectionBottom,
    Bottom,
    Text,
    LAST = Text
};
 
}
 
ScHeaderControl::ScHeaderControl( vcl::Window* pParent, SelectionEngine* pSelectionEngine,
                                  SCCOLROW nNewSize, bool bNewVertical, ScTabView* pTab ) :
            Window      ( pParent ),
            pSelEngine  ( pSelectionEngine ),
            aShowHelpTimer("sc HeaderControl Popover Timer"),
            bVertical   ( bNewVertical ),
            nSize       ( nNewSize ),
            nMarkStart  ( 0 ),
            nMarkEnd    ( 0 ),
            bMarkRange  ( false ),
            bDragging   ( false ),
            nDragNo     ( 0 ),
            nDragStart  ( 0 ),
            nDragPos    ( 0 ),
            nTipVisible ( nullptr ),
            bDragMoved  ( false ),
            bIgnoreMove ( false ),
            bInRefMode  ( false ),
            pTabView    ( pTab )
{
    // RTL: no default mirroring for this window, the spreadsheet itself
    // is also not mirrored
    // mirror the vertical window for correct border drawing
    // table layout depends on sheet format, not UI setting, so the
    // borders of the vertical window have to be handled manually, too.
    EnableRTL( false );
 
    aNormFont = GetFont();
    aNormFont.SetTransparent( true );       //! hard-set WEIGHT_NORMAL ???
 
    mnDefaultFontHeight = aNormFont.GetFontHeight();
 
    aBoldFont = aNormFont;
    aBoldFont.SetWeight( WEIGHT_BOLD );
    aAutoFilterFont = aNormFont;
 
    SetFont(aBoldFont);
    bBoldSet = true;
    bAutoFilterSet = false;
 
    Size aSize = LogicToPixel( Size(
        GetTextWidth(u"8888"_ustr),
        GetTextHeight() ) );
    aSize.AdjustWidth(4 );    // place for highlight border
    aSize.AdjustHeight(3 );
    SetSizePixel( aSize );
 
    nWidth = nSmallWidth = aSize.Width();
    nBigWidth = LogicToPixel( Size( GetTextWidth(u"8888888"_ustr), 0 ) ).Width() + 5;
 
    aShowHelpTimer.SetInvokeHandler(LINK(this, ScHeaderControl, ShowDragHelpHdl));
    aShowHelpTimer.SetTimeout(GetSettings().GetMouseSettings().GetDoubleClickTime());
 
    SetBackground();
}
 
void ScHeaderControl::dispose()
{
    aShowHelpTimer.Stop();
    vcl::Window::dispose();
}
 
void ScHeaderControl::SetWidth( tools::Long nNew )
{
    OSL_ENSURE( bVertical, "SetWidth works only on row headers" );
    if ( nNew != nWidth )
    {
        Size aSize( nNew, GetSizePixel().Height() );
        SetSizePixel( aSize );
 
        nWidth = nNew;
 
        Invalidate();
    }
}
 
ScHeaderControl::~ScHeaderControl()
{
}
 
void ScHeaderControl::DoPaint( SCCOLROW nStart, SCCOLROW nEnd )
{
    bool bLayoutRTL = IsLayoutRTL();
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
 
    tools::Rectangle aRect( Point(0,0), GetOutputSizePixel() );
    if ( bVertical )
    {
        aRect.SetTop( GetScrPos( nStart )-nLayoutSign );      // extra pixel for line at top of selection
        aRect.SetBottom( GetScrPos( nEnd+1 )-nLayoutSign );
    }
    else
    {
        aRect.SetLeft( GetScrPos( nStart )-nLayoutSign );     // extra pixel for line left of selection
        aRect.SetRight( GetScrPos( nEnd+1 )-nLayoutSign );
    }
    Invalidate(aRect);
}
 
void ScHeaderControl::SetMark( bool bNewSet, SCCOLROW nNewStart, SCCOLROW nNewEnd )
{
    bool bEnabled = ScModule::get()->GetInputOptions().GetMarkHeader(); //! cache?
    if (!bEnabled)
        bNewSet = false;
 
    bool bOldSet       = bMarkRange;
    SCCOLROW nOldStart = nMarkStart;
    SCCOLROW nOldEnd   = nMarkEnd;
    PutInOrder( nNewStart, nNewEnd );
    bMarkRange = bNewSet;
    nMarkStart = nNewStart;
    nMarkEnd   = nNewEnd;
 
    //  Paint
 
    if ( bNewSet )
    {
        if ( bOldSet )
        {
            if ( nNewStart == nOldStart )
            {
                if ( nNewEnd != nOldEnd )
                    DoPaint( std::min( nNewEnd, nOldEnd ) + 1, std::max( nNewEnd, nOldEnd ) );
            }
            else if ( nNewEnd == nOldEnd )
                DoPaint( std::min( nNewStart, nOldStart ), std::max( nNewStart, nOldStart ) - 1 );
            else if ( nNewStart > nOldEnd || nNewEnd < nOldStart )
            {
                //  two areas
                DoPaint( nOldStart, nOldEnd );
                DoPaint( nNewStart, nNewEnd );
            }
            else //  somehow overlapping... (it is not often)
                DoPaint( std::min( nNewStart, nOldStart ), std::max( nNewEnd, nOldEnd ) );
        }
        else
            DoPaint( nNewStart, nNewEnd );      //  completely new selection
    }
    else if ( bOldSet )
        DoPaint( nOldStart, nOldEnd );          //  cancel selection
}
 
tools::Long ScHeaderControl::GetScrPos( SCCOLROW nEntryNo ) const
{
    tools::Long nScrPos;
 
    tools::Long nMax = ( bVertical ? GetOutputSizePixel().Height() : GetOutputSizePixel().Width() ) + 1;
    if (nEntryNo >= nSize)
        nScrPos = nMax;
    else
    {
        nScrPos = 0;
        for (SCCOLROW i=GetPos(); i<nEntryNo && nScrPos<nMax; i++)
        {
            sal_uInt16 nAdd = GetEntrySize(i);
            if (nAdd)
                nScrPos += nAdd;
            else
            {
                SCCOLROW nHidden = GetHiddenCount(i);
                if (nHidden > 0)
                    i += nHidden - 1;
            }
        }
    }
 
    if ( IsLayoutRTL() )
        nScrPos = nMax - nScrPos - 2;
 
    return nScrPos;
}
 
void ScHeaderControl::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect )
{
    // It is important for VCL to have few calls, that is why the outer lines are
    // grouped together
 
    const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
    bool bHighContrast = rStyleSettings.GetHighContrastMode();
    bool bDark = rStyleSettings.GetFaceColor().IsDark();
    // Use the same distinction for bDark as in Window::DrawSelectionBackground
 
    Color aTextColor = rStyleSettings.GetButtonTextColor();
    Color aSelTextColor = rStyleSettings.GetHighlightTextColor();
    Color aAFilterTextColor = rStyleSettings.GetButtonTextColor();
    aAFilterTextColor.Merge(COL_LIGHTBLUE, bDark ? 150 : 10); // color of filtered row numbers
    aNormFont.SetColor( aTextColor );
    aAutoFilterFont.SetColor(aAFilterTextColor);
    if ( bHighContrast )
        aBoldFont.SetColor( aTextColor );
    else
        aBoldFont.SetColor( aSelTextColor );
 
    if (bAutoFilterSet)
        SetTextColor(aAFilterTextColor);
    else
        SetTextColor((bBoldSet && !bHighContrast) ? aSelTextColor : aTextColor);
 
    ScModule* mod = ScModule::get();
    Color aSelLineColor = mod->GetColorConfig().GetColorValue(svtools::CALCCELLFOCUS).nColor;
    aSelLineColor.Merge( COL_BLACK, 0xe0 );        // darken just a little bit
 
    bool bLayoutRTL = IsLayoutRTL();
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
 
    OUString            aString;
    sal_uInt16          nBarSize;
    Point               aScrPos;
 
    if (bVertical)
        nBarSize = static_cast<sal_uInt16>(GetSizePixel().Width());
    else
        nBarSize = static_cast<sal_uInt16>(GetSizePixel().Height());
 
    SCCOLROW    nPos = GetPos();
 
    tools::Long nPStart = bVertical ? rRect.Top() : rRect.Left();
    tools::Long nPEnd = bVertical ? rRect.Bottom() : rRect.Right();
 
    tools::Long nTransStart = nPEnd + 1;
    tools::Long nTransEnd = 0;
 
    tools::Long nInitScrPos = 0;
    if ( bLayoutRTL )
    {
        std::swap(nPStart, nPEnd);
        std::swap(nTransStart, nTransEnd);
        if ( bVertical )            // start loops from the end
            nInitScrPos = GetSizePixel().Height() - 1;
        else
            nInitScrPos = GetSizePixel().Width() - 1;
    }
 
    // complete the painting of the outer lines
    // first find the end of the last cell
 
    tools::Long nLineEnd = nInitScrPos - nLayoutSign;
 
    for (SCCOLROW i=nPos; i<nSize; i++)
    {
        sal_uInt16 nSizePix = GetEntrySize( i );
        if (nSizePix)
        {
            nLineEnd += nSizePix * nLayoutSign;
 
            if ( bMarkRange && i >= nMarkStart && i <= nMarkEnd )
            {
                tools::Long nLineStart = nLineEnd - ( nSizePix - 1 ) * nLayoutSign;
                if ( nLineStart * nLayoutSign < nTransStart * nLayoutSign )
                    nTransStart = nLineStart;
                if ( nLineEnd * nLayoutSign > nTransEnd * nLayoutSign )
                    nTransEnd = nLineEnd;
            }
 
            if ( nLineEnd * nLayoutSign > nPEnd * nLayoutSign )
            {
                nLineEnd = nPEnd;
                break;
            }
        }
        else
        {
            SCCOLROW nHidden = GetHiddenCount(i);
            if (nHidden > 0)
                i += nHidden - 1;
        }
    }
 
    //  background is different for entry area and behind the entries
 
    tools::Rectangle aFillRect;
    GetOutDev()->SetLineColor();
 
    if ( nLineEnd * nLayoutSign >= nInitScrPos * nLayoutSign )
    {
        Color aFaceColor(rStyleSettings.GetFaceColor());
        if (pTabView)
        {
            ScViewData& rViewData = pTabView->GetViewData();
            sc::SheetViewID nSheetViewID = rViewData.GetSheetViewID();
            if (nSheetViewID >= 0)
            {
                auto pSheetManager = rViewData.GetDocument().GetSheetViewManager(rViewData.GetTabNumber());
                auto pSheetView = pSheetManager->get(nSheetViewID);
                if (pSheetView->isSynced())
                    aFaceColor.Merge(COL_LIGHTBLUE, 220);
                else
                    aFaceColor.Merge(COL_LIGHTRED, 220);
            }
        }
        if (bDark)
            aFaceColor.IncreaseLuminance(20);
        else
            aFaceColor.DecreaseLuminance(20);
        GetOutDev()->SetFillColor(aFaceColor);
        if ( bVertical )
            aFillRect = tools::Rectangle( 0, nInitScrPos, nBarSize-1, nLineEnd );
        else
            aFillRect = tools::Rectangle( nInitScrPos, 0, nLineEnd, nBarSize-1 );
        GetOutDev()->DrawRect( aFillRect );
    }
 
    if ( nLineEnd * nLayoutSign < nPEnd * nLayoutSign )
    {
        Color aAppBackgroundColor = mod->GetColorConfig().GetColorValue(svtools::APPBACKGROUND).nColor;
        GetOutDev()->SetFillColor(aAppBackgroundColor);
        if ( bVertical )
            aFillRect = tools::Rectangle( 0, nLineEnd+nLayoutSign, nBarSize-1, nPEnd );
        else
            aFillRect = tools::Rectangle( nLineEnd+nLayoutSign, 0, nPEnd, nBarSize-1 );
        GetOutDev()->DrawRect( aFillRect );
    }
 
    if ( nLineEnd * nLayoutSign >= nPStart * nLayoutSign )
    {
        if ( nTransEnd * nLayoutSign >= nTransStart * nLayoutSign )
        {
            if (bVertical)
                aFillRect = tools::Rectangle( 0, nTransStart, nBarSize-1, nTransEnd );
            else
                aFillRect = tools::Rectangle( nTransStart, 0, nTransEnd, nBarSize-1 );
 
            if ( bHighContrast )
            {
                if ( bDark )
                {
                    //  solid grey background for dark face color is drawn before lines
                    GetOutDev()->SetLineColor();
                    GetOutDev()->SetFillColor( COL_LIGHTGRAY );
                    GetOutDev()->DrawRect( aFillRect );
                }
            }
            else
            {
                // background for selection
                GetOutDev()->SetLineColor();
                Color aColor = mod->GetColorConfig().GetColorValue(svtools::CALCCELLFOCUS).nColor;
// merging the highlightcolor (which is used if accent does not exist) with the background
// fails in many cases such as Breeze Dark (highlight is too close to background) and
// Breeze Light (font color is white and not readable anymore)
#ifdef MACOSX
                aColor.Merge( rStyleSettings.GetFaceColor(), 80 );
#endif
                GetOutDev()->SetFillColor( aColor );
                GetOutDev()->DrawRect( aFillRect );
            }
        }
 
        GetOutDev()->SetLineColor( rStyleSettings.GetShadowColor() );
        if (bVertical)
        {
            GetOutDev()->DrawLine( Point( 0, nPStart ), Point( 0, nLineEnd ) ); //left
            GetOutDev()->DrawLine( Point( nBarSize-1, nPStart ), Point( nBarSize-1, nLineEnd ) ); //right
        }
        else
        {
            GetOutDev()->DrawLine( Point( nPStart, nBarSize-1 ), Point( nLineEnd, nBarSize-1 ) ); //bottom
            GetOutDev()->DrawLine( Point( nPStart, 0 ), Point( nLineEnd, 0 ) ); //top
        }
    }
 
    // tdf#89841 Use blue row numbers when Autofilter selected
    std::vector<sc::ColRowSpan> aSpans;
    if (bVertical && pTabView)
    {
        SCTAB nTab = pTabView->GetViewData().CurrentTabForData();
        ScDocument& rDoc = pTabView->GetViewData().GetDocument();
 
        ScDBData* pDBData = rDoc.GetAnonymousDBData(nTab);
        if (pDBData && pDBData->HasAutoFilter())
        {
            SCSIZE nSelected = 0;
            SCSIZE nTotal = 0;
            pDBData->GetFilterSelCount(nSelected, nTotal);
            if (nTotal > nSelected)
            {
                ScRange aRange;
                pDBData->GetArea(aRange);
                SCCOLROW nStartRow = static_cast<SCCOLROW>(aRange.aStart.Row());
                SCCOLROW nEndRow = static_cast<SCCOLROW>(aRange.aEnd.Row());
                if (pDBData->HasHeader())
                    nStartRow++;
                aSpans.push_back(sc::ColRowSpan(nStartRow, nEndRow));
            }
        }
 
        ScDBCollection* pDocColl = rDoc.GetDBCollection();
        if (!pDocColl->empty())
        {
            ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs();
            for (const auto& rxDB : rDBs)
            {
                if (rxDB->GetTab() == nTab && rxDB->HasAutoFilter())
                {
                    SCSIZE nSelected = 0;
                    SCSIZE nTotal = 0;
                    rxDB->GetFilterSelCount(nSelected, nTotal);
                    if (nTotal > nSelected)
                    {
                        ScRange aRange;
                        rxDB->GetArea(aRange);
                        SCCOLROW nStartRow = static_cast<SCCOLROW>(aRange.aStart.Row());
                        SCCOLROW nEndRow = static_cast<SCCOLROW>(aRange.aEnd.Row());
                        if (rxDB->HasHeader())
                            nStartRow++;
                        aSpans.push_back(sc::ColRowSpan(nStartRow, nEndRow));
                    }
                }
            }
        }
    }
 
    //  loop through entries several times to avoid changing the line color too often
    //  and to allow merging of lines
 
    ScGridMerger aGrid( GetOutDev(), 1, 1 );
 
    for (HeaderPaintPass ePass : o3tl::enumrange<HeaderPaintPass>())
    {
        //  set line color etc. before entry loop
        switch (ePass)
        {
            case HeaderPaintPass::SelectionBottom:
                // same as non-selected for high contrast
                GetOutDev()->SetLineColor( bHighContrast ? rStyleSettings.GetShadowColor() : aSelLineColor );
                break;
            case HeaderPaintPass::Bottom:
                GetOutDev()->SetLineColor( rStyleSettings.GetShadowColor() );
                break;
            case HeaderPaintPass::Text:
                // DrawSelectionBackground is used only for high contrast on light background
                if ( nTransEnd * nLayoutSign >= nTransStart * nLayoutSign && bHighContrast && !bDark )
                {
                    //  Transparent selection background is drawn after lines, before text.
                    //  Use DrawSelectionBackground to make sure there is a visible
                    //  difference. The case of a dark face color, where DrawSelectionBackground
                    //  would just paint over the lines, is handled separately (bDark).
                    //  Otherwise, GetHighlightColor is used with 80% transparency.
                    //  The window's background color (SetBackground) has to be the background
                    //  of the cell area, for the contrast comparison in DrawSelectionBackground.
 
                    tools::Rectangle aTransRect;
                    if (bVertical)
                        aTransRect = tools::Rectangle( 0, nTransStart, nBarSize-1, nTransEnd );
                    else
                        aTransRect = tools::Rectangle( nTransStart, 0, nTransEnd, nBarSize-1 );
                    SetBackground( rStyleSettings.GetFaceColor() );
                    DrawSelectionBackground( aTransRect, 0, true, false );
                    SetBackground();
                }
                break;
        }
 
        SCCOLROW nCount = 0;
        tools::Long nScrPos = nInitScrPos;
        sal_Int32 nFontHeight = 0;
        do
        {
            if (bVertical)
                aScrPos = Point( 0, nScrPos );
            else
                aScrPos = Point( nScrPos, 0 );
 
            SCCOLROW    nEntryNo = nCount + nPos;
            if ( nEntryNo >= nSize )                // rDoc.MaxCol()/rDoc.MaxRow()
                nScrPos = nPEnd + nLayoutSign;      //  beyond nPEnd -> stop
            else
            {
                sal_uInt16 nSizePix = GetEntrySize( nEntryNo );
 
                if (nSizePix == 0)
                {
                    SCCOLROW nHidden = GetHiddenCount(nEntryNo);
                    if (nHidden > 0)
                        nCount += nHidden - 1;
                }
                else if ((nScrPos+nSizePix*nLayoutSign)*nLayoutSign >= nPStart*nLayoutSign)
                {
                    Point aEndPos(aScrPos);
                    if (bVertical)
                        aEndPos = Point( aScrPos.X()+nBarSize-1, aScrPos.Y()+(nSizePix-1)*nLayoutSign );
                    else
                        aEndPos = Point( aScrPos.X()+(nSizePix-1)*nLayoutSign, aScrPos.Y()+nBarSize-1 );
 
                    bool bMark = bMarkRange && nEntryNo >= nMarkStart && nEntryNo <= nMarkEnd;
                    bool bNextToMark = bMarkRange && nEntryNo + 1 >= nMarkStart && nEntryNo <= nMarkEnd;
 
                    switch (ePass)
                    {
                        case HeaderPaintPass::SelectionBottom:
                        case HeaderPaintPass::Bottom:
                            if (ePass == (bNextToMark ? HeaderPaintPass::SelectionBottom : HeaderPaintPass::Bottom))
                            {
                                if (bVertical)
                                    aGrid.AddHorLine(/* here we work in pixels */ true, aScrPos.X(), aEndPos.X(), aEndPos.Y());
                                else
                                    aGrid.AddVerLine(/* here we work in pixels */ true, aEndPos.X(), aScrPos.Y(), aEndPos.Y());
 
                                //  thick bottom for hidden rows
                                //  (drawn directly, without aGrid)
                                if ( nEntryNo+1 < nSize )
                                    if ( GetEntrySize(nEntryNo+1)==0 )
                                    {
                                        if (bVertical)
                                            GetOutDev()->DrawLine( Point(aScrPos.X(),aEndPos.Y()-nLayoutSign),
                                                      Point(aEndPos.X(),aEndPos.Y()-nLayoutSign) );
                                        else
                                            GetOutDev()->DrawLine( Point(aEndPos.X()-nLayoutSign,aScrPos.Y()),
                                                      Point(aEndPos.X()-nLayoutSign,aEndPos.Y()) );
                                    }
                            }
                            break;
 
                        case HeaderPaintPass::Text:
                            if ( nSizePix > 1 )     // minimal check for small columns/rows
                            {
                                sal_Int32 nCurrentFontHeight = std::min(aEndPos.Y() - aScrPos.Y() + 1, mnDefaultFontHeight);
                                bool bChangedHeight = false;
                                if (nFontHeight != nCurrentFontHeight)
                                {
                                    nFontHeight = nCurrentFontHeight;
                                    aNormFont.SetFontHeight(nFontHeight);
                                    aBoldFont.SetFontHeight(nFontHeight);
                                    aAutoFilterFont.SetFontHeight(nFontHeight);
                                    bChangedHeight = true;
                                }
 
                                if (bVertical)
                                {
                                    bool bAutoFilterPos = false;
                                    for (const auto& rSpan : aSpans)
                                    {
                                        if (nEntryNo >= rSpan.mnStart && nEntryNo <= rSpan.mnEnd)
                                        {
                                            bAutoFilterPos = true;
                                            break;
                                        }
                                    }
 
                                    if (bMark != bBoldSet || bAutoFilterPos != bAutoFilterSet || bChangedHeight)
                                    {
                                        if (bMark)
                                            SetFont(aBoldFont);
                                        else if (bAutoFilterPos)
                                            SetFont(aAutoFilterFont);
                                        else
                                            SetFont(aNormFont);
                                        bBoldSet = bMark;
                                        bAutoFilterSet = bAutoFilterPos && !bMark;
                                    }
                                }
                                else
                                {
                                    if (bMark != bBoldSet || bChangedHeight)
                                    {
                                        if (bMark)
                                            SetFont(aBoldFont);
                                        else
                                            SetFont(aNormFont);
                                        bBoldSet = bMark;
                                    }
                                }
                                aString = GetEntryText(nEntryNo);
                                const bool bRight
                                    = GetTextWidth(aString) > fabs(aScrPos.X() - aEndPos.X());
                                DrawTextFlags nDrawTextStyle(
                                    (bRight ? DrawTextFlags::Right : DrawTextFlags::Center)
                                    | DrawTextFlags::VCenter | DrawTextFlags::Clip);
                                tools::Rectangle aRect;
                                if (!bVertical && bLayoutRTL)
                                    aRect = tools::Rectangle(aEndPos.X(), aScrPos.Y(), aScrPos.X(),
                                                             aEndPos.Y());
                                else
                                    aRect = tools::Rectangle(aScrPos.X(), aScrPos.Y(), aEndPos.X(),
                                                             aEndPos.Y());
                                GetOutDev()->DrawText(aRect, aString, nDrawTextStyle);
                            }
                            break;
                    }
 
                    // when selecting the complete row/column:
                    //  InvertRect( Rectangle( aScrPos, aEndPos ) );
                }
                nScrPos += nSizePix * nLayoutSign;      // also if before the visible area
            }
            ++nCount;
        }
        while ( nScrPos * nLayoutSign <= nPEnd * nLayoutSign );
 
        aGrid.Flush();
    }
}
 
SCCOLROW ScHeaderControl::GetMousePos(const Point& rPos, bool& rBorder) const
{
    bool        bFound = false;
    SCCOLROW    nPos = GetPos();
    SCCOLROW    nHitNo = nPos;
    SCCOLROW    nEntryNo = 1 + nPos;
    tools::Long    nScrPos;
    tools::Long    nMousePos = bVertical ? rPos.Y() : rPos.X();
    tools::Long    nDif;
    Size    aSize = GetOutputSizePixel();
    tools::Long    nWinSize = bVertical ? aSize.Height() : aSize.Width();
 
    bool bLayoutRTL = IsLayoutRTL();
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
    tools::Long nEndPos = bLayoutRTL ? -1 : nWinSize;
 
    nScrPos = GetScrPos( nPos ) - nLayoutSign;
    do
    {
        if (nEntryNo > nSize)
            nScrPos = nEndPos + nLayoutSign;
        else
            nScrPos += GetEntrySize( nEntryNo - 1 ) * nLayoutSign;      //! GetHiddenCount() ??
 
        nDif = nMousePos - nScrPos;
        if (nDif >= -5 && nDif <= 5)
        {
            bFound = true;
            nHitNo=nEntryNo-1;
        }
        else if (nDif * nLayoutSign >= 0 && nEntryNo < nSize)
            nHitNo = nEntryNo;
        ++nEntryNo;
    }
    while ( nScrPos * nLayoutSign < nEndPos * nLayoutSign && nDif * nLayoutSign > 0 );
 
    rBorder = bFound;
    return nHitNo;
}
 
bool ScHeaderControl::IsSelectionAllowed(SCCOLROW nPos) const
{
    ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
    if (!pViewSh)
        return false;
 
    ScViewData& rViewData = pViewSh->GetViewData();
    sal_uInt16 nTab = rViewData.CurrentTabForData();
    ScDocument& rDoc = rViewData.GetDocument();
    const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
    bool bSelectAllowed = true;
    if ( pProtect && pProtect->isProtected() )
    {
        // This sheet is protected.  Check if a context menu is allowed on this cell.
        bool bCellsProtected = false;
        if (bVertical)
        {
            // row header
            SCROW nRPos = static_cast<SCROW>(nPos);
            bCellsProtected = rDoc.HasAttrib(0, nRPos, nTab, rDoc.MaxCol(), nRPos, nTab, HasAttrFlags::Protected);
        }
        else
        {
            // column header
            SCCOL nCPos = static_cast<SCCOL>(nPos);
            bCellsProtected = rDoc.HasAttrib(nCPos, 0, nTab, nCPos, rDoc.MaxRow(), nTab, HasAttrFlags::Protected);
        }
 
        bool bSelProtected   = pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bool bSelUnprotected = pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
 
        if (bCellsProtected)
            bSelectAllowed = bSelProtected;
        else
            bSelectAllowed = bSelUnprotected;
    }
    return bSelectAllowed;
}
 
void ScHeaderControl::MouseButtonDown( const MouseEvent& rMEvt )
{
    if (IsDisabled())
        return;
 
    bIgnoreMove = false;
    SelectWindow();
 
    bool bIsBorder;
    SCCOLROW nHitNo = GetMousePos(rMEvt.GetPosPixel(), bIsBorder);
    if (!IsSelectionAllowed(nHitNo))
        return;
    if ( ! rMEvt.IsLeft() )
        return;
    if (ScModule::get()->IsFormulaMode())
    {
        if( !pTabView )
            return;
        SCTAB nTab = pTabView->GetViewData().CurrentTabForData();
        if( !rMEvt.IsShift() )
            pTabView->DoneRefMode( rMEvt.IsMod1() );
        ScDocument& rDoc = pTabView->GetViewData().GetDocument();
        if( !bVertical )
        {
            pTabView->InitRefMode( nHitNo, 0, nTab, SC_REFTYPE_REF );
            pTabView->UpdateRef( nHitNo, rDoc.MaxRow(), nTab );
        }
        else
        {
            pTabView->InitRefMode( 0, nHitNo, nTab, SC_REFTYPE_REF );
            pTabView->UpdateRef( rDoc.MaxCol(), nHitNo, nTab );
        }
        bInRefMode = true;
        return;
    }
    if ( bIsBorder && ResizeAllowed() )
    {
        nDragNo = nHitNo;
        sal_uInt16 nClicks = rMEvt.GetClicks();
        if ( nClicks && nClicks%2==0 )
        {
            SetEntrySize( nDragNo, HDR_SIZE_OPTIMUM );
            SetPointer( PointerStyle::Arrow );
        }
        else
        {
            if (bVertical)
                nDragStart = rMEvt.GetPosPixel().Y();
            else
                nDragStart = rMEvt.GetPosPixel().X();
            nDragPos = nDragStart;
            // tdf#140833 launch help tip to show after the double click time has expired
            // so under gtk the popover isn't active when the double click is processed
            // by gtk because under load on wayland the double click is getting handled
            // by something else and getting sent to the window underneath our window
            aShowHelpTimer.Start();
            DrawInvert( nDragPos );
 
            StartTracking();
            bDragging = true;
            bDragMoved = false;
        }
    }
    else
    {
        pSelEngine->SetWindow( this );
        tools::Rectangle aVis( Point(), GetOutputSizePixel() );
        if (bVertical)
        {
            aVis.SetLeft( LONG_MIN );
            aVis.SetRight( LONG_MAX );
        }
        else
        {
            aVis.SetTop( LONG_MIN );
            aVis.SetBottom( LONG_MAX );
        }
        pSelEngine->SetVisibleArea( aVis );
 
        SetMarking( true );     //  must precede SelMouseButtonDown
        pSelEngine->SelMouseButtonDown( rMEvt );
 
        //  In column/row headers a simple click already is a selection.
        //  -> Call SelMouseMove to ensure CreateAnchor is called (and DestroyAnchor
        //  if the next click is somewhere else with Control key).
        pSelEngine->SelMouseMove( rMEvt );
 
        if (IsMouseCaptured())
        {
            // tracking instead of CaptureMouse, so it can be cancelled cleanly
            //! someday SelectionEngine itself should call StartTracking!?!
            ReleaseMouse();
            StartTracking();
        }
    }
}
 
void ScHeaderControl::MouseButtonUp( const MouseEvent& rMEvt )
{
    if ( IsDisabled() )
        return;
 
    if (ScModule* mod = ScModule::get(); mod->IsFormulaMode())
    {
        mod->EndReference();
        bInRefMode = false;
        return;
    }
 
    SetMarking( false );
    bIgnoreMove = false;
 
    if ( bDragging )
    {
        DrawInvert( nDragPos );
        ReleaseMouse();
        HideDragHelp();
        bDragging = false;
 
        tools::Long nScrPos    = GetScrPos( nDragNo );
        tools::Long nMousePos  = bVertical ? rMEvt.GetPosPixel().Y() : rMEvt.GetPosPixel().X();
        bool bLayoutRTL = IsLayoutRTL();
        tools::Long nNewWidth  = bLayoutRTL ? ( nScrPos - nMousePos + 1 )
                                     : ( nMousePos + 2 - nScrPos );
 
        if ( nNewWidth < 0 /* && !IsSelected(nDragNo) */ )
        {
            SCCOLROW nStart = 0;
            SCCOLROW nEnd = nDragNo;
            while (nNewWidth < 0)
            {
                nStart = nDragNo;
                if (nDragNo>0)
                {
                    --nDragNo;
                    nNewWidth += GetEntrySize( nDragNo );   //! GetHiddenCount() ???
                }
                else
                    nNewWidth = 0;
            }
            HideEntries( nStart, nEnd );
        }
        else
        {
            if (bDragMoved)
                SetEntrySize( nDragNo, static_cast<sal_uInt16>(nNewWidth) );
        }
    }
    else
    {
        pSelEngine->SelMouseButtonUp( rMEvt );
        ReleaseMouse();
    }
}
 
void ScHeaderControl::MouseMove( const MouseEvent& rMEvt )
{
    if ( IsDisabled() )
    {
        SetPointer( PointerStyle::Arrow );
        return;
    }
 
    if (bInRefMode && rMEvt.IsLeft() && ScModule::get()->IsFormulaMode())
    {
        if( !pTabView )
            return;
        bool bTmp;
        SCCOLROW nHitNo = GetMousePos(rMEvt.GetPosPixel(), bTmp);
        SCTAB nTab = pTabView->GetViewData().CurrentTabForData();
        ScDocument& rDoc = pTabView->GetViewData().GetDocument();
        if( !bVertical )
            pTabView->UpdateRef( nHitNo, rDoc.MaxRow(), nTab );
        else
            pTabView->UpdateRef( rDoc.MaxCol(), nHitNo, nTab );
 
        return;
    }
 
    if ( bDragging )
    {
        tools::Long nNewPos = bVertical ? rMEvt.GetPosPixel().Y() : rMEvt.GetPosPixel().X();
        if ( nNewPos != nDragPos )
        {
            DrawInvert( nDragPos );
            nDragPos = nNewPos;
            ShowDragHelp();
            DrawInvert( nDragPos );
 
            if (nDragPos <= nDragStart-SC_DRAG_MIN || nDragPos >= nDragStart+SC_DRAG_MIN)
                bDragMoved = true;
        }
    }
    else
    {
        bool bIsBorder;
        (void)GetMousePos(rMEvt.GetPosPixel(), bIsBorder);
 
        if ( bIsBorder && rMEvt.GetButtons()==0 && ResizeAllowed() )
            SetPointer( bVertical ? PointerStyle::VSizeBar : PointerStyle::HSizeBar );
        else
            SetPointer( PointerStyle::Arrow );
 
        if (!bIgnoreMove)
            pSelEngine->SelMouseMove( rMEvt );
    }
}
 
void ScHeaderControl::Tracking( const TrackingEvent& rTEvt )
{
    // Distribute the tracking events to the various MouseEvents, because
    // SelectionEngine does not know anything about Tracking
 
    if ( rTEvt.IsTrackingCanceled() )
        StopMarking();
    else if ( rTEvt.IsTrackingEnded() )
        MouseButtonUp( rTEvt.GetMouseEvent() );
    else
        MouseMove( rTEvt.GetMouseEvent() );
}
 
void ScHeaderControl::Command( const CommandEvent& rCEvt )
{
    CommandEventId nCmd = rCEvt.GetCommand();
    if ( nCmd == CommandEventId::ContextMenu )
    {
        StopMarking();      // finish selection / dragging
 
        // execute popup menu
 
        ScTabViewShell* pViewSh = dynamic_cast< ScTabViewShell *>( SfxViewShell::Current() );
        if ( pViewSh )
        {
            if ( rCEvt.IsMouseEvent() )
            {
                // #i18735# select the column/row under the mouse pointer
                ScViewData& rViewData = pViewSh->GetViewData();
 
                SelectWindow();     // also deselects drawing objects, stops draw text edit
                if ( rViewData.HasEditView( rViewData.GetActivePart() ) )
                    ScModule::get()->InputEnterHandler(); // always end edit mode
 
                bool bBorder;
                SCCOLROW nPos = GetMousePos(rCEvt.GetMousePosPixel(), bBorder );
                if (!IsSelectionAllowed(nPos))
                    // Selecting this cell is not allowed, neither is context menu.
                    return;
 
                SCTAB nTab = rViewData.CurrentTabForData();
                ScDocument& rDoc = pViewSh->GetViewData().GetDocument();
                ScRange aNewRange;
                if ( bVertical )
                    aNewRange = ScRange( 0, sal::static_int_cast<SCROW>(nPos), nTab,
                                         rDoc.MaxCol(), sal::static_int_cast<SCROW>(nPos), nTab );
                else
                    aNewRange = ScRange( sal::static_int_cast<SCCOL>(nPos), 0, nTab,
                                         sal::static_int_cast<SCCOL>(nPos), rDoc.MaxRow(), nTab );
 
                // see if any part of the range is already selected
                ScRangeList aRanges;
                rViewData.GetMarkData().FillRangeListWithMarks( &aRanges, false );
                bool bSelected = aRanges.Intersects(aNewRange);
 
                // select the range if no part of it was selected
                if ( !bSelected )
                    pViewSh->MarkRange( aNewRange );
            }
 
            pViewSh->GetDispatcher()->ExecutePopup( bVertical ? u"rowheader"_ustr : u"colheader"_ustr );
        }
    }
    else if ( nCmd == CommandEventId::StartDrag )
    {
        pSelEngine->Command( rCEvt );
    }
}
 
void ScHeaderControl::StopMarking()
{
    if ( bDragging )
    {
        DrawInvert( nDragPos );
        HideDragHelp();
        bDragging = false;
    }
 
    SetMarking( false );
    bIgnoreMove = true;
 
    //  don't call pSelEngine->Reset, so selection across the parts of
    //  a split/frozen view is possible
    if (IsMouseCaptured())
        ReleaseMouse();
}
 
IMPL_LINK_NOARG(ScHeaderControl, ShowDragHelpHdl, Timer*, void)
{
    ShowDragHelp();
}
 
void ScHeaderControl::ShowDragHelp()
{
    aShowHelpTimer.Stop();
    if (!Help::IsQuickHelpEnabled())
        return;
 
    tools::Long nScrPos    = GetScrPos( nDragNo );
    bool bLayoutRTL = IsLayoutRTL();
    tools::Long nVal = bLayoutRTL ? ( nScrPos - nDragPos + 1 )
                           : ( nDragPos + 2 - nScrPos );
 
    OUString aHelpStr = GetDragHelp( nVal );
    Point aPos = OutputToScreenPixel( Point(0,0) );
    Size aSize = GetSizePixel();
 
    Point aMousePos = OutputToScreenPixel(GetPointerPosPixel());
 
    tools::Rectangle aRect;
    QuickHelpFlags nAlign;
    if (!bVertical)
    {
        // above
        aRect.SetLeft( aMousePos.X() );
        aRect.SetTop( aPos.Y() - 4 );
        nAlign       = QuickHelpFlags::Bottom|QuickHelpFlags::Center;
    }
    else
    {
        // top right
        aRect.SetLeft( aPos.X() + aSize.Width() + 8 );
        aRect.SetTop( aMousePos.Y() - 2 );
        nAlign       = QuickHelpFlags::Left|QuickHelpFlags::Bottom;
    }
 
    aRect.SetRight( aRect.Left() );
    aRect.SetBottom( aRect.Top() );
 
    if (nTipVisible)
        Help::HidePopover(this, nTipVisible);
    nTipVisible = Help::ShowPopover(this, aRect, aHelpStr, nAlign);
}
 
void ScHeaderControl::HideDragHelp()
{
    aShowHelpTimer.Stop();
    if (nTipVisible)
    {
        Help::HidePopover(this, nTipVisible);
        nTipVisible = nullptr;
    }
}
 
void ScHeaderControl::RequestHelp( const HelpEvent& rHEvt )
{
    //  If the own QuickHelp is displayed, don't let RequestHelp remove it
 
    bool bOwn = bDragging && Help::IsQuickHelpEnabled();
    if (!bOwn)
        Window::RequestHelp(rHEvt);
}
 
//                  dummies for virtual methods
 
SCCOLROW ScHeaderControl::GetHiddenCount( SCCOLROW nEntryNo ) const
{
    SCCOLROW nHidden = 0;
    while ( nEntryNo < nSize && GetEntrySize( nEntryNo ) == 0 )
    {
        ++nEntryNo;
        ++nHidden;
    }
    return nHidden;
}
 
bool ScHeaderControl::IsLayoutRTL() const
{
    return false;
}
 
bool ScHeaderControl::IsDisabled() const
{
    return false;
}
 
bool ScHeaderControl::ResizeAllowed() const
{
    return true;
}
 
void ScHeaderControl::SelectWindow()
{
}
 
void ScHeaderControl::DrawInvert( tools::Long /* nDragPos */ )
{
}
 
OUString ScHeaderControl::GetDragHelp( tools::Long /* nVal */ )
{
    return OUString();
}
 
void ScHeaderControl::SetMarking( bool /* bSet */ )
{
}
 
void ScHeaderControl::GetMarkRange(SCCOLROW& rStart, SCCOLROW& rEnd) const
{
    rStart = nMarkStart;
    rEnd = nMarkEnd;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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