/* -*- 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 <memory>
#include <scitems.hxx>
#include <editeng/eeitem.hxx>

#include <svtools/colorcfg.hxx>
#include <editeng/colritem.hxx>
#include <editeng/editview.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/brushitem.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/printer.hxx>
#include <vcl/cursor.hxx>
#include <vcl/settings.hxx>
#include <o3tl/unit_conversion.hxx>
#include <osl/diagnose.h>
#include <tools/UnitConversion.hxx>
#include <tools/weakbase.hxx>

#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <sfx2/lokhelper.hxx>
#include <sfx2/lokcomponenthelpers.hxx>
#include <officecfg/Office/Calc.hxx>

#include <svx/svdview.hxx>
#include <svx/svdpagv.hxx>
#include <svx/sdrpagewindow.hxx>
#include <svx/sdr/contact/objectcontactofpageview.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <tabvwsh.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/sysdata.hxx>

#include <gridwin.hxx>
#include <viewdata.hxx>
#include <output.hxx>
#include <document.hxx>
#include <attrib.hxx>
#include <patattr.hxx>
#include <dbdata.hxx>
#include <notemark.hxx>
#include <dbfunc.hxx>
#include <scmod.hxx>
#include <inputhdl.hxx>
#include <rfindlst.hxx>
#include <hiranges.hxx>
#include <pagedata.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <docsh.hxx>
#include <cbutton.hxx>
#include <invmerge.hxx>
#include <editutil.hxx>
#include <inputopt.hxx>
#include <fillinfo.hxx>
#include <dpcontrol.hxx>
#include <queryparam.hxx>
#include <queryentry.hxx>
#include <markdata.hxx>
#include <sc.hrc>
#include <vcl/virdev.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <drwlayer.hxx>

static void lcl_LimitRect( tools::Rectangle& rRect, const tools::Rectangle& rVisible )
{
    if ( rRect.Top()    < rVisible.Top()-1 )    rRect.SetTop( rVisible.Top()-1 );
    if ( rRect.Bottom() > rVisible.Bottom()+1 ) rRect.SetBottom( rVisible.Bottom()+1 );

    // The header row must be drawn also when the inner rectangle is not visible,
    // that is why there is no return value anymore.
    // When it is far away, then lcl_DrawOneFrame is not even called.
}

static void lcl_DrawOneFrame( vcl::RenderContext* pDev, const tools::Rectangle& rInnerPixel,
                        const OUString& rTitle, const Color& rColor, bool bTextBelow,
                        double nPPTX, double nPPTY, const Fraction& rZoomY,
                        const ScDocument& rDoc, ScViewData& rButtonViewData, bool bLayoutRTL )
{
    // rButtonViewData is only used to set the button size,

    tools::Rectangle aInner = rInnerPixel;
    if ( bLayoutRTL )
    {
        aInner.SetLeft( rInnerPixel.Right() );
        aInner.SetRight( rInnerPixel.Left() );
    }

    tools::Rectangle aVisible( Point(0,0), pDev->GetOutputSizePixel() );
    lcl_LimitRect( aInner, aVisible );

    tools::Rectangle aOuter = aInner;
    tools::Long nHor = static_cast<tools::Long>( SC_SCENARIO_HSPACE * nPPTX );
    tools::Long nVer = static_cast<tools::Long>( SC_SCENARIO_VSPACE * nPPTY );
    aOuter.AdjustLeft( -nHor );
    aOuter.AdjustRight(nHor );
    aOuter.AdjustTop( -nVer );
    aOuter.AdjustBottom(nVer );

    //  use ScPatternAttr::GetFont only for font size
    vcl::Font aAttrFont;
    rDoc.getCellAttributeHelper().getDefaultCellAttribute().fillFontOnly(aAttrFont, pDev, &rZoomY);

    //  everything else from application font
    vcl::Font aAppFont = pDev->GetSettings().GetStyleSettings().GetAppFont();
    aAppFont.SetFontSize( aAttrFont.GetFontSize() );

    aAppFont.SetAlignment( ALIGN_TOP );
    pDev->SetFont( aAppFont );

    Size aTextSize( pDev->GetTextWidth( rTitle ), pDev->GetTextHeight() );

    if ( bTextBelow )
        aOuter.AdjustBottom(aTextSize.Height() );
    else
        aOuter.AdjustTop( -(aTextSize.Height()) );

    pDev->SetLineColor();
    pDev->SetFillColor( rColor );
    //  left, top, right, bottom
    pDev->DrawRect( tools::Rectangle( aOuter.Left(),  aOuter.Top(),    aInner.Left(),  aOuter.Bottom() ) );
    pDev->DrawRect( tools::Rectangle( aOuter.Left(),  aOuter.Top(),    aOuter.Right(), aInner.Top()    ) );
    pDev->DrawRect( tools::Rectangle( aInner.Right(), aOuter.Top(),    aOuter.Right(), aOuter.Bottom() ) );
    pDev->DrawRect( tools::Rectangle( aOuter.Left(),  aInner.Bottom(), aOuter.Right(), aOuter.Bottom() ) );

    tools::Long nButtonY = bTextBelow ? aInner.Bottom() : aOuter.Top();

    ScDDComboBoxButton aComboButton(pDev);
    aComboButton.SetOptSizePixel();
    tools::Long nBWidth  = tools::Long(aComboButton.GetSizePixel().Width() * rZoomY);
    tools::Long nBHeight = nVer + aTextSize.Height() + 1;
    Size aButSize( nBWidth, nBHeight );
    tools::Long nButtonPos = bLayoutRTL ? aOuter.Left() : aOuter.Right()-nBWidth+1;
    aComboButton.Draw( Point(nButtonPos, nButtonY), aButSize );
    rButtonViewData.SetScenButSize( aButSize );

    tools::Long nTextStart = bLayoutRTL ? aInner.Right() - aTextSize.Width() + 1 : aInner.Left();

    bool bWasClip = false;
    vcl::Region aOldClip;
    bool bClip = ( aTextSize.Width() > aOuter.Right() - nBWidth - aInner.Left() );
    if ( bClip )
    {
        if (pDev->IsClipRegion())
        {
            bWasClip = true;
            aOldClip = pDev->GetActiveClipRegion();
        }
        tools::Long nClipStartX = bLayoutRTL ? aOuter.Left() + nBWidth : aInner.Left();
        tools::Long nClipEndX = bLayoutRTL ? aInner.Right() : aOuter.Right() - nBWidth;
        pDev->SetClipRegion( vcl::Region(tools::Rectangle( nClipStartX, nButtonY + nVer/2,
                            nClipEndX, nButtonY + nVer/2 + aTextSize.Height())) );
    }

    pDev->DrawText( Point( nTextStart, nButtonY + nVer/2 ), rTitle );

    if ( bClip )
    {
        if ( bWasClip )
            pDev->SetClipRegion(aOldClip);
        else
            pDev->SetClipRegion();
    }

    pDev->SetFillColor();
    pDev->SetLineColor( COL_BLACK );
    pDev->DrawRect( aInner );
    pDev->DrawRect( aOuter );
}

static void lcl_DrawScenarioFrames( OutputDevice* pDev, ScViewData& rViewData, ScSplitPos eWhich,
                            SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2 )
{
    ScDocument& rDoc = rViewData.GetDocument();
    SCTAB nTab = rViewData.CurrentTabForData();
    SCTAB nTabCount = rDoc.GetTableCount();
    if ( nTab+1 >= nTabCount || !rDoc.IsScenario(nTab+1) || rDoc.IsScenario(nTab) )
        return;

    if ( nX1 > 0 ) --nX1;
    if ( nY1>=2 ) nY1 -= 2;             // Hack: Header row affects two cells
    else if ( nY1 > 0 ) --nY1;
    if ( nX2 < rDoc.MaxCol() ) ++nX2;
    if ( nY2 < rDoc.MaxRow()-1 ) nY2 += 2;     // Hack: Header row affects two cells
    else if ( nY2 < rDoc.MaxRow() ) ++nY2;
    ScRange aViewRange( nX1,nY1,nTab, nX2,nY2,nTab );

    //! cache the ranges in table!!!!

    ScMarkData aMarks(rDoc.GetSheetLimits());
    for (SCTAB i=nTab+1; i<nTabCount && rDoc.IsScenario(i); i++)
        rDoc.MarkScenario( i, nTab, aMarks, false, ScScenarioFlags::ShowFrame );
    ScRangeListRef xRanges = new ScRangeList;
    aMarks.FillRangeListWithMarks( xRanges.get(), false );

    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;

    for (size_t j = 0, n = xRanges->size(); j < n; ++j)
    {
        ScRange aRange = (*xRanges)[j];
        // Always extend scenario frame to merged cells where no new non-covered cells
        // are framed
        rDoc.ExtendTotalMerge( aRange );

        //! -> Extend repaint when merging !!!

        if ( aRange.Intersects( aViewRange ) )          //! Space for Text/Button?
        {
            Point aStartPos = rViewData.GetScrPos(
                                aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true );
            Point aEndPos = rViewData.GetScrPos(
                                aRange.aEnd.Col()+1, aRange.aEnd.Row()+1, eWhich, true );
            //  on the grid:
            aStartPos.AdjustX( -nLayoutSign );
            aStartPos.AdjustY( -1 );
            aEndPos.AdjustX( -nLayoutSign );
            aEndPos.AdjustY( -1 );

            bool bTextBelow = ( aRange.aStart.Row() == 0 );

            OUString aCurrent;
            Color aColor( COL_LIGHTGRAY );
            for (SCTAB nAct=nTab+1; nAct<nTabCount && rDoc.IsScenario(nAct); nAct++)
                if ( rDoc.IsActiveScenario(nAct) && rDoc.HasScenarioRange(nAct,aRange) )
                {
                    OUString aDummyComment;
                    ScScenarioFlags nDummyFlags;
                    rDoc.GetName( nAct, aCurrent );
                    rDoc.GetScenarioData( nAct, aDummyComment, aColor, nDummyFlags );
                }

            if (aCurrent.isEmpty())
                aCurrent = ScResId( STR_EMPTYDATA );

            //! Own text "(None)" instead of "(Empty)" ???

            lcl_DrawOneFrame( pDev, tools::Rectangle( aStartPos, aEndPos ),
                                aCurrent, aColor, bTextBelow,
                                rViewData.GetPPTX(), rViewData.GetPPTY(), rViewData.GetZoomY(),
                                rDoc, rViewData, bLayoutRTL );
        }
    }
}

static void lcl_DrawHighlight( ScOutputData& rOutputData, const ScViewData& rViewData,
                        const std::vector<ScHighlightEntry>& rHighlightRanges )
{
    SCTAB nTab = rViewData.CurrentTabForData();
    for ( const auto& rHighlightRange : rHighlightRanges)
    {
        ScRange aRange = rHighlightRange.aRef;
        if ( nTab >= aRange.aStart.Tab() && nTab <= aRange.aEnd.Tab() )
        {
            rOutputData.DrawRefMark(
                                aRange.aStart.Col(), aRange.aStart.Row(),
                                aRange.aEnd.Col(), aRange.aEnd.Row(),
                                rHighlightRange.aColor, false );
        }
    }
}

void ScGridWindow::DoInvertRect( const tools::Rectangle& rPixel )
{
    if ( rPixel == aInvertRect )
        aInvertRect = tools::Rectangle();      // Cancel
    else
    {
        OSL_ENSURE( aInvertRect.IsEmpty(), "DoInvertRect no pairs" );

        aInvertRect = rPixel;           // Mark new rectangle
    }

    UpdateHeaderOverlay();      // uses aInvertRect
}

void ScGridWindow::PrePaint(vcl::RenderContext& /*rRenderContext*/)
{
    // forward PrePaint to DrawingLayer
    ScTabViewShell* pTabViewShell = mrViewData.GetViewShell();

    if(pTabViewShell)
    {
        SdrView* pDrawView = pTabViewShell->GetScDrawView();

        if (pDrawView)
        {
            pDrawView->PrePaint();
        }
    }
}

bool ScGridWindow::NeedLOKCursorInvalidation(const tools::Rectangle& rCursorRect,
        const Fraction aScaleX, const Fraction aScaleY)
{
    // Don't see the need for a map as there will be only a few zoom levels
    // and as of now X and Y zooms in online are the same.
    for (auto& rEntry : maLOKLastCursor)
    {
        if (aScaleX == rEntry.aScaleX && aScaleY == rEntry.aScaleY)
        {
            if (rCursorRect == rEntry.aRect)
                return false; // No change

            // Update and allow invalidate.
            rEntry.aRect = rCursorRect;
            return true;
        }
    }

    maLOKLastCursor.push_back(LOKCursorEntry{aScaleX, aScaleY, rCursorRect});
    return true;
}

void ScGridWindow::InvalidateLOKViewCursor(const tools::Rectangle& rCursorRect,
        const Fraction aScaleX, const Fraction aScaleY)
{
    if (!NeedLOKCursorInvalidation(rCursorRect, aScaleX, aScaleY))
        return;

    ScTabViewShell* pThisViewShell = mrViewData.GetViewShell();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();

    while (pViewShell)
    {
        if (pViewShell != pThisViewShell && pViewShell->GetDocId() == pThisViewShell->GetDocId())
        {
            ScTabViewShell* pOtherViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
            if (pOtherViewShell)
            {
                ScViewData& rOtherViewData = pOtherViewShell->GetViewData();
                Fraction aZoomX = rOtherViewData.GetZoomX();
                Fraction aZoomY = rOtherViewData.GetZoomY();
                if (aZoomX == aScaleX && aZoomY == aScaleY)
                {
                    SfxLokHelper::notifyOtherView(*pThisViewShell, pOtherViewShell,
                            LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", rCursorRect.toString());
                }
            }
        }

        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}

void ScGridWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect )
{
    ScDocument& rDoc = mrViewData.GetDocument();
    if ( rDoc.IsInInterpreter() )
    {
        // Via Reschedule, interpreted cells do not trigger Invalidate again,
        // otherwise for instance an error box would never appear (bug 36381).
        // Later, through bNeedsRepaint everything is painted again.
        if ( bNeedsRepaint )
        {
            //! Merge Rectangle?
            aRepaintPixel = tools::Rectangle();            // multiple -> paint all
        }
        else
        {
            bNeedsRepaint = true;
            aRepaintPixel = LogicToPixel(rRect);    // only affected ranges
        }
        return;
    }

    // #i117893# If GetSizePixel needs to call the resize handler, the resulting nested Paint call
    // (possibly for a larger rectangle) has to be allowed. Call GetSizePixel before setting bIsInPaint.
    GetSizePixel();

    if (bIsInPaint)
        return;

    bIsInPaint = true;

    tools::Rectangle aPixRect = LogicToPixel( rRect );

    SCCOL nX1 = mrViewData.GetPosX(eHWhich);
    SCROW nY1 = mrViewData.GetPosY(eVWhich);

    SCTAB nTab = mrViewData.CurrentTabForData();

    double nPPTX = mrViewData.GetPPTX();
    double nPPTY = mrViewData.GetPPTY();

    tools::Rectangle aMirroredPixel = aPixRect;
    if ( rDoc.IsLayoutRTL( nTab ) )
    {
        //  mirror and swap
        tools::Long nWidth = GetSizePixel().Width();
        aMirroredPixel.SetLeft( nWidth - 1 - aPixRect.Right() );
        aMirroredPixel.SetRight( nWidth - 1 - aPixRect.Left() );
    }

    tools::Long nScrX = ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX );
    while ( nScrX <= aMirroredPixel.Left() && nX1 < rDoc.MaxCol() )
    {
        ++nX1;
        nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX );
    }
    SCCOL nX2 = nX1;
    while ( nScrX <= aMirroredPixel.Right() && nX2 < rDoc.MaxCol() )
    {
        ++nX2;
        nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX2, nTab ), nPPTX );
    }

    tools::Long nScrY = 0;
    ScViewData::AddPixelsWhile( nScrY, aPixRect.Top(), nY1, rDoc.MaxRow(), nPPTY, &rDoc, nTab);
    SCROW nY2 = nY1;
    if (nScrY <= aPixRect.Bottom() && nY2 < rDoc.MaxRow())
    {
        ++nY2;
        ScViewData::AddPixelsWhile( nScrY, aPixRect.Bottom(), nY2, rDoc.MaxRow(), nPPTY, &rDoc, nTab);
    }

    Draw( nX1,nY1,nX2,nY2, ScUpdateMode::Marks ); // don't continue with painting

    bIsInPaint = false;
}

void ScGridWindow::Resize()
{
    // tdf#159348 update highlight overlay when window is resized
    if (officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::get())
        UpdateHighlightOverlay();
}

void ScGridWindow::Draw( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, ScUpdateMode eMode )
{
    ScDocument& rDoc = mrViewData.GetDocument();

    // let's ignore the normal Draw() attempts when doing the tiled rendering,
    // all the rendering should go through PaintTile() in that case.
    // TODO revisit if we can actually turn this into an assert(), and clean
    // up the callers
    if (comphelper::LibreOfficeKit::isActive())
        return;

    bool bTextWysiwyg = ScModule::get()->GetInputOptions().GetTextWysiwyg();

    if (mrViewData.IsMinimized())
        return;

    PutInOrder( nX1, nX2 );
    PutInOrder( nY1, nY2 );

    OSL_ENSURE( rDoc.ValidCol(nX2) && rDoc.ValidRow(nY2), "GridWin Draw area too big" );

    UpdateVisibleRange();

    if (nX2 < maVisibleRange.mnCol1 || nY2 < maVisibleRange.mnRow1)
        return;
    // invisible
    if (nX1 < maVisibleRange.mnCol1)
        nX1 = maVisibleRange.mnCol1;
    if (nY1 < maVisibleRange.mnRow1)
        nY1 = maVisibleRange.mnRow1;

    if (nX1 > maVisibleRange.mnCol2 || nY1 > maVisibleRange.mnRow2)
        return;

    if (nX2 > maVisibleRange.mnCol2)
        nX2 = maVisibleRange.mnCol2;
    if (nY2 > maVisibleRange.mnRow2)
        nY2 = maVisibleRange.mnRow2;

    if ( eMode != ScUpdateMode::Marks && nX2 < maVisibleRange.mnCol2)
        nX2 = maVisibleRange.mnCol2;  // to continue painting

    // point of no return

    ++nPaintCount; // mark that painting is in progress

    SCTAB nTab = mrViewData.CurrentTabForData();
    rDoc.ExtendHidden( nX1, nY1, nX2, nY2, nTab );

    Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich );
    tools::Long nMirrorWidth = GetSizePixel().Width();
    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );
    if ( bLayoutRTL )
    {
        tools::Long nEndPixel = mrViewData.GetScrPos( nX2+1, maVisibleRange.mnRow1, eWhich ).X();
        nMirrorWidth = aScrPos.X() - nEndPixel;
        aScrPos.setX( nEndPixel + 1 );
    }

    tools::Long nScrX = aScrPos.X();
    tools::Long nScrY = aScrPos.Y();

    SCCOL nCurX = mrViewData.GetCurX();
    SCROW nCurY = mrViewData.GetCurY();
    SCCOL nCurEndX = nCurX;
    SCROW nCurEndY = nCurY;
    rDoc.ExtendMerge( nCurX, nCurY, nCurEndX, nCurEndY, nTab );
    bool bCurVis = nCursorHideCount==0 &&
                    ( nCurEndX+1 >= nX1 && nCurX <= nX2+1 && nCurEndY+1 >= nY1 && nCurY <= nY2+1 );

    //  AutoFill Handles
    if ( !bCurVis && nCursorHideCount==0 && bAutoMarkVisible && aAutoMarkPos.Tab() == nTab &&
            ( aAutoMarkPos.Col() != nCurX || aAutoMarkPos.Row() != nCurY ) )
    {
        SCCOL nHdlX = aAutoMarkPos.Col();
        SCROW nHdlY = aAutoMarkPos.Row();
        rDoc.ExtendMerge( nHdlX, nHdlY, nHdlX, nHdlY, nTab );
        // left and top is unaffected

        //! Paint AutoFill handles alone (without Cursor) ???
    }

    double nPPTX = mrViewData.GetPPTX();
    double nPPTY = mrViewData.GetPPTY();

    const ScViewOptions& rOpts = mrViewData.GetOptions();

    // data block

    ScTableInfo aTabInfo(nY1, nY2, true);
    rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nTab,
                   nPPTX, nPPTY, false, rOpts.GetOption(sc::ViewOption::FORMULAS),
                   &mrViewData.GetMarkData() );

    Fraction aZoomX = mrViewData.GetZoomX();
    Fraction aZoomY = mrViewData.GetZoomY();
    ScOutputData aOutputData( GetOutDev(), OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab,
                               nScrX, nScrY, nX1, nY1, nX2, nY2, nPPTX, nPPTY,
                               &aZoomX, &aZoomY );

    aOutputData.SetMirrorWidth( nMirrorWidth ); // needed for RTL
    aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get());

    ScopedVclPtr< VirtualDevice > xFmtVirtDev;
    bool bLogicText = bTextWysiwyg; // call DrawStrings in logic MapMode?

    if ( bTextWysiwyg )
    {
        //  use printer for text formatting

        OutputDevice* pFmtDev = rDoc.GetPrinter();
        pFmtDev->SetMapMode( mrViewData.GetLogicMode(eWhich) );
        aOutputData.SetFmtDevice( pFmtDev );
    }
    else if ( aZoomX != aZoomY && mrViewData.IsOle() )
    {
        //  #i45033# For OLE inplace editing with different zoom factors,
        //  use a virtual device with 1/100th mm as text formatting reference

        xFmtVirtDev.disposeAndReset( VclPtr<VirtualDevice>::Create() );
        xFmtVirtDev->SetMapMode(MapMode(MapUnit::Map100thMM));
        aOutputData.SetFmtDevice( xFmtVirtDev.get() );

        bLogicText = true; // use logic MapMode
    }

    DrawContent(*GetOutDev(), aTabInfo, aOutputData, bLogicText);

    // If something was inverted during the Paint (selection changed from Basic Macro)
    // then this is now mixed up and has to be repainted
    OSL_ENSURE(nPaintCount, "Wrong nPaintCount");
    --nPaintCount;
    if (!nPaintCount)
        CheckNeedsRepaint();

    // Flag drawn formula cells "unchanged".
    rDoc.ResetChanged(ScRange(nX1, nY1, nTab, nX2, nY2, nTab));
    rDoc.PrepareFormulaCalc();
}

namespace {

class SuppressEditViewMessagesGuard
{
public:
    SuppressEditViewMessagesGuard(EditView& rEditView) :
        mrEditView(rEditView),
        mbOrigSuppressFlag(rEditView.IsSuppressLOKMessages())
    {
        if (!mbOrigSuppressFlag)
            mrEditView.SuppressLOKMessages(true);
    }

    ~SuppressEditViewMessagesGuard()
    {
        if (mrEditView.IsSuppressLOKMessages() != mbOrigSuppressFlag)
            mrEditView.SuppressLOKMessages(mbOrigSuppressFlag);
    }

private:
    EditView& mrEditView;
    const bool mbOrigSuppressFlag;
};

}

/**
 * Used to store the necessary information about the (combined-)tile
 * area relevant to coordinate transformations in RTL mode.
 */
class ScLokRTLContext
{
public:
    ScLokRTLContext(const ScOutputData& rOutputData, const tools::Long nTileDeviceOriginPixelX):
        mrOutputData(rOutputData),
        mnTileDevOriginX(nTileDeviceOriginPixelX)
    {}

    /**
     * Converts from document x pixel position to the
     * corresponding pixel position w.r.t the tile device origin.
     */
    tools::Long docToTilePos(tools::Long nPosX) const
    {
        tools::Long nMirrorX = (-2 * mnTileDevOriginX) + mrOutputData.GetScrW();
        return nMirrorX - 1 - nPosX;
    }


private:
    const ScOutputData& mrOutputData;
    const tools::Long mnTileDevOriginX;
};

namespace
{
tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect)
{
    return {-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()};
}

tools::Long GetSide(const tools::Rectangle& rRect, int i)
{
    static const decltype(&tools::Rectangle::Left) GetSides[4] = {
        &tools::Rectangle::Left, &tools::Rectangle::Top,
        &tools::Rectangle::Right, &tools::Rectangle::Bottom
    };
    return (rRect.*GetSides[i])();
}

Fraction GetZoom(const ScViewData& rViewData, int i)
{
    static const decltype(&ScViewData::GetZoomX) GetZooms[4] = {
        &ScViewData::GetZoomX, &ScViewData::GetZoomY,
        &ScViewData::GetZoomX, &ScViewData::GetZoomY
    };
    return (rViewData.*GetZooms[i])();
}
}

void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData,
        bool bLogicText)
{
    ScModule* pScMod = ScModule::get();
    ScDocument& rDoc = mrViewData.GetDocument();
    const ScViewOptions& rOpts = mrViewData.GetOptions();
    bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive();
    bool bNoBackgroundAndGrid = bIsTiledRendering
                                && comphelper::LibreOfficeKit::isCompatFlagSet(
                                       comphelper::LibreOfficeKit::Compat::scNoGridBackground);

    SCTAB nTab = aOutputData.mnTab;
    SCCOL nX1 = aOutputData.mnX1;
    SCROW nY1 = aOutputData.mnY1;
    SCCOL nX2 = aOutputData.mnX2;
    SCROW nY2 = aOutputData.mnY2;
    tools::Long nScrX = aOutputData.mnScrX;
    tools::Long nScrY = aOutputData.mnScrY;

    const svtools::ColorConfig& rColorCfg = pScMod->GetColorConfig();
    Color aGridColor( rColorCfg.GetColorValue( svtools::CALCGRID ).nColor );
    if ( aGridColor == COL_TRANSPARENT )
    {
        //  use view options' grid color only if color config has "automatic" color
        aGridColor = rOpts.GetGridColor();
    }

    ScTabViewShell* pCurTabViewShell = mrViewData.GetViewShell();

    aOutputData.SetSyntaxMode       ( mrViewData.IsSyntaxMode() );
    aOutputData.SetGridColor        ( aGridColor );
    aOutputData.SetShowNullValues   ( rOpts.GetOption(sc::ViewOption::NULLVALS) );
    aOutputData.SetShowFormulas     ( rOpts.GetOption(sc::ViewOption::FORMULAS) );
    aOutputData.SetShowSpellErrors  ( pCurTabViewShell && pCurTabViewShell->IsAutoSpell() );
    aOutputData.SetMarkClipped      ( pScMod->GetColorConfig().GetColorValue(svtools::CALCTEXTOVERFLOW).bIsVisible );

    aOutputData.SetUseStyleColor( true );       // always set in table view

    aOutputData.SetViewShell(pCurTabViewShell);

    bool bGrid = rOpts.GetOption(sc::ViewOption::GRID) && mrViewData.GetShowGrid();
    bool bGridFirst = !rOpts.GetOption(sc::ViewOption::GRID_ONTOP);

    bool bPage = rOpts.GetOption(sc::ViewOption::PAGEBREAKS) && !bIsTiledRendering;

    bool bPageMode = mrViewData.IsPagebreakMode();
    if (bPageMode)                                      // after FindChanged
    {
        // SetPagebreakMode also initializes bPrinted Flags
        aOutputData.SetPagebreakMode( mrViewData.GetView()->GetPageBreakData() );
    }

    EditView*   pEditView = nullptr;
    bool        bEditMode = mrViewData.HasEditView(eWhich);
    if ( bEditMode && mrViewData.GetRefTabNo() == nTab )
    {
        SCCOL nEditCol;
        SCROW nEditRow;
        mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow );
        SCCOL nEditEndCol = mrViewData.GetEditEndCol();
        SCROW nEditEndRow = mrViewData.GetEditEndRow();


        if (officecfg::Office::Calc::Content::Display::EditCellBackgroundHighlighting::get())
        {
            Color aDocColor = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
            if (!getViewData().GetMarkData().IsMarked() && mrViewData.GetEditHighlight())
            {
                Color aHighlightColor = pScMod->GetColorConfig().GetColorValue(svtools::CALCCELLFOCUS).nColor;
                aHighlightColor.Merge(aDocColor, 100);
                aDocColor = aHighlightColor;
            }

            Color aBackColor = rDoc.GetPattern(nEditCol, nEditRow, getViewData().CurrentTabForData())->GetItem(ATTR_BACKGROUND).GetColor();
            if (!aBackColor.IsTransparent())
                aDocColor = aBackColor;

            pEditView->SetBackgroundColor(aDocColor);
        }

        if ( nEditEndCol >= nX1 && nEditCol <= nX2 && nEditEndRow >= nY1 && nEditRow <= nY2 )
            aOutputData.SetEditCell( nEditCol, nEditRow );
        else
            bEditMode = false;
    }

    const MapMode aOriginalMode = rDevice.GetMapMode();

    // define drawing layer map mode and paint rectangle
    MapMode aDrawMode = GetDrawMapMode();
    if (bIsTiledRendering)
    {
        // FIXME this shouldn't be necessary once we change the entire Calc to
        // work in the logic coordinates (ideally 100ths of mm - so that it is
        // the same as editeng and drawinglayer), and get rid of all the
        // SetMapMode's and other unnecessary fun we have with pixels
        // See also ScGridWindow::GetDrawMapMode() for the rest of this hack
        aDrawMode.SetOrigin(PixelToLogic(Point(nScrX, nScrY), aDrawMode));
    }
    tools::Rectangle aDrawingRectLogic;
    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );
    bool bLokRTL = bLayoutRTL && bIsTiledRendering;
    std::unique_ptr<ScLokRTLContext> pLokRTLCtxt(
        bLokRTL ?
            new ScLokRTLContext(aOutputData, o3tl::convert(aOriginalMode.GetOrigin().X(), o3tl::Length::twip, o3tl::Length::px)) :
            nullptr);

    {
        // get drawing pixel rect
        tools::Rectangle aDrawingRectPixel(
            bLokRTL ? Point(-(nScrX + aOutputData.GetScrW()), nScrY) : Point(nScrX, nScrY),
            Size(aOutputData.GetScrW(), aOutputData.GetScrH()));

        // correct for border (left/right)
        if(rDoc.MaxCol() == nX2 && !bLokRTL)
        {
            if(bLayoutRTL)
            {
                aDrawingRectPixel.SetLeft( 0 );
            }
            else
            {
                aDrawingRectPixel.SetRight( GetOutputSizePixel().getWidth() );
            }
        }

        // correct for border (bottom)
        if(rDoc.MaxRow() == nY2)
        {
            aDrawingRectPixel.SetBottom( GetOutputSizePixel().getHeight() );
        }

        // get logic positions
        aDrawingRectLogic = PixelToLogic(aDrawingRectPixel, aDrawMode);
    }

    bool bInPlaceEditing = bEditMode && (mrViewData.GetRefTabNo() == mrViewData.CurrentTabForData());
    vcl::Cursor* pInPlaceCrsr = nullptr;
    bool bInPlaceVisCursor = false;
    if (bInPlaceEditing)
    {
        // toggle the cursor off if it's on to ensure the cursor invert
        // background logic remains valid after the background is cleared on
        // the next cursor flash
        pInPlaceCrsr = pEditView->GetCursor();
        bInPlaceVisCursor = pInPlaceCrsr && pInPlaceCrsr->IsVisible();
        if (bInPlaceVisCursor)
            pInPlaceCrsr->Hide();
    }

    OutputDevice* pContentDev = &rDevice;   // device for document content, used by overlay manager
    SdrPaintWindow* pTargetPaintWindow = nullptr; // #i74769# work with SdrPaintWindow directly

    // init redraw
    if (pCurTabViewShell)
    {
        MapMode aCurrentMapMode(pContentDev->GetMapMode());
        pContentDev->SetMapMode(aDrawMode);
        SdrView* pDrawView = pCurTabViewShell->GetScDrawView();

        if(pDrawView)
        {
            // #i74769# Use new BeginDrawLayers() interface
            vcl::Region aDrawingRegion(aDrawingRectLogic);
            pTargetPaintWindow = pDrawView->BeginDrawLayers(pContentDev, aDrawingRegion);
            OSL_ENSURE(pTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)");

            if (!bIsTiledRendering)
            {
                // #i74769# get target device from SdrPaintWindow, this may be the prerender
                // device now, too.
                pContentDev = &(pTargetPaintWindow->GetTargetOutputDevice());
                aOutputData.SetContentDevice(pContentDev);
            }
        }

        pContentDev->SetMapMode(aCurrentMapMode);
    }

    // app-background / document edge (area) (Pixel)
    if ( !bIsTiledRendering && ( nX2 == rDoc.MaxCol() || nY2 == rDoc.MaxRow() ) )
    {
        // save MapMode and set to pixel
        MapMode aCurrentMapMode(pContentDev->GetMapMode());
        pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));

        tools::Rectangle aPixRect( Point(), GetOutputSizePixel() );
        pContentDev->SetFillColor( rColorCfg.GetColorValue(svtools::APPBACKGROUND).nColor );
        pContentDev->SetLineColor();
        if ( nX2==rDoc.MaxCol() )
        {
            tools::Rectangle aDrawRect( aPixRect );
            if ( bLayoutRTL )
                aDrawRect.SetRight( nScrX - 1 );
            else
                aDrawRect.SetLeft( nScrX + aOutputData.GetScrW() );
            if (aDrawRect.Right() >= aDrawRect.Left())
                pContentDev->DrawRect( aDrawRect );
        }
        if ( nY2==rDoc.MaxRow() )
        {
            tools::Rectangle aDrawRect( aPixRect );
            aDrawRect.SetTop( nScrY + aOutputData.GetScrH() );
            if ( nX2==rDoc.MaxCol() )
            {
                // no double painting of the corner
                if ( bLayoutRTL )
                    aDrawRect.SetLeft( nScrX );
                else
                    aDrawRect.SetRight( nScrX + aOutputData.GetScrW() - 1 );
            }
            if (aDrawRect.Bottom() >= aDrawRect.Top())
                pContentDev->DrawRect( aDrawRect );
        }

        // restore MapMode
        pContentDev->SetMapMode(aCurrentMapMode);
    }

    if ( rDoc.HasBackgroundDraw( nTab, aDrawingRectLogic ) )
    {
        pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));
        aOutputData.DrawClear();

            // drawing background

        pContentDev->SetMapMode(aDrawMode);
        DrawRedraw( aOutputData, SC_LAYER_BACK );
    }
    else
        aOutputData.SetSolidBackground(!bNoBackgroundAndGrid);

    aOutputData.DrawDocumentBackground();

    if (bGridFirst && (bGrid || bPage))
    {
        // Draw lines in background color cover over lok client grid lines in merged cell areas if bNoBackgroundAndGrid is set.
        if (bNoBackgroundAndGrid)
            aOutputData.DrawGrid(*pContentDev, false /* bGrid */, false /* bPage */, true /* bMergeCover */);
        else
            aOutputData.DrawGrid(*pContentDev, bGrid, bPage);
    }

    aOutputData.DrawBackground(*pContentDev);

    if (!bGridFirst && (bGrid || bPage) && !bNoBackgroundAndGrid)
        aOutputData.DrawGrid(*pContentDev, bGrid, bPage);

    pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));

    //tdf#128258 - draw a dotted line before hidden columns/rows
    DrawHiddenIndicator(nX1,nY1,nX2,nY2, *pContentDev);

    if ( bPageMode )
    {
        // DrawPagePreview draws complete lines/page numbers, must always be clipped
        if ( aOutputData.SetChangedClip() )
        {
            DrawPagePreview(nX1,nY1,nX2,nY2, *pContentDev);
            pContentDev->SetClipRegion();
        }
    }

    aOutputData.DrawShadow();
    aOutputData.DrawFrame(*pContentDev);

    aOutputData.DrawSparklines(*pContentDev);

    if ( rOpts.GetOption(sc::ViewOption::FORMULAS_MARKS) )
        aOutputData.DrawFormulaMarks(*pContentDev);

    if ( !bLogicText )
        aOutputData.DrawStrings();     // in pixel MapMode

    // edit cells and printer-metrics text must be before the buttons
    // (DataPilot buttons contain labels in UI font)

    pContentDev->SetMapMode(mrViewData.GetLogicMode(eWhich));
    if ( bLogicText )
        aOutputData.DrawStrings(true);      // in logic MapMode if bLogicText is set
    aOutputData.DrawEdit(true);

    // the buttons are painted in absolute coordinates
    if (bIsTiledRendering)
    {
        // Tiled offset nScrX, nScrY
        MapMode aMap( MapUnit::MapPixel );
        Point aOrigin(o3tl::convert(aOriginalMode.GetOrigin(), o3tl::Length::twip, o3tl::Length::px));
        aOrigin.Move(nScrX, nScrY);
        aMap.SetOrigin(aOrigin);
        pContentDev->SetMapMode(aMap);
    }
    else
        pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));

    // Autofilter- and Pivot-Buttons
    DrawButtons(nX1, nX2, rTableInfo, pContentDev, pLokRTLCtxt.get());          // Pixel

    // Show Note Mark
    if (rOpts.GetOption(sc::ViewOption::NOTES))
        aOutputData.DrawNoteMarks(*pContentDev);

    pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));

    aOutputData.DrawClipMarks();

    // In any case, Scenario / ChangeTracking must happen after DrawGrid, also for !bGridFirst

    //! test if ChangeTrack display is active
    //! Disable scenario frame via view option?

    SCTAB nTabCount = rDoc.GetTableCount();
    const std::vector<ScHighlightEntry> &rHigh = mrViewData.GetView()->GetHighlightRanges();
    bool bHasScenario = ( nTab+1<nTabCount && rDoc.IsScenario(nTab+1) && !rDoc.IsScenario(nTab) );
    bool bHasChange = ( rDoc.GetChangeTrack() != nullptr );

    if ( bHasChange || bHasScenario || !rHigh.empty() )
    {
        //! Merge SetChangedClip() with DrawMarks() ?? (different MapMode!)

        if ( bHasChange )
            aOutputData.DrawChangeTrack();

        if ( bHasScenario )
            lcl_DrawScenarioFrames( pContentDev, mrViewData, eWhich, nX1,nY1,nX2,nY2 );

        lcl_DrawHighlight( aOutputData, mrViewData, rHigh );
    }

        // Drawing foreground

    pContentDev->SetMapMode(aDrawMode);

    // Bitmaps and buttons are in absolute pixel coordinates.
    const MapMode aOrig = pContentDev->GetMapMode();
    if (bIsTiledRendering)
    {
        Point aOrigin(o3tl::convert(aOriginalMode.GetOrigin(), o3tl::Length::twip, o3tl::Length::px));
        tools::Long nXOffset = bLayoutRTL ? -aOrigin.getX() + aOutputData.GetScrW()
                                          : aOrigin.getX();
        Size aPixelOffset(nXOffset, aOrigin.getY());
        pContentDev->SetPixelOffset(aPixelOffset);
        comphelper::LibreOfficeKit::setLocalRendering();
    }

    DrawRedraw( aOutputData, SC_LAYER_FRONT );
    DrawRedraw( aOutputData, SC_LAYER_INTERN );
    DrawSdrGrid( aDrawingRectLogic, pContentDev );

    if (bIsTiledRendering)
    {
        pContentDev->SetPixelOffset(Size());
        pContentDev->SetMapMode(aOrig);
    }

    pContentDev->SetMapMode(MapMode(MapUnit::MapPixel));

    if ( mrViewData.IsRefMode() && nTab >= mrViewData.GetRefStartZ() && nTab <= mrViewData.GetRefEndZ() )
    {
        Color aRefColor( rColorCfg.GetColorValue(svtools::CALCREFERENCE).nColor );
        aOutputData.DrawRefMark( mrViewData.GetRefStartX(), mrViewData.GetRefStartY(),
                                mrViewData.GetRefEndX(), mrViewData.GetRefEndY(),
                                aRefColor, false );
    }

        // range finder

    ScInputHandler* pHdl = pScMod->GetInputHdl( mrViewData.GetViewShell() );
    if (pHdl)
    {
        ScDocShell* pDocSh = mrViewData.GetDocShell();
        ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList();
        if ( pRangeFinder && !pRangeFinder->IsHidden() &&
                pRangeFinder->GetDocName() == pDocSh->GetTitle() )
        {
            sal_uInt16 nCount = static_cast<sal_uInt16>(pRangeFinder->Count());
            for (sal_uInt16 i=0; i<nCount; i++)
            {
                ScRangeFindData& rData = pRangeFinder->GetObject(i);

                ScRange aRef = rData.aRef;
                aRef.PutInOrder();
                if ( aRef.aStart.Tab() >= nTab && aRef.aEnd.Tab() <= nTab )
                    aOutputData.DrawRefMark( aRef.aStart.Col(), aRef.aStart.Row(),
                                            aRef.aEnd.Col(), aRef.aEnd.Row(),
                                            rData.nColor,
                                            true );
            }
        }
    }

    // end redraw
    if (pCurTabViewShell)
    {
        MapMode aCurrentMapMode(pContentDev->GetMapMode());
        pContentDev->SetMapMode(aDrawMode);

        if (bIsTiledRendering)
        {
            Point aOrigin = aOriginalMode.GetOrigin();
            if (bLayoutRTL)
                aOrigin.setX(-aOrigin.getX()
                             + o3tl::toTwips(aOutputData.mnScrX + aOutputData.GetScrW(), o3tl::Length::px));
            else
                aOrigin.AdjustX(o3tl::toTwips(aOutputData.mnScrX, o3tl::Length::px));

            aOrigin.AdjustY(o3tl::toTwips(aOutputData.mnScrY, o3tl::Length::px));
            aOrigin = o3tl::convert(aOrigin, o3tl::Length::twip, o3tl::Length::mm100);
            // keep into account the zoom factor
            aOrigin = aOrigin.scale(
                aDrawMode.GetScaleX().GetDenominator(), aDrawMode.GetScaleX().GetNumerator(),
                aDrawMode.GetScaleY().GetDenominator(), aDrawMode.GetScaleY().GetNumerator());

            MapMode aNew = rDevice.GetMapMode();
            aNew.SetOrigin(aOrigin);
            rDevice.SetMapMode(aNew);
        }

        SdrView* pDrawView = pCurTabViewShell->GetScDrawView();

        if(pDrawView)
        {
            // #i74769# work with SdrPaintWindow directly
            pDrawView->EndDrawLayers(*pTargetPaintWindow, true);
        }

        pContentDev->SetMapMode(aCurrentMapMode);
    }

    // in place editing - lok case
    if (bIsTiledRendering)
    {
        ScTabViewShell* pThisViewShell = mrViewData.GetViewShell();
        ViewShellList aCurrentDocViewList = LOKEditViewHistory::GetSortedViewsForDoc(pThisViewShell->GetDocId());
        tools::Rectangle aTileRectPx(Point(nScrX, nScrY), Size(aOutputData.GetScrW(), aOutputData.GetScrH()));

        for (SfxViewShell* pVS: aCurrentDocViewList)
        {
            auto pTabViewShell = dynamic_cast<ScTabViewShell*>(pVS);
            if (!pTabViewShell)
                continue;

            ScViewData& rOtherViewData = pTabViewShell->GetViewData();
            ScSplitPos eOtherWhich = rOtherViewData.GetEditActivePart();

            bool bOtherEditMode = rOtherViewData.HasEditView(eOtherWhich);
            SCCOL nCol1 = rOtherViewData.GetEditStartCol();
            SCROW nRow1 = rOtherViewData.GetEditStartRow();
            SCCOL nCol2 = rOtherViewData.GetEditEndCol();
            SCROW nRow2 = rOtherViewData.GetEditEndRow();

            if (!(bOtherEditMode
                  && ( nCol2 >= nX1 && nCol1 <= nX2 && nRow2 >= nY1 && nRow1 <= nY2 )
                  && rOtherViewData.GetRefTabNo() == nTab))
                continue; // only views where in place editing is occurring need to be rendered

            EditView* pOtherEditView = rOtherViewData.GetEditView(eOtherWhich);
            if (!pOtherEditView)
                continue;

            rDevice.SetLineColor();
            // Theme colors
            const ScPatternAttr* pPattern = rDoc.GetPattern( nCol1, nRow1, nTab );
            Color aCellColor = pPattern->GetItem(ATTR_BACKGROUND).GetColor();
            if (aCellColor.IsTransparent())
            {
                if (ScTabViewShell* pCurrentViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()))
                {
                    const ScViewRenderingOptions& rViewRenderingOptions = pCurrentViewShell->GetViewRenderingData();
                    aCellColor = rViewRenderingOptions.GetDocColor();
                }
            }
            rDevice.SetFillColor(aCellColor);
            pOtherEditView->SetBackgroundColor(aCellColor);

            // edit rectangle / background
            Point aStart = mrViewData.GetScrPos( nCol1, nRow1, eOtherWhich );
            Point aEnd = mrViewData.GetScrPos( nCol2+1, nRow2+1, eOtherWhich );
            tools::Rectangle aEditRectPx(aStart, aEnd);
            if (bLokRTL)
            {
                // Transform the cell range X coordinates such that the edit cell area is
                // horizontally mirrored w.r.t the (combined-)tile.
                aStart.setX(pLokRTLCtxt->docToTilePos(aStart.X()));
                aEnd.setX(pLokRTLCtxt->docToTilePos(aEnd.X()));
            }

            // don't overwrite grid
            tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
            aEnd.AdjustX( -(2 * nLayoutSign) );
            aEnd.AdjustY( -2 );

            tools::Rectangle aBackground(aStart, aEnd);
            if (bLokRTL)
                aBackground.Normalize();
            tools::Rectangle aBGAbs(aBackground);

            // Need to draw the background in absolute coords.
            Point aOriginTw = aOriginalMode.GetOrigin();
            Point aOriginAbsTw = aOriginTw + o3tl::toTwips(aTileRectPx.GetPos(), o3tl::Length::px);
            Point aOriginAbsPx = o3tl::convert(aOriginAbsTw, o3tl::Length::twip, o3tl::Length::px);
            aBackground += aOriginAbsPx;
            rDevice.SetMapMode(aDrawMode);

            // keep into account the zoom factor
            Point aNewOrigin(o3tl::convert(aOriginAbsTw, o3tl::Length::twip, o3tl::Length::mm100));
            aNewOrigin = aNewOrigin.scale(
                aDrawMode.GetScaleX().GetDenominator(), aDrawMode.GetScaleX().GetNumerator(),
                aDrawMode.GetScaleY().GetDenominator(), aDrawMode.GetScaleY().GetNumerator());

            MapMode aNewMM = rDevice.GetMapMode();
            aNewMM.SetOrigin(aNewOrigin);
            rDevice.SetMapMode(aNewMM);

            // paint the background
            rDevice.DrawRect(rDevice.PixelToLogic(aBackground));

            OutputDevice& rOtherWin = pOtherEditView->GetOutputDevice();
            const MapMode aOrigMapMode = rOtherWin.GetMapMode();
            const o3tl::Length aOrigOutputAreaUnit = MapToO3tlLength(aOrigMapMode.GetMapUnit());

            // paint text
            const tools::Rectangle aOrigOutputArea(pOtherEditView->GetOutputArea()); // Not in pixels.

            tools::Rectangle aNewOutputArea;
            // compute output area for view with a different zoom level wrt the view used for painting
            if (!(mrViewData.GetZoomX() == rOtherViewData.GetZoomX() &&
                  mrViewData.GetZoomY() == rOtherViewData.GetZoomY()))
            {
                Point aOtherStart = rOtherViewData.GetScrPos( nCol1, nRow1, eOtherWhich );
                Point aOtherEnd = rOtherViewData.GetScrPos( nCol2+1, nRow2+1, eOtherWhich );
                tools::Rectangle aOtherEditRect(
                    o3tl::convert(tools::Rectangle(aOtherStart, aOtherEnd), o3tl::Length::px,
                                  aOrigOutputAreaUnit));

                tools::Long sides[4];
                for (auto i: {0, 1, 2, 3})
                {
                    const Fraction zoomThis = ::GetZoom(mrViewData, i);
                    const Fraction zoomOther = ::GetZoom(rOtherViewData, i);

                    const auto unscaledOtherEditRectSide
                        = o3tl::convert(GetSide(aOtherEditRect, i),
                                        zoomOther.GetDenominator(), zoomOther.GetNumerator());

                    const auto scaledAdd
                        = o3tl::convert(GetSide(aOrigOutputArea, i) - unscaledOtherEditRectSide,
                                        zoomThis.GetNumerator(), zoomThis.GetDenominator());

                    sides[i] = GetSide(aEditRectPx, i)
                               + o3tl::convert(scaledAdd, aOrigOutputAreaUnit, o3tl::Length::px);
                }

                aNewOutputArea = tools::Rectangle(sides[0], sides[1], sides[2], sides[3]);
                aNewOutputArea += aOriginAbsPx;
            }
            // compute output area for RTL case
            if (bLokRTL)
            {
                if (aNewOutputArea.IsEmpty())
                {
                    // same zoom level as view used for painting
                    aNewOutputArea = rDevice.LogicToPixel(aOrigOutputArea);
                }
                // a small workaround for getting text position matching cursor position horizontally.
                const tools::Long nCursorGapPx = 2;
                // Transform the cell range X coordinates such that the edit cell area is
                // horizontally mirrored w.r.t the (combined-)tile.
                aNewOutputArea = tools::Rectangle(
                    pLokRTLCtxt->docToTilePos(aNewOutputArea.Left() - aOriginAbsPx.X()) + aOriginAbsPx.X(),
                    aNewOutputArea.Top(),
                    pLokRTLCtxt->docToTilePos(aNewOutputArea.Right() - aOriginAbsPx.X()) + aOriginAbsPx.X() + nCursorGapPx,
                    aNewOutputArea.Bottom());
                aNewOutputArea.Normalize();
            }

            if (aNewOutputArea.IsEmpty())
            {
                // same zoom level and not RTL: no need to change the output area before painting
                pOtherEditView->DrawText_ToEditView(rDevice.PixelToLogic(aTileRectPx), &rDevice);
            }
            else
            {
                // EditView has an 'output area' which is used to clip the 'paint area' we provide below.
                // So they need to be in the same coordinates/units. This is tied to the mapmode of the gridwin
                // attached to the EditView, so we have to change its mapmode too (temporarily). We save the
                // original mapmode and 'output area' and roll them back when we finish painting to rDevice.
                rOtherWin.SetMapMode(rDevice.GetMapMode());

                // Avoid sending wrong cursor/selection messages by the 'other' view, as the output-area is going
                // to be tweaked temporarily to match the current view's zoom.
                SuppressEditViewMessagesGuard aGuard(*pOtherEditView);

                pOtherEditView->SetOutputArea(rDevice.PixelToLogic(aNewOutputArea));
                pOtherEditView->DrawText_ToEditView(rDevice.PixelToLogic(aTileRectPx), &rDevice);

                // EditView will do the cursor notifications correctly if we're in
                // print-twips messaging mode.
                if (pTabViewShell == pThisViewShell
                    && !comphelper::LibreOfficeKit::isCompatFlagSet(
                        comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs))
                {
                    // Now we need to get relative cursor position within the editview.
                    // This is for sending the pixel-aligned twips position of the cursor to the specific views with
                    // the same given zoom level.
                    tools::Rectangle aCursorRect = pEditView->GetEditCursor();
                    Point aCursPos = o3tl::toTwips(aCursorRect.TopLeft(), o3tl::Length::mm100);

                    const MapMode& rDevMM = rDevice.GetMapMode();
                    MapMode aMM(MapUnit::MapTwip);
                    aMM.SetScaleX(rDevMM.GetScaleX());
                    aMM.SetScaleY(rDevMM.GetScaleY());

                    aBGAbs.AdjustLeft(1);
                    aBGAbs.AdjustTop(1);
                    aCursorRect = GetOutDev()->PixelToLogic(aBGAbs, aMM);
                    aCursorRect.setWidth(0);
                    aCursorRect.Move(aCursPos.getX(), 0);
                    // Sends view cursor position to views of all matching zooms if needed (avoids duplicates).
                    InvalidateLOKViewCursor(aCursorRect, aMM.GetScaleX(), aMM.GetScaleY());
                }

                // Rollback the mapmode and 'output area'.
                rOtherWin.SetMapMode(aOrigMapMode);
                pOtherEditView->SetOutputArea(aOrigOutputArea);
            }
            rDevice.SetMapMode(MapMode(MapUnit::MapPixel));
        }
    }

    // In-place editing - when the user is typing, we need to paint the text
    // using the editeng.
    // It's being done after EndDrawLayers() to get it outside the overlay
    // buffer and on top of everything.
    if (bInPlaceEditing && !bIsTiledRendering && bInPlaceVisCursor)
        pInPlaceCrsr->Show();

    if (mrViewData.HasEditView(eWhich))
    {
        // set MapMode for text edit
        rDevice.SetMapMode(mrViewData.GetLogicMode());
    }
    else
        rDevice.SetMapMode(aDrawMode);

    if (bPage && bInitialPageBreaks)
        SetupInitialPageBreaks(rDoc, nTab);
}


void ScGridWindow::SetupInitialPageBreaks(const ScDocument& rDoc, SCTAB nTab)
{
    // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page breaks
    // is enabled, breaks should be visible. If the document is opened the first
    // time, the breaks are not calculated yet, so for this initialization
    // a timer will be triggered here.
    std::set<SCCOL> aColBreaks;
    std::set<SCROW> aRowBreaks;
    rDoc.GetAllColBreaks(aColBreaks, nTab, true, false);
    rDoc.GetAllRowBreaks(aRowBreaks, nTab, true, false);
    if (aColBreaks.empty() || aRowBreaks.empty())
    {
        maShowPageBreaksTimer.SetPriority(TaskPriority::DEFAULT_IDLE);
        maShowPageBreaksTimer.Start();
    }
    bInitialPageBreaks = false;
}

namespace
{
    template<typename IndexType>
    void lcl_getBoundingRowColumnforTile(ScViewData& rViewData,
            tools::Long nTileStartPosPx, tools::Long nTileEndPosPx,
            sal_Int32& nTopLeftTileOffset, sal_Int32& nTopLeftTileOrigin,
            sal_Int32& nTopLeftTileIndex, sal_Int32& nBottomRightTileIndex)
    {
        const bool bColumnHeader = std::is_same<IndexType, SCCOL>::value;

        SCTAB nTab = rViewData.CurrentTabForData();

        IndexType nStartIndex = -1;
        IndexType nEndIndex = -1;
        tools::Long nStartPosPx = 0;
        tools::Long nEndPosPx = 0;

        ScPositionHelper& rPositionHelper =
                bColumnHeader ? rViewData.GetLOKWidthHelper() : rViewData.GetLOKHeightHelper();
        const auto& rStartNearest = rPositionHelper.getNearestByPosition(nTileStartPosPx);
        const auto& rEndNearest = rPositionHelper.getNearestByPosition(nTileEndPosPx);

        ScBoundsProvider aBoundsProvider(rViewData, nTab, bColumnHeader);
        aBoundsProvider.Compute(rStartNearest, rEndNearest, nTileStartPosPx, nTileEndPosPx);
        aBoundsProvider.GetStartIndexAndPosition(nStartIndex, nStartPosPx); ++nStartIndex;
        aBoundsProvider.GetEndIndexAndPosition(nEndIndex, nEndPosPx);

        nTopLeftTileOffset = nTileStartPosPx - nStartPosPx;
        nTopLeftTileOrigin = nStartPosPx;
        nTopLeftTileIndex = nStartIndex;
        nBottomRightTileIndex = nEndIndex;
    }

    void lcl_RTLAdjustTileColOffset(ScViewData& rViewData, sal_Int32& nTileColOffset,
        tools::Long nTileEndPx, sal_Int32 nEndCol, SCTAB nTab,
        const ScDocument& rDoc, double fPPTX)
    {
        auto GetColWidthPx = [&rDoc, nTab, fPPTX](SCCOL nCol) {
            const sal_uInt16 nSize = rDoc.GetColWidth(nCol, nTab);
            const tools::Long nSizePx = ScViewData::ToPixel(nSize, fPPTX);
            return nSizePx;
        };

        ScPositionHelper rHelper = rViewData.GetLOKWidthHelper();
        tools::Long nEndColPos = rHelper.computePosition(nEndCol, GetColWidthPx);

        nTileColOffset += (nEndColPos - nTileEndPx - nTileColOffset);
    }

    class ScLOKProxyObjectContact final : public sdr::contact::ObjectContactOfPageView
    {
    private:
        tools::WeakReference<ScDrawView> m_xScDrawView;

    public:
        explicit ScLOKProxyObjectContact(
            ScDrawView* pDrawView,
            SdrPageWindow& rPageWindow,
            const char* pDebugName) :
            ObjectContactOfPageView(rPageWindow, pDebugName),
            m_xScDrawView(pDrawView)
        {
        }

        virtual bool supportsGridOffsets() const override { return true; }

        virtual void calculateGridOffsetForViewObjectContact(
            basegfx::B2DVector& rTarget,
            const sdr::contact::ViewObjectContact& rClient) const override
        {
            ScDrawView* pScDrawView = m_xScDrawView.get();
            if (!pScDrawView)
                return;

            SdrPageView* pPageView(pScDrawView->GetSdrPageView());
            if (!pPageView)
                return;

            SdrPageWindow* pSdrPageWindow = nullptr;
            if (pPageView->PageWindowCount() > 0)
                pSdrPageWindow = pPageView->GetPageWindow(0);
            if (!pSdrPageWindow)
                return;

            sdr::contact::ObjectContact& rObjContact(pSdrPageWindow->GetObjectContact());

            SdrObject* pTargetSdrObject(rClient.GetViewContact().TryToGetSdrObject());
            if (pTargetSdrObject)
                rTarget = pTargetSdrObject->GetViewContact().GetViewObjectContact(rObjContact).getGridOffset();
        }
    };

    class ScLOKDrawView : public FmFormView
    {
    public:
        ScLOKDrawView(OutputDevice* pOut, ScViewData& rData) :
            FmFormView(*rData.GetDocument().GetDrawLayer(), pOut),
            mpScDrawView(rData.GetScDrawView())
        {
        }

        virtual sdr::contact::ObjectContact* createViewSpecificObjectContact(
                SdrPageWindow& rPageWindow, const char* pDebugName) const override
        {
            if (!mpScDrawView)
                return SdrView::createViewSpecificObjectContact(rPageWindow, pDebugName);

            return new ScLOKProxyObjectContact(mpScDrawView, rPageWindow, pDebugName);
        }

    private:
        ScDrawView* mpScDrawView;
    };
} // anonymous namespace

void ScGridWindow::resetCachedViewGridOffsets() const
{
    if (mpLOKDrawView)
        if (SdrPageView* pPageView = mpLOKDrawView->GetSdrPageView())
            pPageView->resetGridOffsetsOfAllPageWindows();
}

void ScGridWindow::PaintTile( VirtualDevice& rDevice,
                              int nOutputWidth, int nOutputHeight,
                              int nTilePosX, int nTilePosY,
                              tools::Long nTileWidth, tools::Long nTileHeight,
                              SCCOL nTiledRenderingAreaEndCol, SCROW nTiledRenderingAreaEndRow )
{
    Fraction origZoomX = mrViewData.GetZoomX();
    Fraction origZoomY = mrViewData.GetZoomY();

    // Output size is in pixels while tile position and size are in logical units (twips).

    // Assumption: always paint the whole sheet i.e. "visible" range is always
    // from (0,0) to last data position.

    // Tile geometry is independent of the zoom level, but the output size is
    // dependent of the zoom level.  Determine the correct zoom level before
    // we start.

    // FIXME the painting works using a mixture of drawing with coordinates in
    // pixels and in logic coordinates; it should be cleaned up to use logic
    // coords only, and avoid all the SetMapMode()'s.
    // Similarly to Writer, we should set the mapmode once on the rDevice, and
    // not care about any zoom settings.

    Fraction aFracX(o3tl::convert(nOutputWidth, o3tl::Length::px, o3tl::Length::twip), nTileWidth);
    Fraction aFracY(o3tl::convert(nOutputHeight, o3tl::Length::px, o3tl::Length::twip), nTileHeight);

    const bool bChangeZoom = (aFracX !=  origZoomX || aFracY != origZoomY);

    // page break zoom, and aLogicMode in ScViewData
    // FIXME: there are issues when SetZoom is called conditionally.
    mrViewData.SetZoom(aFracX, aFracY, true);
    if (bChangeZoom)
    {
        if (ScDrawView* pDrawView = mrViewData.GetScDrawView())
            pDrawView->resetGridOffsetsForAllSdrPageViews();
    }

    const double fTilePosXPixel = static_cast<double>(nTilePosX) * nOutputWidth / nTileWidth;
    const double fTilePosYPixel = static_cast<double>(nTilePosY) * nOutputHeight / nTileHeight;
    const double fTileBottomPixel = static_cast<double>(nTilePosY + nTileHeight) * nOutputHeight / nTileHeight;
    const double fTileRightPixel = static_cast<double>(nTilePosX + nTileWidth) * nOutputWidth / nTileWidth;

    SCTAB nTab = mrViewData.CurrentTabForData();
    ScDocument& rDoc = mrViewData.GetDocument();

    const double fPPTX = mrViewData.GetPPTX();
    const double fPPTY = mrViewData.GetPPTY();

    // find approximate col/row offsets of nearby.
    sal_Int32 nTopLeftTileRowOffset = 0;
    sal_Int32 nTopLeftTileColOffset = 0;
    sal_Int32 nTopLeftTileRowOrigin = 0;
    sal_Int32 nTopLeftTileColOrigin = 0;

    sal_Int32 nTopLeftTileRow = 0;
    sal_Int32 nTopLeftTileCol = 0;
    sal_Int32 nBottomRightTileRow = 0;
    sal_Int32 nBottomRightTileCol = 0;

    lcl_getBoundingRowColumnforTile<SCROW>(mrViewData,
            fTilePosYPixel, fTileBottomPixel,
            nTopLeftTileRowOffset, nTopLeftTileRowOrigin,
            nTopLeftTileRow, nBottomRightTileRow);

    lcl_getBoundingRowColumnforTile<SCCOL>(mrViewData,
            fTilePosXPixel, fTileRightPixel,
            nTopLeftTileColOffset, nTopLeftTileColOrigin,
            nTopLeftTileCol, nBottomRightTileCol);

    // Enlarge
    nBottomRightTileCol++;
    nBottomRightTileRow++;

    if (nTopLeftTileCol > rDoc.MaxCol())
        nTopLeftTileCol = rDoc.MaxCol();

    if (nBottomRightTileCol > rDoc.MaxCol())
        nBottomRightTileCol = rDoc.MaxCol();

    if (nTopLeftTileRow > MAXTILEDROW)
        nTopLeftTileRow = MAXTILEDROW;

    if (nBottomRightTileRow > MAXTILEDROW)
        nBottomRightTileRow = MAXTILEDROW;

    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );

    if (bLayoutRTL)
    {
        lcl_RTLAdjustTileColOffset(mrViewData, nTopLeftTileColOffset,
            fTileRightPixel, nBottomRightTileCol, nTab,
            rDoc, fPPTX);
    }

    // size of the document including drawings, charts, etc.
    SCCOL nEndCol = nTiledRenderingAreaEndCol;
    SCROW nEndRow = nTiledRenderingAreaEndRow;

    if (nEndCol < nBottomRightTileCol)
        nEndCol = nBottomRightTileCol;

    if (nEndRow < nBottomRightTileRow)
        nEndRow = nBottomRightTileRow;

    nTopLeftTileCol = std::max<sal_Int32>(nTopLeftTileCol, 0);
    nTopLeftTileRow = std::max<sal_Int32>(nTopLeftTileRow, 0);
    nTopLeftTileColOrigin = o3tl::convert(nTopLeftTileColOrigin, o3tl::Length::px, o3tl::Length::twip);
    nTopLeftTileRowOrigin = o3tl::convert(nTopLeftTileRowOrigin, o3tl::Length::px, o3tl::Length::twip);

    // Checkout -> 'rDoc.ExtendMerge' ... if we miss merged cells.

    // Origin must be the offset of the first col and row
    // containing our top-left pixel.
    const MapMode aOriginalMode = rDevice.GetMapMode();
    MapMode aAbsMode = aOriginalMode;
    const Point aOrigin(-nTopLeftTileColOrigin, -nTopLeftTileRowOrigin);
    aAbsMode.SetOrigin(aOrigin);
    rDevice.SetMapMode(aAbsMode);

    ScTableInfo aTabInfo(nTopLeftTileRow, nBottomRightTileRow, false);
    rDoc.FillInfo(aTabInfo, nTopLeftTileCol, nTopLeftTileRow,
                   nBottomRightTileCol, nBottomRightTileRow,
                   nTab, fPPTX, fPPTY, false, false);

// FIXME: is this called some
//        Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich );

    ScOutputData aOutputData(&rDevice, OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab,
                             -nTopLeftTileColOffset,
                             -nTopLeftTileRowOffset,
                             nTopLeftTileCol, nTopLeftTileRow,
                             nBottomRightTileCol, nBottomRightTileRow,
                             fPPTX, fPPTY, nullptr, nullptr);

    // setup the SdrPage so that drawinglayer works correctly
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    if (pModel)
    {
        bool bPrintTwipsMsgs = comphelper::LibreOfficeKit::isCompatFlagSet(
                comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
        if (!mpLOKDrawView)
        {
            mpLOKDrawView.reset(bPrintTwipsMsgs ?
                new ScLOKDrawView(
                    &rDevice,
                    mrViewData) :
                new FmFormView(
                    *pModel,
                    &rDevice));
        }

        mpLOKDrawView->SetNegativeX(bLayoutRTL);
        mpLOKDrawView->ShowSdrPage(mpLOKDrawView->GetModel().GetPage(nTab));
        aOutputData.SetDrawView(mpLOKDrawView.get());
        aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get());
    }

    // draw the content
    DrawContent(rDevice, aTabInfo, aOutputData, true);
    rDevice.SetMapMode(aOriginalMode);

    // Paint the chart(s) in edit mode.
    LokChartHelper::PaintAllChartsOnTile(rDevice, nOutputWidth, nOutputHeight,
        nTilePosX, nTilePosY, nTileWidth, nTileHeight, bLayoutRTL);

    rDevice.SetMapMode(aOriginalMode);

    // Flag drawn formula cells "unchanged".
    rDoc.ResetChanged(ScRange(nTopLeftTileCol, nTopLeftTileRow, nTab, nBottomRightTileCol, nBottomRightTileRow, nTab));
    rDoc.PrepareFormulaCalc();

    mrViewData.SetZoom(origZoomX, origZoomY, true);
    if (bChangeZoom)
    {
        if (ScDrawView* pDrawView = mrViewData.GetScDrawView())
            pDrawView->resetGridOffsetsForAllSdrPageViews();
    }

    if (bLayoutRTL)
    {
        Bitmap aCellBMP = rDevice.GetBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight));
        aCellBMP.Mirror(BmpMirrorFlags::Horizontal);
        rDevice.DrawBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight), aCellBMP);
    }
}

void ScGridWindow::LogicInvalidatePart(const tools::Rectangle* pRectangle, int nPart)
{
    tools::Rectangle aRectangle;
    tools::Rectangle* pResultRectangle;
    if (!pRectangle)
        pResultRectangle = nullptr;
    else
    {
        aRectangle = *pRectangle;
        // When dragging shapes the map mode is disabled.
        if (IsMapModeEnabled())
        {
            if (GetMapMode().GetMapUnit() == MapUnit::Map100thMM)
            {
                aRectangle = o3tl::convert(aRectangle, o3tl::Length::mm100, o3tl::Length::twip);
            }
        }
        else
            aRectangle = PixelToLogic(aRectangle, MapMode(MapUnit::MapTwip));
        pResultRectangle = &aRectangle;
    }

    // Trim invalidation rectangle overlapping negative X region in RTL mode.
    if (pResultRectangle && pResultRectangle->Left() < 0
        && mrViewData.GetDocument().IsLayoutRTL(mrViewData.CurrentTabForData()))
    {
        pResultRectangle->SetLeft(0);
        if (pResultRectangle->Right() < 0)
            pResultRectangle->SetRight(0);
    }

    ScTabViewShell* pViewShell = mrViewData.GetViewShell();
    SfxLokHelper::notifyInvalidation(pViewShell, nPart, pResultRectangle);
}

void ScGridWindow::LogicInvalidate(const tools::Rectangle* pRectangle)
{
    ScTabViewShell* pViewShell = mrViewData.GetViewShell();
    LogicInvalidatePart(pRectangle, pViewShell->getPart());
}

bool ScGridWindow::InvalidateByForeignEditView(EditView* pEditView)
{
    if (!pEditView)
        return false;

    auto* pGridWin = dynamic_cast<ScGridWindow*>(pEditView->GetWindow());
    if (!pGridWin)
        return false;

    const ScViewData& rViewData = pGridWin->getViewData();
    tools::Long nRefTabNo = rViewData.GetRefTabNo();
    tools::Long nX = rViewData.GetCurXForTab(nRefTabNo);
    tools::Long nY = rViewData.GetCurYForTab(nRefTabNo);

    tools::Rectangle aPixRect = getViewData().GetEditArea(eWhich, nX, nY, this, nullptr, true);
    tools::Rectangle aLogicRect = PixelToLogic(aPixRect, getViewData().GetLogicMode());
    Invalidate(pEditView->IsNegativeX() ? lcl_negateRectX(aLogicRect) : aLogicRect);

    return true;
}

void ScGridWindow::SetCellSelectionPixel(int nType, int nPixelX, int nPixelY)
{
    ScTabView* pTabView = mrViewData.GetView();
    ScTabViewShell* pViewShell = mrViewData.GetViewShell();
    ScInputHandler* pInputHandler = ScModule::get()->GetInputHdl(pViewShell);

    if (pInputHandler && pInputHandler->IsInputMode())
    {
        // we need to switch off the editeng
        ScTabView::UpdateInputLine();
        pViewShell->UpdateInputHandler();
    }

    if (nType == LOK_SETTEXTSELECTION_RESET)
    {
        pTabView->DoneBlockMode();
        return;
    }

    // obtain the current selection
    ScRangeList aRangeList = mrViewData.GetMarkData().GetMarkedRanges();

    SCCOL nCol1, nCol2;
    SCROW nRow1, nRow2;
    SCTAB nTab1, nTab2;

    bool bWasEmpty = false;
    if (aRangeList.empty())
    {
        nCol1 = nCol2 = mrViewData.GetCurX();
        nRow1 = nRow2 = mrViewData.GetCurY();
        bWasEmpty = true;
    }
    else
        aRangeList.Combine().GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);

    // convert the coordinates to column/row
    SCCOL nNewPosX;
    SCROW nNewPosY;
    SCTAB nTab = mrViewData.CurrentTabForData();
    mrViewData.GetPosFromPixel(nPixelX, nPixelY, eWhich, nNewPosX, nNewPosY);

    // change the selection
    switch (nType)
    {
        case LOK_SETTEXTSELECTION_START:
            if (nNewPosX != nCol1 || nNewPosY != nRow1 || bWasEmpty)
            {
                pTabView->SetCursor(nNewPosX, nNewPosY);
                pTabView->DoneBlockMode();
                pTabView->InitBlockMode(nNewPosX, nNewPosY, nTab, true);
                pTabView->MarkCursor(nCol2, nRow2, nTab);
            }
            break;
        case LOK_SETTEXTSELECTION_END:
            if (nNewPosX != nCol2 || nNewPosY != nRow2 || bWasEmpty)
            {
                pTabView->SetCursor(nCol1, nRow1);
                pTabView->DoneBlockMode();
                pTabView->InitBlockMode(nCol1, nRow1, nTab, true);
                pTabView->MarkCursor(nNewPosX, nNewPosY, nTab);
            }
            break;
        default:
            assert(false);
            break;
    }
}

void ScGridWindow::CheckNeedsRepaint()
{
    //  called at the end of painting, and from timer after background text width calculation

    if (!bNeedsRepaint)
        return;

    bNeedsRepaint = false;
    if (aRepaintPixel.IsEmpty())
        Invalidate();
    else
        Invalidate(PixelToLogic(aRepaintPixel));
    aRepaintPixel = tools::Rectangle();

    // selection function in status bar might also be invalid
    SfxBindings& rBindings = mrViewData.GetBindings();
    rBindings.Invalidate( SID_STATUS_SUM );
    rBindings.Invalidate( SID_ATTR_SIZE );
    rBindings.Invalidate( SID_TABLE_CELL );
}

void ScGridWindow::DrawHiddenIndicator( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext)
{
    ScDocument& rDoc = mrViewData.GetDocument();
    SCTAB nTab = mrViewData.CurrentTabForData();
    const svtools::ColorConfig& rColorCfg = ScModule::get()->GetColorConfig();
    const svtools::ColorConfigValue aColorValue = rColorCfg.GetColorValue(svtools::CALCHIDDENROWCOL);
    if (aColorValue.bIsVisible) {
        rRenderContext.SetLineColor(aColorValue.nColor);
        LineInfo aLineInfo(LineStyle::Dash, 2);
        aLineInfo.SetDashCount(0);
        aLineInfo.SetDotCount(1);
        aLineInfo.SetDistance(15);
        // round caps except when running VCL_PLUGIN=gen due to a performance issue
        // https://bugs.documentfoundation.org/show_bug.cgi?id=128258#c14
        if (mrViewData.GetActiveWin()->GetSystemData()->toolkit != SystemEnvData::Toolkit::Gen)
            aLineInfo.SetLineCap(css::drawing::LineCap_ROUND);
        aLineInfo.SetDotLen(1);
        for (int i=nX1; i<nX2; i++) {
            if (rDoc.ColHidden(i,nTab) && (i<rDoc.MaxCol() ? !rDoc.ColHidden(i+1,nTab) : true)) {
                Point aStart = mrViewData.GetScrPos(i, nY1, eWhich, true );
                Point aEnd = mrViewData.GetScrPos(i, nY2, eWhich, true );
                rRenderContext.DrawLine(aStart,aEnd,aLineInfo);
            }
        }
        for (int i=nY1; i<nY2; i++) {
            if (rDoc.RowHidden(i,nTab) && (i<rDoc.MaxRow() ? !rDoc.RowHidden(i+1,nTab) : true)) {
                Point aStart = mrViewData.GetScrPos(nX1, i, eWhich, true );
                Point aEnd = mrViewData.GetScrPos(nX2, i, eWhich, true );
                rRenderContext.DrawLine(aStart,aEnd,aLineInfo);
            }
        }
    } //visible
}

void ScGridWindow::DrawPagePreview( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext)
{
    ScPageBreakData* pPageData = mrViewData.GetView()->GetPageBreakData();
    if (!pPageData)
        return;

    ScDocument& rDoc = mrViewData.GetDocument();
    SCTAB nTab = mrViewData.CurrentTabForData();
    Size aWinSize = GetOutputSizePixel();
    const svtools::ColorConfig& rColorCfg = ScModule::get()->GetColorConfig();
    Color aManual( rColorCfg.GetColorValue(svtools::CALCPAGEBREAKMANUAL).nColor );
    Color aAutomatic( rColorCfg.GetColorValue(svtools::CALCPAGEBREAK).nColor );

    OUString aPageStr = ScResId( STR_PGNUM );
    if ( nPageScript == SvtScriptType::NONE )
    {
        //  get script type of translated "Page" string only once
        nPageScript = rDoc.GetStringScriptType( aPageStr );
        if (nPageScript == SvtScriptType::NONE)
            nPageScript = ScGlobal::GetDefaultScriptType();
    }

    vcl::Font aFont;
    std::unique_ptr<ScEditEngineDefaulter> pEditEng;
    const ScPatternAttr& rDefPattern(rDoc.getCellAttributeHelper().getDefaultCellAttribute());
    if ( nPageScript == SvtScriptType::LATIN )
    {
        //  use single font and call DrawText directly
        rDefPattern.fillFontOnly(aFont);
        aFont.SetColor(COL_LIGHTGRAY);
        //  font size is set as needed
    }
    else
    {
        //  use EditEngine to draw mixed-script string
        pEditEng.reset(new ScEditEngineDefaulter( EditEngine::CreatePool().get(), true ));
        pEditEng->SetRefMapMode(rRenderContext.GetMapMode());
        SfxItemSet aEditDefaults( pEditEng->GetEmptyItemSet() );
        rDefPattern.FillEditItemSet( &aEditDefaults );
        aEditDefaults.Put( SvxColorItem( COL_LIGHTGRAY, EE_CHAR_COLOR ) );
        pEditEng->SetDefaults( std::move(aEditDefaults) );
    }

    sal_uInt16 nCount = sal::static_int_cast<sal_uInt16>( pPageData->GetCount() );
    for (sal_uInt16 nPos=0; nPos<nCount; nPos++)
    {
        ScPrintRangeData& rData = pPageData->GetData(nPos);
        ScRange aRange = rData.GetPrintRange();
        if ( aRange.aStart.Col() <= nX2+1  && aRange.aEnd.Col()+1 >= nX1 &&
             aRange.aStart.Row() <= nY2+1 && aRange.aEnd.Row()+1 >= nY1 )
        {
            // 3 pixel frame around the print area
            //  (middle pixel on the grid lines)

            rRenderContext.SetLineColor();
            if (rData.IsAutomatic())
                rRenderContext.SetFillColor( aAutomatic );
            else
                rRenderContext.SetFillColor( aManual );

            Point aStart = mrViewData.GetScrPos(
                                aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true );
            Point aEnd = mrViewData.GetScrPos(
                                aRange.aEnd.Col() + 1, aRange.aEnd.Row() + 1, eWhich, true );
            aStart.AdjustX( -2 );
            aStart.AdjustY( -2 );

            // Prevent overflows:
            if ( aStart.X() < -10 ) aStart.setX( -10 );
            if ( aStart.Y() < -10 ) aStart.setY( -10 );
            if ( aEnd.X() > aWinSize.Width() + 10 )
                aEnd.setX( aWinSize.Width() + 10 );
            if ( aEnd.Y() > aWinSize.Height() + 10 )
                aEnd.setY( aWinSize.Height() + 10 );

            rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aEnd.X(),aStart.Y()+2) ) );
            rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aStart.X()+2,aEnd.Y()) ) );
            rRenderContext.DrawRect( tools::Rectangle( Point(aStart.X(),aEnd.Y()-2), aEnd ) );
            rRenderContext.DrawRect( tools::Rectangle( Point(aEnd.X()-2,aStart.Y()), aEnd ) );

            // Page breaks
            //! Display differently (dashed ????)

            size_t nColBreaks = rData.GetPagesX();
            const SCCOL* pColEnd = rData.GetPageEndX();
            size_t nColPos;
            for (nColPos=0; nColPos+1<nColBreaks; nColPos++)
            {
                SCCOL nBreak = pColEnd[nColPos]+1;
                if ( nBreak >= nX1 && nBreak <= nX2+1 )
                {
                    //! Search for hidden
                    if (rDoc.HasColBreak(nBreak, nTab) & ScBreakType::Manual)
                        rRenderContext.SetFillColor( aManual );
                    else
                        rRenderContext.SetFillColor( aAutomatic );
                    Point aBreak = mrViewData.GetScrPos(
                                    nBreak, aRange.aStart.Row(), eWhich, true );
                    rRenderContext.DrawRect( tools::Rectangle( aBreak.X()-1, aStart.Y(), aBreak.X(), aEnd.Y() ) );
                }
            }

            size_t nRowBreaks = rData.GetPagesY();
            const SCROW* pRowEnd = rData.GetPageEndY();
            size_t nRowPos;
            for (nRowPos=0; nRowPos+1<nRowBreaks; nRowPos++)
            {
                SCROW nBreak = pRowEnd[nRowPos]+1;
                if ( nBreak >= nY1 && nBreak <= nY2+1 )
                {
                    //! Search for hidden
                    if (rDoc.HasRowBreak(nBreak, nTab) & ScBreakType::Manual)
                        rRenderContext.SetFillColor( aManual );
                    else
                        rRenderContext.SetFillColor( aAutomatic );
                    Point aBreak = mrViewData.GetScrPos(
                                    aRange.aStart.Col(), nBreak, eWhich, true );
                    rRenderContext.DrawRect( tools::Rectangle( aStart.X(), aBreak.Y()-1, aEnd.X(), aBreak.Y() ) );
                }
            }

            // Page numbers

            SCROW nPrStartY = aRange.aStart.Row();
            for (nRowPos=0; nRowPos<nRowBreaks; nRowPos++)
            {
                SCROW nPrEndY = pRowEnd[nRowPos];
                if ( nPrEndY >= nY1 && nPrStartY <= nY2 )
                {
                    SCCOL nPrStartX = aRange.aStart.Col();
                    for (nColPos=0; nColPos<nColBreaks; nColPos++)
                    {
                        SCCOL nPrEndX = pColEnd[nColPos];
                        if ( nPrEndX >= nX1 && nPrStartX <= nX2 )
                        {
                            Point aPageStart = mrViewData.GetScrPos(
                                                    nPrStartX, nPrStartY, eWhich, true );
                            Point aPageEnd = mrViewData.GetScrPos(
                                                    nPrEndX+1,nPrEndY+1, eWhich, true );

                            tools::Long nPageNo = rData.GetFirstPage();
                            if ( rData.IsTopDown() )
                                nPageNo += static_cast<tools::Long>(nColPos)*nRowBreaks+nRowPos;
                            else
                                nPageNo += static_cast<tools::Long>(nRowPos)*nColBreaks+nColPos;

                            OUString aThisPageStr = aPageStr.replaceFirst("%1", OUString::number(nPageNo));

                            if ( pEditEng )
                            {
                                //  find right font size with EditEngine
                                tools::Long nHeight = 100;
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) );
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) );
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) );
                                pEditEng->SetTextCurrentDefaults( aThisPageStr );
                                Size aSize100( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() );

                                //  40% of width or 60% of height
                                tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width();
                                tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height();
                                nHeight = std::min(nSizeX,nSizeY);
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) );
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) );
                                pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) );

                                //  centered output with EditEngine
                                Size aTextSize( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() );
                                Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2,
                                            (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 );
                                pEditEng->DrawText_ToPosition(rRenderContext, aPos);
                            }
                            else
                            {
                                //  find right font size for DrawText
                                aFont.SetFontSize( Size( 0,100 ) );
                                rRenderContext.SetFont( aFont );

                                Size aSize100(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() );
                                if (aSize100.Width() && aSize100.Height())
                                {
                                    //  40% of width or 60% of height
                                    tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width();
                                    tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height();
                                    aFont.SetFontSize( Size( 0,std::min(nSizeX,nSizeY) ) );
                                    rRenderContext.SetFont( aFont );
                                }

                                //  centered output with DrawText
                                Size aTextSize(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() );
                                Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2,
                                            (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 );
                                rRenderContext.DrawText( aPos, aThisPageStr );
                            }
                        }
                        nPrStartX = nPrEndX + 1;
                    }
                }
                nPrStartY = nPrEndY + 1;
            }
        }
    }
}

void ScGridWindow::DrawButtons(SCCOL nX1, SCCOL nX2, const ScTableInfo& rTabInfo, OutputDevice* pContentDev, const ScLokRTLContext* pLokRTLContext)
{
    aComboButton.SetOutputDevice( pContentDev );

    ScDocument& rDoc = mrViewData.GetDocument();
    ScDPFieldButton aCellBtn(pContentDev, GetSettings().GetStyleSettings(), mrViewData.GetZoomY(), rDoc);

    SCCOL nCol;
    SCROW nRow;
    SCSIZE nArrY;
    SCSIZE nQuery;
    SCTAB           nTab = mrViewData.CurrentTabForData();
    ScDBData*       pDBData = nullptr;
    std::unique_ptr<ScQueryParam> pQueryParam;

    RowInfo*        pRowInfo = rTabInfo.mpRowInfo.get();
    sal_uInt16      nArrCount = rTabInfo.mnArrCount;

    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );

    Point aOldPos  = aComboButton.GetPosPixel();    // store state for MouseDown/Up
    Size  aOldSize = aComboButton.GetSizePixel();

    for (nArrY=1; nArrY+1<nArrCount; nArrY++)
    {
        if ( pRowInfo[nArrY].bAutoFilter && pRowInfo[nArrY].bChanged )
        {
            RowInfo* pThisRowInfo = &pRowInfo[nArrY];

            nRow = pThisRowInfo->nRowNo;

            for (nCol=nX1; nCol<=nX2; nCol++)
            {
                ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
                //if several columns merged on a row, there should be only one auto button at the end of the columns.
                //if several rows merged on a column, the button may be in the middle, so "!pInfo->bVOverlapped" should not be used
                if ( pInfo->bAutoFilter && !pInfo->bHOverlapped )
                {
                    if (!pQueryParam)
                        pQueryParam.reset(new ScQueryParam);

                    bool bNewData = true;
                    if (pDBData)
                    {
                        SCCOL nStartCol;
                        SCROW nStartRow;
                        SCCOL nEndCol;
                        SCROW nEndRow;
                        SCTAB nAreaTab;
                        pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow );
                        if ( nCol >= nStartCol && nCol <= nEndCol &&
                             nRow >= nStartRow && nRow <= nEndRow )
                            bNewData = false;
                    }
                    if (bNewData)
                    {
                        pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA );
                        if (pDBData)
                            pDBData->GetQueryParam( *pQueryParam );
                        else
                        {
                            // can also be part of DataPilot table
                        }
                    }

                    //  pQueryParam can only include MAXQUERY entries

                    bool bArrowState = false;
                    if (pQueryParam->bInplace)
                    {
                        SCSIZE nCount = pQueryParam->GetEntryCount();
                        for (nQuery = 0; nQuery < nCount; ++nQuery)
                        {
                            //  Do no restrict to EQUAL here
                            //  (Column head should become blue also when ">1")
                            const ScQueryEntry& rEntry = pQueryParam->GetEntry(nQuery);
                            if (rEntry.bDoQuery && rEntry.nField == nCol)
                            {
                                bArrowState = true;
                                break;
                            }
                        }
                    }

                    tools::Long    nSizeX;
                    tools::Long    nSizeY;
                    SCCOL nStartCol= nCol;
                    SCROW nStartRow = nRow;
                    //if address(nCol,nRow) is not the start pos of the merge area, the value of the nSizeX will be incorrect, it will be the length of the cell.
                    //should first get the start pos of the merge area, then get the nSizeX through the start pos.
                    rDoc.ExtendOverlapped(nStartCol, nStartRow,nCol, nRow, nTab);//get nStartCol,nStartRow
                    mrViewData.GetMergeSizePixel( nStartCol, nStartRow, nSizeX, nSizeY );//get nSizeX
                    nSizeY = ScViewData::ToPixel(rDoc.GetRowHeight(nRow, nTab), mrViewData.GetPPTY());
                    Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich );
                    if (pLokRTLContext)
                        aScrPos.setX(pLokRTLContext->docToTilePos(aScrPos.X()));

                    aCellBtn.setBoundingBox(aScrPos, Size(nSizeX-1, nSizeY-1), bLayoutRTL);
                    aCellBtn.setPopupLeft(bLayoutRTL);   // #i114944# AutoFilter button is left-aligned in RTL
                    aCellBtn.setDrawBaseButton(false);
                    aCellBtn.setDrawPopupButton(true);
                    aCellBtn.setHasHiddenMember(bArrowState);
                    aCellBtn.draw();
                }
            }
        }

        if ( (pRowInfo[nArrY].bPivotToggle || pRowInfo[nArrY].bPivotButton) && pRowInfo[nArrY].bChanged )
        {
            RowInfo* pThisRowInfo = &pRowInfo[nArrY];
            nRow = pThisRowInfo->nRowNo;
            for (nCol=nX1; nCol<=nX2; nCol++)
            {
                ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
                if (pInfo->bHOverlapped || pInfo->bVOverlapped)
                    continue;

                Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich );
                tools::Long nSizeX;
                tools::Long nSizeY;
                mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY );
                tools::Long nPosX = aScrPos.X();
                tools::Long nPosY = aScrPos.Y();
                // bLayoutRTL is handled in setBoundingBox

                bool bDrawToggle = pInfo->bPivotCollapseButton || pInfo->bPivotExpandButton;
                if (!bDrawToggle)
                {
                    OUString aStr = rDoc.GetString(nCol, nRow, nTab);
                    aCellBtn.setText(aStr);
                }

                const ScIndentItem& rIndentItem = rDoc.GetAttr(nCol, nRow, nTab, ATTR_INDENT);
                sal_uInt16 nIndent = rIndentItem.GetValue();
                aCellBtn.setBoundingBox(Point(nPosX, nPosY), Size(nSizeX-1, nSizeY-1), bLayoutRTL);
                aCellBtn.setPopupLeft(false);   // DataPilot popup is always right-aligned for now
                aCellBtn.setDrawBaseButton(pInfo->bPivotButton);
                aCellBtn.setDrawPopupButton(pInfo->bPivotPopupButton);
                aCellBtn.setDrawPopupButtonMulti(pInfo->bPivotPopupButtonMulti);
                aCellBtn.setDrawToggleButton(bDrawToggle, pInfo->bPivotCollapseButton, nIndent);
                aCellBtn.setHasHiddenMember(pInfo->bFilterActive);
                aCellBtn.draw();
            }
        }

        if ( !comphelper::LibreOfficeKit::isActive() && bListValButton && pRowInfo[nArrY].nRowNo == aListValPos.Row() && pRowInfo[nArrY].bChanged )
        {
            tools::Rectangle aRect = GetListValButtonRect( aListValPos );
            aComboButton.SetPosPixel( aRect.TopLeft() );
            aComboButton.SetSizePixel( aRect.GetSize() );
            pContentDev->SetClipRegion(vcl::Region(aRect));
            aComboButton.Draw();
            pContentDev->SetClipRegion();           // always called from Draw() without clip region
            aComboButton.SetPosPixel( aOldPos );    // restore old state
            aComboButton.SetSizePixel( aOldSize );  // for MouseUp/Down (AutoFilter)
        }
    }

    pQueryParam.reset();
    aComboButton.SetOutputDevice( GetOutDev() );
}

tools::Rectangle ScGridWindow::GetListValButtonRect( const ScAddress& rButtonPos )
{
    ScDocument& rDoc = mrViewData.GetDocument();
    SCTAB nTab = mrViewData.CurrentTabForData();
    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;

    ScDDComboBoxButton aButton( GetOutDev() );             // for optimal size
    Size aBtnSize = aButton.GetSizePixel();

    SCCOL nCol = rButtonPos.Col();
    SCROW nRow = rButtonPos.Row();

    tools::Long nCellSizeX;    // width of this cell, including merged
    tools::Long nDummy;
    mrViewData.GetMergeSizePixel( nCol, nRow, nCellSizeX, nDummy );

    // for height, only the cell's row is used, excluding merged cells
    tools::Long nCellSizeY = ScViewData::ToPixel( rDoc.GetRowHeight( nRow, nTab ), mrViewData.GetPPTY() );
    tools::Long nAvailable = nCellSizeX;

    //  left edge of next cell if there is a non-hidden next column
    SCCOL nNextCol = nCol + 1;
    const ScMergeAttr& rMerge = rDoc.GetAttr( nCol,nRow,nTab, ATTR_MERGE );
    if (rMerge.GetColMerge() > 1)
        nNextCol = nCol + rMerge.GetColMerge();    // next cell after the merged area
    while ( nNextCol <= rDoc.MaxCol() && rDoc.ColHidden(nNextCol, nTab) )
        ++nNextCol;
    bool bNextCell = ( nNextCol <= rDoc.MaxCol() );
    if ( bNextCell )
        nAvailable = ScViewData::ToPixel( rDoc.GetColWidth( nNextCol, nTab ), mrViewData.GetPPTX() );

    if ( nAvailable < aBtnSize.Width() )
        aBtnSize.setWidth( nAvailable );
    if ( nCellSizeY < aBtnSize.Height() )
        aBtnSize.setHeight( nCellSizeY );

    Point aPos = mrViewData.GetScrPos( nCol, nRow, eWhich, true );
    aPos.AdjustX(nCellSizeX * nLayoutSign );               // start of next cell
    if (!bNextCell)
        aPos.AdjustX( -(aBtnSize.Width() * nLayoutSign) );     // right edge of cell if next cell not available
    aPos.AdjustY(nCellSizeY - aBtnSize.Height() );
    // X remains at the left edge

    if ( bLayoutRTL )
        aPos.AdjustX( -(aBtnSize.Width()-1) );     // align right edge of button with cell border

    return tools::Rectangle( aPos, aBtnSize );
}

bool ScGridWindow::IsAutoFilterActive( SCCOL nCol, SCROW nRow, SCTAB nTab )
{
    ScDocument&     rDoc    = mrViewData.GetDocument();
    ScDBData*       pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA );
    ScQueryParam    aQueryParam;

    if ( pDBData )
        pDBData->GetQueryParam( aQueryParam );
    else
    {
        OSL_FAIL("Auto filter button without DBData");
    }

    bool    bSimpleQuery = true;
    bool    bColumnFound = false;
    SCSIZE  nQuery;

    if ( !aQueryParam.bInplace )
        bSimpleQuery = false;

    // aQueryParam can only include MAXQUERY entries

    SCSIZE nCount = aQueryParam.GetEntryCount();
    for (nQuery = 0; nQuery < nCount && bSimpleQuery; ++nQuery)
        if ( aQueryParam.GetEntry(nQuery).bDoQuery )
        {
            if (aQueryParam.GetEntry(nQuery).nField == nCol)
                bColumnFound = true;

            if (nQuery > 0)
                if (aQueryParam.GetEntry(nQuery).eConnect != SC_AND)
                    bSimpleQuery = false;
        }

    return ( bSimpleQuery && bColumnFound );
}

void ScGridWindow::GetSelectionRects( ::std::vector< tools::Rectangle >& rPixelRects ) const
{
    GetPixelRectsFor( mrViewData.GetMarkData(), rPixelRects );
}

void ScGridWindow::GetSelectionRectsPrintTwips(::std::vector< tools::Rectangle >& rRects) const
{
    GetRectsAnyFor(mrViewData.GetMarkData(), rRects, true);
}

/// convert rMarkData into pixel rectangles for this view
void ScGridWindow::GetPixelRectsFor( const ScMarkData &rMarkData,
                                     ::std::vector< tools::Rectangle >& rPixelRects ) const
{
    GetRectsAnyFor(rMarkData, rPixelRects, false);
}

void ScGridWindow::GetRectsAnyFor(const ScMarkData &rMarkData,
                                  ::std::vector< tools::Rectangle >& rRects,
                                  bool bInPrintTwips) const
{
    ScDocument& rDoc = mrViewData.GetDocument();
    SCTAB nTab = mrViewData.CurrentTabForData();
    double nPPTX = mrViewData.GetPPTX();
    double nPPTY = mrViewData.GetPPTY();
    bool bLayoutRTL = rDoc.IsLayoutRTL( nTab );
    // LOK clients needs exact document coordinates, so don't horizontally mirror them.
    tools::Long nLayoutSign = (!comphelper::LibreOfficeKit::isActive() && bLayoutRTL) ? -1 : 1;

    ScMarkData aMultiMark( rMarkData );
    aMultiMark.SetMarking( false );

    if (!aMultiMark.IsMultiMarked())
    {
        // simple range case - simplify calculation
        const ScRange& aSimpleRange = aMultiMark.GetMarkArea();

        aMultiMark.MarkToMulti();
        if ( !aMultiMark.IsMultiMarked() )
            return;

        SCCOL nX1 = aSimpleRange.aStart.Col();
        SCROW nY1 = aSimpleRange.aStart.Row();
        SCCOL nX2 = aSimpleRange.aEnd.Col();
        SCROW nY2 = aSimpleRange.aEnd.Row();

        PutInOrder( nX1, nX2 );
        PutInOrder( nY1, nY2 );

        SCCOL nPosX = mrViewData.GetPosX( eHWhich );
        SCROW nPosY = mrViewData.GetPosY( eVWhich );
        // is the selection visible at all?
        if (nX2 < nPosX || nY2 < nPosY)
            return;

        Point aScrStartPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) :
            mrViewData.GetScrPos(nX1, nY1, eWhich);

        tools::Long nStartX = aScrStartPos.X();
        tools::Long nStartY = aScrStartPos.Y();

        Point aScrEndPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX2, nY2) :
            mrViewData.GetScrPos(nX2, nY2, eWhich);

        tools::Long nWidthTwips = rDoc.GetColWidth(nX2, nTab);
        const tools::Long nWidth = bInPrintTwips ?
            nWidthTwips : ScViewData::ToPixel(nWidthTwips, nPPTX);
        tools::Long nEndX = aScrEndPos.X() + (nWidth - 1) * nLayoutSign;

        sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY2, nTab );
        const tools::Long nHeight = bInPrintTwips ?
            nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY);
        tools::Long nEndY = aScrEndPos.Y() + nHeight - 1;

        ScInvertMerger aInvert( &rRects );
        aInvert.AddRect( tools::Rectangle( nStartX, nStartY, nEndX, nEndY ) );

        return;
    }

    aMultiMark.MarkToMulti();
    if ( !aMultiMark.IsMultiMarked() )
        return;
    const ScRange& aMultiRange = aMultiMark.GetMultiMarkArea();
    SCCOL nX1 = aMultiRange.aStart.Col();
    SCROW nY1 = aMultiRange.aStart.Row();
    SCCOL nX2 = aMultiRange.aEnd.Col();
    SCROW nY2 = aMultiRange.aEnd.Row();

    PutInOrder( nX1, nX2 );
    PutInOrder( nY1, nY2 );

    SCCOL nTestX2 = nX2;
    SCROW nTestY2 = nY2;

    rDoc.ExtendMerge( nX1,nY1, nTestX2,nTestY2, nTab );

    SCCOL nPosX = mrViewData.GetPosX( eHWhich );
    SCROW nPosY = mrViewData.GetPosY( eVWhich );
    // is the selection visible at all?
    if (nTestX2 < nPosX || nTestY2 < nPosY)
        return;
    SCCOL nRealX1 = nX1;
    if (nX1 < nPosX)
        nX1 = nPosX;
    if (nY1 < nPosY)
        nY1 = nPosY;

    if (!comphelper::LibreOfficeKit::isActive())
    {
        // limit the selection to only what is visible on the screen
        SCCOL nXRight = nPosX + mrViewData.VisibleCellsX(eHWhich);
        if (nXRight > rDoc.MaxCol())
            nXRight = rDoc.MaxCol();

        SCROW nYBottom = nPosY + mrViewData.VisibleCellsY(eVWhich);
        if (nYBottom > rDoc.MaxRow())
            nYBottom = rDoc.MaxRow();

        // is the selection visible at all?
        if (nX1 > nXRight || nY1 > nYBottom)
            return;

        if (nX2 > nXRight)
            nX2 = nXRight;
        if (nY2 > nYBottom)
            nY2 = nYBottom;
    }
    else
    {
        SCCOL nMaxTiledCol;
        SCROW nMaxTiledRow;
        rDoc.GetTiledRenderingArea(nTab, nMaxTiledCol, nMaxTiledRow);

        if (nX2 > nMaxTiledCol)
            nX2 = nMaxTiledCol;
        if (nY2 > nMaxTiledRow)
            nY2 = nMaxTiledRow;
    }

    ScInvertMerger aInvert( &rRects );

    Point aScrPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) :
            mrViewData.GetScrPos(nX1, nY1, eWhich);
    tools::Long nScrY = aScrPos.Y();
    bool bWasHidden = false;
    for (SCROW nY=nY1; nY<=nY2; nY++)
    {
        bool bFirstRow = ( nY == nPosY );                       // first visible row?
        bool bDoHidden = false;                                 // repeat hidden ?
        sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY,nTab );
        bool bDoRow = ( nHeightTwips != 0 );
        if (bDoRow)
        {
            if (bWasHidden)                 // test hidden merge
            {
                bDoHidden = true;
                bDoRow = true;
            }

            bWasHidden = false;
        }
        else
        {
            bWasHidden = true;
            if (nY==nY2)
                bDoRow = true;              // last cell of the block
        }

        if ( bDoRow )
        {
            SCCOL nLoopEndX = nX2;
            if (nX2 < nX1)                      // the rest of the merge
            {
                SCCOL nStartX = nX1;
                while (rDoc.GetAttr(nStartX, nY, nTab, ATTR_MERGE_FLAG).IsHorOverlapped())
                    --nStartX;
                if (nStartX <= nX2)
                    nLoopEndX = nX1;
            }

            const tools::Long nHeight = bInPrintTwips ?
                    nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY);
            tools::Long nEndY = nScrY + nHeight - 1;
            tools::Long nScrX = aScrPos.X();
            for (SCCOL nX=nX1; nX<=nLoopEndX; nX++)
            {
                tools::Long nWidth = rDoc.GetColWidth(nX, nTab);
                if (!bInPrintTwips)
                    nWidth = ScViewData::ToPixel(nWidth, nPPTX);

                if ( nWidth > 0 )
                {
                    tools::Long nEndX = nScrX + ( nWidth - 1 ) * nLayoutSign;

                    SCROW nThisY = nY;
                    const ScPatternAttr* pPattern = rDoc.GetPattern( nX, nY, nTab );
                    const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
                    if ( pMergeFlag->IsVerOverlapped() && ( bDoHidden || bFirstRow ) )
                    {
                        while ( pMergeFlag->IsVerOverlapped() && nThisY > 0 &&
                                (rDoc.RowHidden(nThisY-1, nTab) || bFirstRow) )
                        {
                            --nThisY;
                            pPattern = rDoc.GetPattern( nX, nThisY, nTab );
                            pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
                        }
                    }

                    // only the rest of the merged is seen ?
                    SCCOL nThisX = nX;
                    if ( pMergeFlag->IsHorOverlapped() && nX == nPosX && nX > nRealX1 )
                    {
                        while ( pMergeFlag->IsHorOverlapped() )
                        {
                            --nThisX;
                            pPattern = rDoc.GetPattern( nThisX, nThisY, nTab );
                            pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
                        }
                    }

                    if ( aMultiMark.IsCellMarked( nThisX, nThisY, true ) )
                    {
                        if ( !pMergeFlag->IsOverlapped() )
                        {
                            const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
                            if (pMerge->GetColMerge() > 0 || pMerge->GetRowMerge() > 0)
                            {
                                const SCCOL nEndColMerge = nThisX + pMerge->GetColMerge();
                                const SCROW nEndRowMerge = nThisY + pMerge->GetRowMerge();
                                Point aEndPos = bInPrintTwips ?
                                        mrViewData.GetPrintTwipsPos(nEndColMerge, nEndRowMerge) :
                                        mrViewData.GetScrPos(nEndColMerge, nEndRowMerge, eWhich);
                                if ( aEndPos.X() * nLayoutSign > nScrX * nLayoutSign && aEndPos.Y() > nScrY )
                                {
                                    aInvert.AddRect( tools::Rectangle( nScrX,nScrY,
                                                aEndPos.X()-nLayoutSign,aEndPos.Y()-1 ) );
                                }
                            }
                            else if ( nEndX * nLayoutSign >= nScrX * nLayoutSign && nEndY >= nScrY )
                            {
                                aInvert.AddRect( tools::Rectangle( nScrX,nScrY,nEndX,nEndY ) );
                            }
                        }
                    }

                    nScrX = nEndX + nLayoutSign;
                }
            }
            nScrY = nEndY + 1;
        }
    }
}

void ScGridWindow::DataChanged( const DataChangedEvent& rDCEvt )
{
    Window::DataChanged(rDCEvt);

    if ( !((rDCEvt.GetType() == DataChangedEventType::PRINTER) ||
         (rDCEvt.GetType() == DataChangedEventType::DISPLAY) ||
         (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
         (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
         ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
          (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
        return;

    if ( rDCEvt.GetType() == DataChangedEventType::FONTS && eWhich == mrViewData.GetActivePart() )
        mrViewData.GetDocShell()->UpdateFontList();

    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
         (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
    {
        if ( eWhich == mrViewData.GetActivePart() )     // only once for the view
        {
            ScTabView* pView = mrViewData.GetView();

            pView->RecalcPPT();

            //  RepeatResize in case scroll bar sizes have changed
            pView->RepeatResize();
            pView->UpdateAllOverlays();

            //  invalidate cell attribs in input handler, in case the
            //  EditEngine BackgroundColor has to be changed
            if ( mrViewData.IsActive() )
            {
                ScInputHandler* pHdl = ScModule::get()->GetInputHdl();
                if (pHdl)
                    pHdl->ForgetLastPattern();
            }
        }
    }

    Invalidate();
}

void ScGridWindow::initiatePageBreaks()
{
    bInitialPageBreaks = true;
}

IMPL_LINK(ScGridWindow, InitiatePageBreaksTimer, Timer*, pTimer, void)
{
    if (pTimer != &maShowPageBreaksTimer)
        return;

    const ScViewOptions& rOpts = mrViewData.GetOptions();
    const bool bPage = rOpts.GetOption(sc::ViewOption::PAGEBREAKS);
    // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page
    // breaks is enabled, breaks should be visible. If the document is
    // opened the first time or a tab is activated the first time, the
    // breaks are not calculated yet, so this initialization is done here.
    if (bPage)
    {
        const SCTAB nCurrentTab = mrViewData.CurrentTabForData();
        ScDocument& rDoc = mrViewData.GetDocument();
        const Size aPageSize = rDoc.GetPageSize(nCurrentTab);
        // Do not attempt to calculate a page size here if it is empty if
        // that involves counting pages.
        // An earlier implementation did
        //   ScPrintFunc(pDocSh, pDocSh->GetPrinter(), nCurrentTab);
        //   rDoc.SetPageSize(nCurrentTab, rDoc.GetPageSize(nCurrentTab));
        // which resulted in tremendous waiting times after having loaded
        // larger documents i.e. imported from CSV, in which UI is entirely
        // blocked. All time is spent under ScPrintFunc::CountPages() in
        // ScTable::ExtendPrintArea() in the loop that calls
        // MaybeAddExtraColumn() to do stuff for each text string content
        // cell (each row in each column). Maybe that can be optimized, or
        // obtaining page size without that overhead would be possible, but
        // as is calling that from here is a no-no so this is a quick
        // disable things.
        if (!aPageSize.IsEmpty())
        {
            ScDocShell* pDocSh = mrViewData.GetDocShell();
            const bool bModified = pDocSh->IsModified();
            // Even setting the same size sets page size valid, so
            // UpdatePageBreaks() actually does something.
            rDoc.SetPageSize( nCurrentTab, aPageSize);
            rDoc.UpdatePageBreaks(nCurrentTab);
            pDocSh->PostPaint(0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab, PaintPartFlags::Grid);
            pDocSh->SetModified(bModified);
        }
    }
}


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