Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sc/source/ui/view/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 102 kB image not shown  

Quelle  gridwin4.cxx

  Sprache: C
 

/* -*- 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.GetTabNo();
    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.GetTabNo();
    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.GetTabNo();

    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.GetTabNo();
    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(VOPT_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 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 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.nTab;
    SCCOL nX1 = aOutputData.nX1;
    SCROW nY1 = aOutputData.nY1;
    SCCOL nX2 = aOutputData.nX2;
    SCROW nY2 = aOutputData.nY2;
    tools::Long nScrX = aOutputData.nScrX;
    tools::Long nScrY = aOutputData.nScrY;

    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( VOPT_NULLVALS ) );
    aOutputData.SetShowFormulas     ( rOpts.GetOption( VOPT_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( VOPT_GRID ) && mrViewData.GetShowGrid();
    bool bGridFirst = !rOpts.GetOption( VOPT_GRID_ONTOP );

    bool bPage = rOpts.GetOption( VOPT_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().GetTabNo())->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.GetTabNo());
    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);

    // Show Note Mark
    if ( rOpts.GetOption( VOPT_NOTES ) )
        aOutputData.DrawNoteMarks(*pContentDev);

    if ( rOpts.GetOption( VOPT_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

    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& rDocSh = mrViewData.GetDocShell();
        ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList();
        if ( pRangeFinder && !pRangeFinder->IsHidden() &&
                pRangeFinder->GetDocName() == rDocSh.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.nScrX + aOutputData.GetScrW(), o3tl::Length::px));
                else
                    aOrigin.AdjustX(o3tl::toTwips(aOutputData.nScrX, o3tl::Length::px));

                aOrigin.AdjustY(o3tl::toTwips(aOutputData.nScrY, 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->Paint(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->Paint(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, truefalse);
    rDoc.GetAllRowBreaks(aRowBreaks, nTab, truefalse);
    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.GetTabNo();

        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.GetTabNo();
    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, falsefalse);

// 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.GetTabNo()))
    {
        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.GetTabNo();
    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.GetTabNo();
    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.GetTabNo();
    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->Draw(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.GetTabNo();
    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);
                }

                sal_uInt16 nIndent = 0;
                if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, nTab, ATTR_INDENT))
                    nIndent = pIndentItem->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.GetTabNo();
    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* pMerge = rDoc.GetAttr( nCol,nRow,nTab, ATTR_MERGE );
    if ( pMerge->GetColMerge() > 1 )
        nNextCol = nCol + pMerge->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.GetTabNo();
    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(VOPT_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.GetTabNo();
        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, rDocSh.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& rDocSh = mrViewData.GetDocShell();
            const bool bModified = rDocSh.IsModified();
            // Even setting the same size sets page size valid, so
            // UpdatePageBreaks() actually does something.
            rDoc.SetPageSize( nCurrentTab, aPageSize);
            rDoc.UpdatePageBreaks(nCurrentTab);
            rDocSh.PostPaint(0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab, PaintPartFlags::Grid);
            rDocSh.SetModified(bModified);
        }
    }
}


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

Messung V0.5 in Prozent
C=90 H=93 G=91

¤ Dauer der Verarbeitung: 0.43 Sekunden  (vorverarbeitet am  2026-05-07) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.