/* -*- 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 <scitems.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <sfx2/bindings.hxx>
#include <osl/diagnose.h>

#include <attrib.hxx>
#include <pagedata.hxx>
#include <tabview.hxx>
#include <tabvwsh.hxx>
#include <printfun.hxx>
#include <stlpool.hxx>
#include <docsh.hxx>
#include <gridwin.hxx>
#include <sc.hrc>
#include <viewutil.hxx>
#include <colrowba.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <scmod.hxx>
#include <table.hxx>
#include <tabprotection.hxx>
#include <markdata.hxx>
#include <inputopt.hxx>
#include <comphelper/lok.hxx>

namespace {

bool isCellQualified(const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, bool bSelectLocked, bool bSelectUnlocked)
{
    bool bCellProtected = pDoc->HasAttrib(
        nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected);

    if (bCellProtected && !bSelectLocked)
        return false;

    if (!bCellProtected && !bSelectUnlocked)
        return false;

    return true;
}

bool areCellsQualified(const ScDocument* pDoc, SCCOL nColStart, SCROW nRowStart, SCCOL nColEnd,
                       SCROW nRowEnd, SCTAB nTab, bool bSelectLocked, bool bSelectUnlocked)
{
    PutInOrder(nColStart, nColEnd);
    PutInOrder(nRowStart, nRowEnd);
    for (SCCOL col = nColStart; col <= nColEnd; ++col)
        for (SCROW row = nRowStart; row <= nRowEnd; ++row)
            if (!isCellQualified(pDoc, col, row, nTab, bSelectLocked, bSelectUnlocked))
                return false;

    return true;
}

void moveCursorByProtRule(
    SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCTAB nTab, const ScDocument* pDoc)
{
    bool bSelectLocked = true;
    bool bSelectUnlocked = true;
    const ScTableProtection* pTabProtection = pDoc->GetTabProtection(nTab);
    if (pTabProtection && pTabProtection->isProtected())
    {
        bSelectLocked   = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
    }

    if (nMovX > 0)
    {
        for (SCCOL i = 0; i < nMovX && rCol < pDoc->MaxCol(); ++i)
        {
            SCCOL nNewUnhiddenCol = rCol + 1;
            SCCOL nEndCol = 0;
            while(pDoc->ColHidden(nNewUnhiddenCol, nTab, nullptr, &nEndCol))
            {
                if(nNewUnhiddenCol >= pDoc->MaxCol())
                    return;

                i += nEndCol - nNewUnhiddenCol + 1;
                nNewUnhiddenCol = nEndCol +1;
            }

            if (!isCellQualified(pDoc, nNewUnhiddenCol, rRow, nTab, bSelectLocked, bSelectUnlocked))
                break;
            rCol = nNewUnhiddenCol;
        }
    }
    else if (nMovX < 0)
    {
        for (SCCOL i = 0; i > nMovX && rCol > 0; --i)
        {
            SCCOL nNewUnhiddenCol = rCol - 1;
            SCCOL nStartCol = 0;
            while(pDoc->ColHidden(nNewUnhiddenCol, nTab, &nStartCol))
            {
                if(nNewUnhiddenCol <= 0)
                    return;

                i -= nNewUnhiddenCol - nStartCol + 1;
                nNewUnhiddenCol = nStartCol - 1;
            }

            if (!isCellQualified(pDoc, nNewUnhiddenCol, rRow, nTab, bSelectLocked, bSelectUnlocked))
                break;
            rCol = nNewUnhiddenCol;
        }
    }

    if (nMovY > 0)
    {
        for (SCROW i = 0; i < nMovY && rRow < pDoc->MaxRow(); ++i)
        {
            SCROW nNewUnhiddenRow = rRow + 1;
            SCROW nEndRow = 0;
            while(pDoc->RowHidden(nNewUnhiddenRow, nTab, nullptr, &nEndRow))
            {
                if(nNewUnhiddenRow >= pDoc->MaxRow())
                    return;

                i += nEndRow - nNewUnhiddenRow + 1;
                nNewUnhiddenRow = nEndRow + 1;
            }

            if (!isCellQualified(pDoc, rCol, nNewUnhiddenRow, nTab, bSelectLocked, bSelectUnlocked))
                break;
            rRow = nNewUnhiddenRow;
        }
    }
    else if (nMovY < 0)
    {
        for (SCROW i = 0; i > nMovY && rRow > 0; --i)
        {
            SCROW nNewUnhiddenRow = rRow - 1;
            SCROW nStartRow = 0;
            while(pDoc->RowHidden(nNewUnhiddenRow, nTab, &nStartRow))
            {
                if(nNewUnhiddenRow <= 0)
                    return;

                i -= nNewUnhiddenRow - nStartRow + 1;
                nNewUnhiddenRow = nStartRow - 1;
            }

            if (!isCellQualified(pDoc, rCol, nNewUnhiddenRow, nTab, bSelectLocked, bSelectUnlocked))
                break;
            rRow = nNewUnhiddenRow;
        }
    }
}

bool checkBoundary(const ScDocument* pDoc, SCCOL& rCol, SCROW& rRow)
{
    bool bGood = true;
    if (rCol < 0)
    {
        rCol = 0;
        bGood = false;
    }
    else if (rCol > pDoc->MaxCol())
    {
        rCol = pDoc->MaxCol();
        bGood = false;
    }

    if (rRow < 0)
    {
        rRow = 0;
        bGood = false;
    }
    else if (rRow > pDoc->MaxRow())
    {
        rRow = pDoc->MaxRow();
        bGood = false;
    }
    return bGood;
}

void moveRefByCell(SCCOL& rNewX, SCROW& rNewY,
                   SCCOL nMovX, SCROW nMovY, SCTAB nRefTab,
                   const ScDocument& rDoc)
{
    SCCOL nOldX = rNewX;
    SCROW nOldY = rNewY;
    bool bSelectLocked = true;
    bool bSelectUnlocked = true;
    const ScTableProtection* pTabProtection = rDoc.GetTabProtection(nRefTab);
    if (pTabProtection && pTabProtection->isProtected())
    {
        bSelectLocked   = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
    }

    moveCursorByProtRule(rNewX, rNewY, nMovX, nMovY, nRefTab, &rDoc);
    checkBoundary(&rDoc, rNewX, rNewY);

    if (nMovX)
    {
        SCCOL nTempX = rNewX;
        while (rDoc.IsHorOverlapped(nTempX, rNewY, nRefTab))
        {
            nTempX = (nMovX > 0) ? nTempX + 1 : nTempX - 1;
            if (!checkBoundary(&rDoc, nTempX, rNewY))
                break;
        }
        if (isCellQualified(&rDoc, nTempX, rNewY, nRefTab, bSelectLocked, bSelectUnlocked))
            rNewX = nTempX;

        if (nMovX < 0 && rNewX > 0)
        {
            const ScMergeAttr& rMergeAttr = rDoc.GetAttr(rNewX, rNewY, nRefTab, ATTR_MERGE);
            if (rMergeAttr.IsMerged() &&
                nOldX >= rNewX &&
                nOldX <= rNewX + rMergeAttr.GetRowMerge() - 1)
                rNewX = rNewX - 1;
        }
    }

    if (nMovY)
    {
        SCROW nTempY = rNewY;
        while (rDoc.IsVerOverlapped(rNewX, nTempY, nRefTab))
        {
            nTempY = (nMovY > 0) ? nTempY + 1 : nTempY - 1;
            if (!checkBoundary(&rDoc, rNewX, nTempY))
                break;
        }
        if (isCellQualified(&rDoc, rNewX, nTempY, nRefTab, bSelectLocked, bSelectUnlocked))
            rNewY = nTempY;

        if (nMovY < 0 && rNewY > 0)
        {
            const ScMergeAttr& rMergeAttr = rDoc.GetAttr(rNewX, rNewY, nRefTab, ATTR_MERGE);
            if (rMergeAttr.IsMerged() &&
                nOldY >= rNewY &&
                nOldY <= rNewY + rMergeAttr.GetRowMerge() - 1)
                rNewY = rNewY - 1;
        }
    }

    rDoc.SkipOverlapped(rNewX, rNewY, nRefTab);
}

void moveCursorByMergedCell(SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCCOL nStartX,
                            SCROW nStartY, SCTAB nTab, const ScDocument* pDoc)
{
    const ScTableProtection* pTabProtection = pDoc->GetTabProtection(nTab);
    bool bSelectLocked = true;
    bool bSelectUnlocked = true;
    if (pTabProtection && pTabProtection->isProtected())
    {
        bSelectLocked   = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
    }

    if (nMovX > 0)
    {
        SCROW rowStart = std::min(rRow, nStartY);
        SCROW rowEnd = std::max(rRow, nStartY);

        for (SCROW i = rowStart; i <= rowEnd && rCol < nStartX;)
        {
            SCCOL tmpCol = rCol;
            while (tmpCol < pDoc->MaxCol() && pDoc->IsHorOverlapped(tmpCol, i, nTab))
                ++tmpCol;
            if (tmpCol != rCol)
            {
                i = rowStart;
                if (tmpCol > nStartX)
                    --tmpCol;
                if (!areCellsQualified(pDoc, rCol + 1, rowStart, tmpCol, rowEnd, nTab,
                                       bSelectLocked, bSelectUnlocked))
                    break;
                rCol = tmpCol;
            }
            else
                ++i;
        }
    }
    else if (nMovX < 0)
    {
        SCROW rowStart = std::min(rRow, nStartY);
        SCROW rowEnd = std::max(rRow, nStartY);

        for (SCROW i = rowStart; i <= rowEnd && rCol > nStartX;)
        {
            SCCOL tmpCol = rCol;
            while (tmpCol >= 0 && pDoc->IsHorOverlapped(tmpCol + 1, i, nTab))
                --tmpCol;
            if (tmpCol != rCol)
            {
                i = rowStart;
                if (tmpCol < nStartX)
                    ++tmpCol;
                if (!areCellsQualified(pDoc, rCol - 1, rowStart, tmpCol, rowEnd, nTab,
                                       bSelectLocked, bSelectUnlocked))
                    break;
                rCol = tmpCol;
            }
            else
                ++i;
        }
    }

    if (nMovY > 0)
    {
        SCCOL colStart = std::min(rCol, nStartX);
        SCCOL colEnd = std::max(rCol, nStartX);

        for (SCCOL i = colStart; i <= colEnd && rRow < nStartY;)
        {
            SCROW tmpRow = rRow;
            while (tmpRow < pDoc->MaxRow() && pDoc->IsVerOverlapped(i, tmpRow, nTab))
                ++tmpRow;
            if (tmpRow != rRow)
            {
                i = colStart;
                if (tmpRow > nStartY)
                    --tmpRow;
                if (!areCellsQualified(pDoc, colStart, rRow + 1, colEnd, tmpRow, nTab,
                                       bSelectLocked, bSelectUnlocked))
                    break;
                rRow = tmpRow;
            }
            else
                ++i;
        }
    }
    else if (nMovY < 0)
    {
        SCCOL colStart = std::min(rCol, nStartX);
        SCCOL colEnd = std::max(rCol, nStartX);

        for (SCCOL i = colStart; i <= colEnd && rRow > nStartY;)
        {
            SCROW tmpRow = rRow;
            while (tmpRow >= 0 && pDoc->IsVerOverlapped(i, tmpRow + 1, nTab))
                --tmpRow;
            if (tmpRow != rRow)
            {
                i = colStart;
                if (tmpRow < nStartY)
                    ++tmpRow;
                if (!areCellsQualified(pDoc, colStart, rRow - 1, colEnd, tmpRow, nTab,
                                       bSelectLocked, bSelectUnlocked))
                    break;
                rRow = tmpRow;
            }
            else
                ++i;
        }
    }
}

void moveCursorToProperSide(SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCCOL nStartX,
                            SCROW nStartY, SCTAB nTab, const ScDocument* pDoc)
{
    SCCOL tmpCol = rCol;
    SCROW tmpRow = rRow;

    if (nMovX > 0 && nStartX < pDoc->MaxCol() && rCol < nStartX)
    {
        SCROW rowStart = std::min(rRow, nStartY);
        SCROW rowEnd = std::max(rRow, nStartY);
        for (SCROW i = rowStart; i <= rowEnd && tmpCol < nStartX;)
        {
            if (pDoc->IsHorOverlapped(tmpCol + 1, i, nTab))
            {
                do
                {
                    ++tmpCol;
                } while (pDoc->IsHorOverlapped(tmpCol + 1, i, nTab));
                i = rowStart;
            }
            else
                ++i;
        }
        if (tmpCol < nStartX)
            tmpCol = rCol;
    }
    else if (nMovX < 0 && nStartX > 0 && rCol > nStartX)
    {
        SCROW rowStart = std::min(rRow, nStartY);
        SCROW rowEnd = std::max(rRow, nStartY);
        for (SCROW i = rowStart; i <= rowEnd && tmpCol > nStartX;)
        {
            if (pDoc->IsHorOverlapped(tmpCol, i, nTab))
            {
                do
                {
                    --tmpCol;
                } while (pDoc->IsHorOverlapped(tmpCol, i, nTab));
                i = rowStart;
            }
            else
                ++i;
        }
        if (tmpCol > nStartX)
            tmpCol = rCol;
    }

    if (nMovY > 0 && nStartY < pDoc->MaxRow() && rRow < nStartY)
    {
        SCCOL colStart = std::min(rCol, nStartX);
        SCCOL colEnd = std::max(rCol, nStartX);
        for (SCCOL i = colStart; i <= colEnd && tmpRow < nStartY;)
        {
            if (pDoc->IsVerOverlapped(i, tmpRow + 1, nTab))
            {
                do
                {
                    ++tmpRow;
                } while (pDoc->IsVerOverlapped(i, tmpRow + 1, nTab));
                i = colStart;
            }
            else
                ++i;
        }
        if (tmpRow < nStartY)
            tmpRow = rRow;
    }
    else if (nMovY < 0 && nStartY > 0 && rRow > nStartY)
    {
        SCCOL colStart = std::min(rCol, nStartX);
        SCCOL colEnd = std::max(rCol, nStartX);
        for (SCCOL i = colStart; i <= colEnd && tmpRow > nStartY;)
        {
            if (pDoc->IsVerOverlapped(i, tmpRow, nTab))
            {
                do
                {
                    --tmpRow;
                } while (pDoc->IsVerOverlapped(i, tmpRow, nTab));
                i = colStart;
            }
            else
                ++i;
        }
        if (tmpRow > nStartY)
            tmpRow = rRow;
    }

    if (tmpCol != rCol)
        rCol = tmpCol;
    if (tmpRow != rRow)
        rRow = tmpRow;
}
}

void ScTabView::PaintMarks(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow )
{
    auto& rDoc = aViewData.GetDocument();
    if (!rDoc.ValidCol(nStartCol)) nStartCol = rDoc.MaxCol();
    if (!rDoc.ValidRow(nStartRow)) nStartRow = rDoc.MaxRow();
    if (!rDoc.ValidCol(nEndCol)) nEndCol = rDoc.MaxCol();
    if (!rDoc.ValidRow(nEndRow)) nEndRow = rDoc.MaxRow();

    bool bLeft = (nStartCol==0 && nEndCol==rDoc.MaxCol());
    bool bTop = (nStartRow==0 && nEndRow==rDoc.MaxRow());

    if (bLeft)
        PaintLeftArea( nStartRow, nEndRow );
    if (bTop)
        PaintTopArea( nStartCol, nEndCol );

    aViewData.GetDocument().ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow,
                                            aViewData.CurrentTabForData() );
    PaintArea( nStartCol, nStartRow, nEndCol, nEndRow, ScUpdateMode::Marks );
}

bool ScTabView::IsMarking( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    return IsBlockMode()
        && nBlockStartX == nCol
        && nBlockStartY == nRow
        && nBlockStartZ == nTab;
}

void ScTabView::InitOwnBlockMode( const ScRange& rMarkRange )
{
    if (IsBlockMode())
        return;

    // when there is no (old) selection anymore, delete anchor in SelectionEngine:
    ScMarkData& rMark = aViewData.GetMarkData();
    if (!rMark.IsMarked() && !rMark.IsMultiMarked())
        GetSelEngine()->CursorPosChanging( false, false );

    meBlockMode = Own;
    nBlockStartX = rMarkRange.aStart.Col();
    nBlockStartY = rMarkRange.aStart.Row();
    nBlockStartZ = rMarkRange.aStart.Tab();
    nBlockEndX = rMarkRange.aEnd.Col();
    nBlockEndY = rMarkRange.aEnd.Row();
    nBlockEndZ = rMarkRange.aEnd.Tab();

    SelectionChanged();     // status is checked with mark set
}

void ScTabView::InitBlockModeHighlight( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ,
                                        bool bCols, bool bRows )
{
    if (meHighlightBlockMode != None)
        return;

    auto& rDoc = aViewData.GetDocument();
    if (!rDoc.ValidCol(nCurX)) nCurX = rDoc.MaxCol();
    if (!rDoc.ValidRow(nCurY)) nCurY = rDoc.MaxRow();

    ScMarkData& rMark = aViewData.GetHighlightData();
    meHighlightBlockMode = Normal;

    SCROW nStartY = nCurY;
    SCCOL nStartX = nCurX;
    SCROW nEndY = nCurY;
    SCCOL nEndX = nCurX;

    if (bCols)
    {
        nStartY = 0;
        nEndY = rDoc.MaxRow();
    }

    if (bRows)
    {
        nStartX = 0;
        nEndX = rDoc.MaxCol();
    }

    rMark.SetMarkArea( ScRange( nStartX, nStartY, nCurZ, nEndX, nEndY, nCurZ ) );
    UpdateHighlightOverlay();
}

void ScTabView::InitBlockMode( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ,
                               bool bTestNeg, bool bCols, bool bRows, bool bForceNeg )
{
    if (IsBlockMode())
        return;

    auto& rDoc = aViewData.GetDocument();
    if (!rDoc.ValidCol(nCurX)) nCurX = rDoc.MaxCol();
    if (!rDoc.ValidRow(nCurY)) nCurY = rDoc.MaxRow();

    ScMarkData& rMark = aViewData.GetMarkData();
    SCTAB nTab = aViewData.CurrentTabForData();

    //  unmark part?
    if (bForceNeg)
        bBlockNeg = true;
    else if (bTestNeg)
    {
        if ( bCols )
            bBlockNeg = rMark.IsColumnMarked( nCurX );
        else if ( bRows )
            bBlockNeg = rMark.IsRowMarked( nCurY );
        else
            bBlockNeg = rMark.IsCellMarked( nCurX, nCurY );
    }
    else
        bBlockNeg = false;
    rMark.SetMarkNegative(bBlockNeg);

    meBlockMode = Normal;
    bBlockCols = bCols;
    bBlockRows = bRows;
    nBlockStartX = nBlockStartXOrig = nCurX;
    nBlockStartY = nBlockStartYOrig = nCurY;
    nBlockStartZ = nCurZ;
    nBlockEndX = nOldCurX = nBlockStartX;
    nBlockEndY = nOldCurY = nBlockStartY;
    nBlockEndZ = nBlockStartZ;

    if (bBlockCols)
    {
        nBlockStartY = nBlockStartYOrig = 0;
        nBlockEndY = rDoc.MaxRow();
    }

    if (bBlockRows)
    {
        nBlockStartX = nBlockStartXOrig = 0;
        nBlockEndX = rDoc.MaxCol();
    }

    rMark.SetMarkArea( ScRange( nBlockStartX,nBlockStartY, nTab, nBlockEndX,nBlockEndY, nTab ) );

    UpdateSelectionOverlay();
}

void ScTabView::DoneBlockModeHighlight( bool bContinue )
{
    if (meHighlightBlockMode == None)
        return;

    ScMarkData& rMark = aViewData.GetHighlightData();
    bool bFlag = rMark.GetMarkingFlag();
    rMark.SetMarking(false);

    if (bContinue)
        rMark.MarkToMulti();
    else
    {
        SCTAB nTab = aViewData.CurrentTabForData();
        ScDocument& rDoc = aViewData.GetDocument();
        if ( rDoc.HasTable(nTab) )
            rMark.ResetMark();
    }
    meHighlightBlockMode = None;

    rMark.SetMarking(bFlag);
    if (bContinue)
        rMark.SetMarking(false);
}

void ScTabView::DoneBlockMode( bool bContinue )
{
    // When switching between sheet and header SelectionEngine DeselectAll may be called,
    // because the other engine does not have any anchor.
    // bMoveIsShift prevents the selection to be canceled.

    if (!IsBlockMode() || bMoveIsShift)
        return;

    ScMarkData& rMark = aViewData.GetMarkData();
    bool bFlag = rMark.GetMarkingFlag();
    rMark.SetMarking(false);

    if (bBlockNeg && !bContinue)
        rMark.MarkToMulti();

    if (bContinue)
        rMark.MarkToMulti();
    else
    {
        // the sheet may be invalid at this point because DoneBlockMode from SetTabNo is
        // called (for example, when the current sheet is closed from another View)
        SCTAB nTab = aViewData.CurrentTabForData();
        ScDocument& rDoc = aViewData.GetDocument();
        if ( rDoc.HasTable(nTab) )
            PaintBlock( true );                             // true -> delete block
        else
            rMark.ResetMark();
    }
    meBlockMode = None;

    rMark.SetMarking(bFlag);
    rMark.SetMarkNegative(false);
}

bool ScTabView::IsBlockMode() const
{
    return meBlockMode != None;
}

void ScTabView::MarkCursor( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ,
                            bool bCols, bool bRows, bool bCellSelection )
{
    ScDocument& rDocument = aViewData.GetDocument();
    if (!rDocument.ValidCol(nCurX)) nCurX = rDocument.MaxCol();
    if (!rDocument.ValidRow(nCurY)) nCurY = rDocument.MaxRow();

    if (!IsBlockMode())
    {
        OSL_FAIL( "MarkCursor not in BlockMode" );
        InitBlockMode( nCurX, nCurY, nCurZ, false, bCols, bRows );
    }

    if (bCols)
        nCurY = rDocument.MaxRow();
    if (bRows)
        nCurX = rDocument.MaxCol();

    ScMarkData& rMark = aViewData.GetMarkData();
    OSL_ENSURE(rMark.IsMarked() || rMark.IsMultiMarked(), "MarkCursor, !IsMarked()");
    const ScRange& aMarkRange = rMark.GetMarkArea();
    if (( aMarkRange.aStart.Col() != nBlockStartX && aMarkRange.aEnd.Col() != nBlockStartX ) ||
        ( aMarkRange.aStart.Row() != nBlockStartY && aMarkRange.aEnd.Row() != nBlockStartY ) ||
        ( meBlockMode == Own ))
    {
        //  Mark has been changed
        // (Eg MarkToSimple if by negative everything was erased, except for a rectangle)
        // or after InitOwnBlockMode is further marked with shift-
        bool bOldShift = bMoveIsShift;
        bMoveIsShift = false;          //  really move
        DoneBlockMode();               //! Set variables directly? (-> no flicker)
        bMoveIsShift = bOldShift;

        InitBlockMode( aMarkRange.aStart.Col(), aMarkRange.aStart.Row(),
                        nBlockStartZ, rMark.IsMarkNegative(), bCols, bRows );
    }

    if ( nCurX != nOldCurX || nCurY != nOldCurY )
    {
        // Current cursor has moved

        SCTAB nTab = nCurZ;

        if ( bCellSelection )
        {
            // Expand selection area accordingly when the current selection cuts
            // through a merged cell.
            ScRange cellSel(nBlockStartXOrig, nBlockStartYOrig, nTab, nCurX, nCurY, nTab);
            cellSel.PutInOrder();
            ScRange oldSel;
            do
            {
                oldSel = cellSel;
                rDocument.ExtendOverlapped(cellSel);
                rDocument.ExtendMerge(cellSel);
            } while (oldSel != cellSel);

            // Preserve the directionality of the selection
            if (nCurX >= nBlockStartXOrig)
            {
                nBlockStartX = cellSel.aStart.Col();
                nBlockEndX = cellSel.aEnd.Col();
            }
            else
            {
                nBlockStartX = cellSel.aEnd.Col();
                nBlockEndX = cellSel.aStart.Col();
            }
            if (nCurY >= nBlockStartYOrig)
            {
                nBlockStartY = cellSel.aStart.Row();
                nBlockEndY = cellSel.aEnd.Row();
            }
            else
            {
                nBlockStartY = cellSel.aEnd.Row();
                nBlockEndY = cellSel.aStart.Row();
            }
        }
        else
        {
            nBlockEndX = nCurX;
            nBlockEndY = nCurY;
        }

        // Set new selection area
        rMark.SetMarkArea( ScRange( nBlockStartX, nBlockStartY, nTab, nBlockEndX, nBlockEndY, nTab ) );

        UpdateSelectionOverlay();
        SelectionChanged();

        nOldCurX = nBlockEndX;
        nOldCurY = nBlockEndY;

        aViewData.GetViewShell()->UpdateInputHandler();
    }

    if ( !bCols && !bRows )
        aHdrFunc.SetAnchorFlag( false );
}

void ScTabView::GetPageMoveEndPosition(SCCOL nMovX, SCROW nMovY, SCCOL& rPageX, SCROW& rPageY)
{
    SCCOL nCurX;
    SCROW nCurY;
    if (aViewData.IsRefMode())
    {
        nCurX = aViewData.GetRefEndX();
        nCurY = aViewData.GetRefEndY();
    }
    else if (IsBlockMode())
    {
        // block end position.
        nCurX = nBlockEndX;
        nCurY = nBlockEndY;
    }
    else
    {
        // cursor position
        nCurX = aViewData.GetCurX();
        nCurY = aViewData.GetCurY();
    }

    ScSplitPos eWhich = aViewData.GetActivePart();
    ScHSplitPos eWhichX = WhichH( eWhich );
    ScVSplitPos eWhichY = WhichV( eWhich );

    auto nScrSizeY = SC_SIZE_NONE;
    if (comphelper::LibreOfficeKit::isActive() && aViewData.GetPageUpDownOffset() > 0) {
        nScrSizeY = ScViewData::ToPixel( aViewData.GetPageUpDownOffset(), aViewData.GetPPTX() );
    }

    SCCOL nPageX;
    SCROW nPageY;
    if (nMovX >= 0)
        nPageX = aViewData.CellsAtX( nCurX, 1, eWhichX ) * nMovX;
    else
        nPageX = aViewData.CellsAtX( nCurX, -1, eWhichX ) * nMovX;

    if (nMovY >= 0)
        nPageY = aViewData.CellsAtY( nCurY, 1, eWhichY, nScrSizeY ) * nMovY;
    else
        nPageY = aViewData.CellsAtY( nCurY, -1, eWhichY, nScrSizeY ) * nMovY;

    if (nMovX != 0 && nPageX == 0) nPageX = (nMovX>0) ? 1 : -1;
    if (nMovY != 0 && nPageY == 0) nPageY = (nMovY>0) ? 1 : -1;

    rPageX = nPageX;
    rPageY = nPageY;
}

void ScTabView::GetAreaMoveEndPosition(SCCOL nMovX, SCROW nMovY, ScFollowMode eMode,
                                       SCCOL& rAreaX, SCROW& rAreaY, ScFollowMode& rMode,
                                       bool bInteractiveByUser)
{
    SCCOL nNewX = -1;
    SCROW nNewY = -1;

    // current cursor position.
    SCCOL nCurX = aViewData.GetCurX();
    SCROW nCurY = aViewData.GetCurY();

    ScModule* pScModule = ScModule::get();
    bool bLegacyCellSelection = pScModule->GetInputOptions().GetLegacyCellSelection();
    bool bIncrementallyExpandToDocLimits(false);

    if (aViewData.IsRefMode())
    {
        nNewX = aViewData.GetRefEndX();
        nNewY = aViewData.GetRefEndY();
        nCurX = aViewData.GetRefStartX();
        nCurY = aViewData.GetRefStartY();
    }
    else if (IsBlockMode())
    {
        // block end position.
        nNewX = nBlockEndX;
        nNewY = nBlockEndY;
    }
    else
    {
        nNewX = nCurX;
        nNewY = nCurY;
        // cool#6931 on ctrl+[right/down] don't immediately leap to the far limits of the document when no more data,
        // instead jump a generous block of emptiness. Limit to direct interaction by user and the simple
        // case.
        bIncrementallyExpandToDocLimits = bInteractiveByUser && (nMovX == 1 || nMovY == 1) &&
                                          !bLegacyCellSelection && comphelper::LibreOfficeKit::isActive();
    }

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

    // FindAreaPos knows only -1 or 1 as direction
    SCCOL nVirtualX = bLegacyCellSelection ? nNewX : nCurX;
    SCROW nVirtualY = bLegacyCellSelection ? nNewY : nCurY;

    SCCOLROW i;
    if ( nMovX > 0 )
        for ( i=0; i<nMovX; i++ )
            rDoc.FindAreaPos( nNewX, nVirtualY, nTab,  SC_MOVE_RIGHT );
    if ( nMovX < 0 )
        for ( i=0; i<-nMovX; i++ )
            rDoc.FindAreaPos( nNewX, nVirtualY, nTab,  SC_MOVE_LEFT );
    if ( nMovY > 0 )
        for ( i=0; i<nMovY; i++ )
            rDoc.FindAreaPos( nVirtualX, nNewY, nTab,  SC_MOVE_DOWN );
    if ( nMovY < 0 )
        for ( i=0; i<-nMovY; i++ )
            rDoc.FindAreaPos( nVirtualX, nNewY, nTab,  SC_MOVE_UP );

    if (eMode==SC_FOLLOW_JUMP)                  // bottom right do not show too much grey
    {
        if (nMovX != 0 && nNewX == rDoc.MaxCol())
        {
            eMode = SC_FOLLOW_LINE;
            if (bIncrementallyExpandToDocLimits)
            {
                if (const ScTable* pTab = rDoc.FetchTable(nTab))
                {
                    if (!pTab->HasData(nNewX, nCurY))
                    {
                        SCCOL nLastUsedCol(0);
                        SCROW nLastUsedRow(0);
                        rDoc.GetPrintArea(nTab, nLastUsedCol, nLastUsedRow);
                        SCCOL nJumpFrom = std::max(nCurX, nLastUsedCol);
                        nNewX = ((nJumpFrom / 13) + 2) * 13 - 1;
                    }
                }
            }
        }
        if (nMovY != 0 && nNewY == rDoc.MaxRow())
        {
            eMode = SC_FOLLOW_LINE;
            if (bIncrementallyExpandToDocLimits)
            {
                if (const ScTable* pTab = rDoc.FetchTable(nTab))
                {
                    if (!pTab->HasData(nCurX, nNewY))
                    {
                        SCCOL nLastUsedCol(0);
                        SCROW nLastUsedRow(0);
                        rDoc.GetPrintArea(nTab, nLastUsedCol, nLastUsedRow);
                        SCROW nJumpFrom = std::max(nCurY, nLastUsedRow);
                        nNewY = ((nJumpFrom / 500) + 2) * 500 - 1;
                    }
                }
            }
        }
    }

    if (aViewData.IsRefMode())
    {
        rAreaX = nNewX - aViewData.GetRefEndX();
        rAreaY = nNewY - aViewData.GetRefEndY();
    }
    else if (IsBlockMode())
    {
        rAreaX = nNewX - nBlockEndX;
        rAreaY = nNewY - nBlockEndY;
    }
    else
    {
        rAreaX = nNewX - nCurX;
        rAreaY = nNewY - nCurY;
    }
    rMode = eMode;
}

void ScTabView::SkipCursorHorizontal(SCCOL& rCurX, SCROW& rCurY, SCCOL nOldX, SCCOL nMovX)
{
    ScDocument& rDoc = aViewData.GetDocument();
    SCTAB nTab = aViewData.CurrentTabForData();

    bool bSkipProtected = false, bSkipUnprotected = false;
    const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
    if (pProtect && pProtect->isProtected())
    {
        bSkipProtected   = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
    }

    bool bSkipCell = false;
    bool bHFlip = false;
    // If a number of last columns are hidden, search up to and including the first of them,
    // because after it nothing changes.
    SCCOL nMaxCol;
    if(rDoc.ColHidden(rDoc.MaxCol(), nTab, &nMaxCol))
        ++nMaxCol;
    else
        nMaxCol = rDoc.MaxCol();
    // Search also at least up to and including the first unallocated column (all unallocated columns
    // share a set of attrs).
    nMaxCol = std::max( nMaxCol, std::min<SCCOL>( rDoc.GetAllocatedColumnsCount(nTab) + 1, rDoc.MaxCol()));
    do
    {
        bSkipCell = rDoc.ColHidden(rCurX, nTab) || rDoc.IsHorOverlapped(rCurX, rCurY, nTab);
        if (bSkipProtected && !bSkipCell)
            bSkipCell = rDoc.HasAttrib(rCurX, rCurY, nTab, rCurX, rCurY, nTab, HasAttrFlags::Protected);
        if (bSkipUnprotected && !bSkipCell)
            bSkipCell = !rDoc.HasAttrib(rCurX, rCurY, nTab, rCurX, rCurY, nTab, HasAttrFlags::Protected);

        if (bSkipCell)
        {
            if (rCurX <= 0 || rCurX >= nMaxCol)
            {
                if (bHFlip)
                {
                    rCurX = nOldX;
                    bSkipCell = false;
                }
                else
                {
                    nMovX = -nMovX;
                    if (nMovX > 0)
                        ++rCurX;
                    else
                        --rCurX;
                    bHFlip = true;
                }
            }
            else
                if (nMovX > 0)
                    ++rCurX;
                else
                    --rCurX;
        }
    }
    while (bSkipCell);

    if (rDoc.IsVerOverlapped(rCurX, rCurY, nTab))
    {
        aViewData.SetOldCursor(rCurX, rCurY);
        while (rDoc.IsVerOverlapped(rCurX, rCurY, nTab))
            --rCurY;
    }
}

void ScTabView::SkipCursorVertical(SCCOL& rCurX, SCROW& rCurY, SCROW nOldY, SCROW nMovY)
{
    ScDocument& rDoc = aViewData.GetDocument();
    SCTAB nTab = aViewData.CurrentTabForData();

    bool bSkipProtected = false, bSkipUnprotected = false;
    const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
    if (pProtect && pProtect->isProtected())
    {
        bSkipProtected   = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS);
        bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS);
    }

    bool bSkipCell = false;
    bool bVFlip = false;
    // Avoid repeated calls to RowHidden(), IsVerOverlapped() and HasAttrib().
    SCROW nFirstSameHiddenRow = -1;
    SCROW nLastSameHiddenRow = -1;
    bool bRowHidden = false;
    SCROW nFirstSameIsVerOverlapped = -1;
    SCROW nLastSameIsVerOverlapped = -1;
    bool bIsVerOverlapped = false;
    SCROW nFirstSameHasAttribRow = -1;
    SCROW nLastSameHasAttribRow = -1;
    bool bHasAttribProtected = false;
    do
    {
        if( rCurY < nFirstSameHiddenRow || rCurY > nLastSameHiddenRow )
            bRowHidden = rDoc.RowHidden(rCurY, nTab, &nFirstSameHiddenRow, &nLastSameHiddenRow);
        bSkipCell = bRowHidden;
        if( !bSkipCell )
        {
            if( rCurY < nFirstSameIsVerOverlapped || rCurY > nLastSameIsVerOverlapped )
                bIsVerOverlapped = rDoc.IsVerOverlapped(rCurX, rCurY, nTab, &nFirstSameIsVerOverlapped, &nLastSameIsVerOverlapped);
            bSkipCell = bIsVerOverlapped;
        }
        if (bSkipProtected && !bSkipCell)
        {
            if( rCurY < nFirstSameHasAttribRow || rCurY > nLastSameHasAttribRow )
                bHasAttribProtected = rDoc.HasAttrib(rCurX, rCurY, nTab, HasAttrFlags::Protected,
                                                     &nFirstSameHasAttribRow, &nLastSameHasAttribRow);
            bSkipCell = bHasAttribProtected;
        }
        if (bSkipUnprotected && !bSkipCell)
        {
            if( rCurY < nFirstSameHasAttribRow || rCurY > nLastSameHasAttribRow )
                bHasAttribProtected = rDoc.HasAttrib(rCurX, rCurY, nTab, HasAttrFlags::Protected,
                                                     &nFirstSameHasAttribRow, &nLastSameHasAttribRow);
            bSkipCell = !bHasAttribProtected;
        }

        if (bSkipCell)
        {
            if (rCurY <= 0 || rCurY >= rDoc.MaxRow())
            {
                if (bVFlip)
                {
                    rCurY = nOldY;
                    bSkipCell = false;
                }
                else
                {
                    nMovY = -nMovY;
                    if (nMovY > 0)
                        ++rCurY;
                    else
                        --rCurY;
                    bVFlip = true;
                }
            }
            else
                if (nMovY > 0)
                    ++rCurY;
                else
                    --rCurY;
        }
    }
    while (bSkipCell);

    if (rDoc.IsHorOverlapped(rCurX, rCurY, nTab))
    {
        aViewData.SetOldCursor(rCurX, rCurY);
        while (rDoc.IsHorOverlapped(rCurX, rCurY, nTab))
            --rCurX;
    }
}

void ScTabView::ExpandBlock(SCCOL nMovX, SCROW nMovY, ScFollowMode eMode)
{
    if (!nMovX && !nMovY)
        // Nothing to do.  Bail out.
        return;

    ScTabViewShell* pViewShell = aViewData.GetViewShell();
    bool bRefInputMode = pViewShell && pViewShell->IsRefInputMode();
    if (bRefInputMode && !aViewData.IsRefMode())
        // initialize formula reference mode if it hasn't already.
        InitRefMode(aViewData.GetCurX(), aViewData.GetCurY(), aViewData.CurrentTabForData(), SC_REFTYPE_REF);

    ScDocument& rDoc = aViewData.GetDocument();

    if (aViewData.IsRefMode())
    {
        // formula reference mode

        SCCOL nNewX = aViewData.GetRefEndX();
        SCROW nNewY = aViewData.GetRefEndY();
        SCTAB nRefTab = aViewData.GetRefEndZ();

        moveRefByCell(nNewX, nNewY, nMovX, nMovY, nRefTab, rDoc);

        UpdateRef(nNewX, nNewY, nRefTab);
        SCCOL nTargetCol = nNewX;
        SCROW nTargetRow = nNewY;
        if (((aViewData.GetRefStartX() == 0) || (aViewData.GetRefStartY() == 0)) &&
            ((nNewX != rDoc.MaxCol()) || (nNewY != rDoc.MaxRow())))
        {
            // Row selection
            if ((aViewData.GetRefStartX() == 0) && (nNewX == rDoc.MaxCol()))
                nTargetCol = aViewData.GetCurX();
            // Column selection
            if ((aViewData.GetRefStartY() == 0) && (nNewY == rDoc.MaxRow()))
                nTargetRow = aViewData.GetCurY();
        }
        AlignToCursor(nTargetCol, nTargetRow, eMode);
    }
    else
    {
        // normal selection mode

        SCTAB nTab = aViewData.CurrentTabForData();
        SCCOL nOrigX = aViewData.GetCurX();
        SCROW nOrigY = aViewData.GetCurY();

        // Note that the origin position *never* moves during selection.

        if (!IsBlockMode())
        {
            InitBlockMode(nOrigX, nOrigY, nTab, true);
            const ScMergeAttr& rMergeAttr = rDoc.GetAttr(nOrigX, nOrigY, nTab, ATTR_MERGE);
            if (rMergeAttr.IsMerged())
            {
                nBlockEndX = nOrigX + rMergeAttr.GetColMerge() - 1;
                nBlockEndY = nOrigY + rMergeAttr.GetRowMerge() - 1;
            }
        }

        moveCursorToProperSide(nBlockEndX, nBlockEndY, nMovX, nMovY, nBlockStartX, nBlockStartY,
                               nTab, &rDoc);
        moveCursorByProtRule(nBlockEndX, nBlockEndY, nMovX, nMovY, nTab, &rDoc);
        checkBoundary(&rDoc, nBlockEndX, nBlockEndY);
        moveCursorByMergedCell(nBlockEndX, nBlockEndY, nMovX, nMovY, nBlockStartX, nBlockStartY,
                               nTab, &rDoc);
        checkBoundary(&rDoc, nBlockEndX, nBlockEndY);

        MarkCursor(nBlockEndX, nBlockEndY, nTab, false, false, true);

        // Check if the entire row(s) or column(s) are selected.
        ScSplitPos eActive = aViewData.GetActivePart();
        bool bRowSelected = (nBlockStartX == 0 && nBlockEndX == rDoc.MaxCol());
        bool bColSelected = (nBlockStartY == 0 && nBlockEndY == rDoc.MaxRow());
        SCCOL nAlignX = bRowSelected ? aViewData.GetPosX(WhichH(eActive)) : nBlockEndX;
        SCROW nAlignY = bColSelected ? aViewData.GetPosY(WhichV(eActive)) : nBlockEndY;
        AlignToCursor(nAlignX, nAlignY, eMode);

        SelectionChanged();
    }
}

void ScTabView::ExpandBlockPage(SCCOL nMovX, SCROW nMovY)
{
    SCCOL nPageX;
    SCROW nPageY;
    GetPageMoveEndPosition(nMovX, nMovY, nPageX, nPageY);
    ExpandBlock(nPageX, nPageY, SC_FOLLOW_FIX);
}

void ScTabView::ExpandBlockArea(SCCOL nMovX, SCROW nMovY)
{
    SCCOL nAreaX;
    SCROW nAreaY;
    ScFollowMode eMode;
    GetAreaMoveEndPosition(nMovX, nMovY, SC_FOLLOW_JUMP, nAreaX, nAreaY, eMode);
    ExpandBlock(nAreaX, nAreaY, eMode);
}

void ScTabView::UpdateCopySourceOverlay()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if (pWin && pWin->IsVisible())
            pWin->UpdateCopySourceOverlay();
}

void ScTabView::UpdateSelectionOverlay()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if ( pWin && pWin->IsVisible() )
            pWin->UpdateSelectionOverlay();
}

void ScTabView::UpdateHighlightOverlay()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if ( pWin && pWin->IsVisible() )
            pWin->UpdateHighlightOverlay();
}

void ScTabView::UpdateShrinkOverlay()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if ( pWin && pWin->IsVisible() )
            pWin->UpdateShrinkOverlay();
}

void ScTabView::UpdateAllOverlays()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if ( pWin && pWin->IsVisible() )
            pWin->UpdateAllOverlays();
}

//!
//! divide PaintBlock into two methods: RepaintBlock and RemoveBlock or similar
//!

void ScTabView::PaintBlock( bool bReset )
{
    ScMarkData& rMark = aViewData.GetMarkData();
    SCTAB nTab = aViewData.CurrentTabForData();
    bool bMulti = rMark.IsMultiMarked();
    if (!(rMark.IsMarked() || bMulti))
        return;

    ScRange aMarkRange;
    HideAllCursors();
    if (bMulti)
    {
        bool bFlag = rMark.GetMarkingFlag();
        rMark.SetMarking(false);
        rMark.MarkToMulti();
        aMarkRange = rMark.GetMultiMarkArea();
        rMark.MarkToSimple();
        rMark.SetMarking(bFlag);
    }
    else
        aMarkRange = rMark.GetMarkArea();

    nBlockStartX = aMarkRange.aStart.Col();
    nBlockStartY = aMarkRange.aStart.Row();
    nBlockStartZ = aMarkRange.aStart.Tab();
    nBlockEndX = aMarkRange.aEnd.Col();
    nBlockEndY = aMarkRange.aEnd.Row();
    nBlockEndZ = aMarkRange.aEnd.Tab();

    bool bDidReset = false;

    if ( nTab>=nBlockStartZ && nTab<=nBlockEndZ )
    {
        if ( bReset )
        {
            // Inverting when deleting only on active View
            if ( aViewData.IsActive() )
            {
                rMark.ResetMark();
                UpdateSelectionOverlay();
                bDidReset = true;
            }
        }
        else
            PaintMarks( nBlockStartX, nBlockStartY, nBlockEndX, nBlockEndY );
    }

    if ( bReset && !bDidReset )
        rMark.ResetMark();

    ShowAllCursors();
}

void ScTabView::SelectAll( bool bContinue )
{
    ScDocument& rDoc = aViewData.GetDocument();
    ScMarkData& rMark = aViewData.GetMarkData();
    SCTAB nTab = aViewData.CurrentTabForData();

    if (rMark.IsMarked())
    {
        if ( rMark.GetMarkArea() == ScRange( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab ) )
            return;
    }

    DoneBlockMode( bContinue );
    InitBlockMode( 0,0,nTab );
    MarkCursor( rDoc.MaxCol(),rDoc.MaxRow(),nTab );

    SelectionChanged();
}

void ScTabView::SelectAllTables()
{
    ScDocument& rDoc = aViewData.GetDocument();
    ScMarkData& rMark = aViewData.GetMarkData();
    SCTAB nCount = rDoc.GetTableCount();

    if (nCount>1)
    {
        for (SCTAB i=0; i<nCount; i++)
            rMark.SelectTable( i, true );

        aViewData.GetDocShell()->PostPaintExtras();
        SfxBindings& rBind = aViewData.GetBindings();
        rBind.Invalidate( FID_FILL_TAB );
        rBind.Invalidate( FID_TAB_DESELECTALL );
    }
}

void ScTabView::DeselectAllTables()
{
    ScDocument& rDoc = aViewData.GetDocument();
    ScMarkData& rMark = aViewData.GetMarkData();
    SCTAB nTab = aViewData.CurrentTabForData();
    SCTAB nCount = rDoc.GetTableCount();

    for (SCTAB i=0; i<nCount; i++)
        rMark.SelectTable( i, ( i == nTab ) );

    aViewData.GetDocShell()->PostPaintExtras();
    SfxBindings& rBind = aViewData.GetBindings();
    rBind.Invalidate( FID_FILL_TAB );
    rBind.Invalidate( FID_TAB_DESELECTALL );
}

static bool lcl_FitsInWindow( double fScaleX, double fScaleY, sal_uInt16 nZoom,
                        tools::Long nWindowX, tools::Long nWindowY, const ScDocument* pDoc, SCTAB nTab,
                        SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
                        SCCOL nFixPosX, SCROW nFixPosY )
{
    double fZoomFactor = static_cast<double>(Fraction(nZoom,100));
    fScaleX *= fZoomFactor;
    fScaleY *= fZoomFactor;

    tools::Long nBlockX = 0;
    SCCOL nCol;
    for (nCol=0; nCol<nFixPosX; nCol++)
    {
        //  for frozen panes, add both parts
        sal_uInt16 nColTwips = pDoc->GetColWidth( nCol, nTab );
        if (nColTwips)
        {
            nBlockX += static_cast<tools::Long>(nColTwips * fScaleX);
            if (nBlockX > nWindowX)
                return false;
        }
    }
    for (nCol=nStartCol; nCol<=nEndCol; nCol++)
    {
        sal_uInt16 nColTwips = pDoc->GetColWidth( nCol, nTab );
        if (nColTwips)
        {
            nBlockX += static_cast<tools::Long>(nColTwips * fScaleX);
            if (nBlockX > nWindowX)
                return false;
        }
    }

    tools::Long nBlockY = 0;
    for (SCROW nRow = 0; nRow <= nFixPosY-1; ++nRow)
    {
        if (pDoc->RowHidden(nRow, nTab))
            continue;

        //  for frozen panes, add both parts
        sal_uInt16 nRowTwips = pDoc->GetRowHeight(nRow, nTab);
        if (nRowTwips)
        {
            nBlockY += static_cast<tools::Long>(nRowTwips * fScaleY);
            if (nBlockY > nWindowY)
                return false;
        }
    }
    for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
    {
        sal_uInt16 nRowTwips = pDoc->GetRowHeight(nRow, nTab);
        if (nRowTwips)
        {
            nBlockY += static_cast<tools::Long>(nRowTwips * fScaleY);
            if (nBlockY > nWindowY)
                return false;
        }
    }

    return true;
}

sal_uInt16 ScTabView::CalcZoom( SvxZoomType eType, sal_uInt16 nOldZoom )
{
    sal_uInt16 nZoom = 100;

    switch ( eType )
    {
        case SvxZoomType::PERCENT: // rZoom is no particular percent value
            nZoom = nOldZoom;
            break;

        case SvxZoomType::OPTIMAL:  // nZoom corresponds to the optimal size
            {
                ScMarkData& rMark = aViewData.GetMarkData();
                ScDocument& rDoc = aViewData.GetDocument();

                if (!rMark.IsMarked() && !rMark.IsMultiMarked())
                    nZoom = 100;                // nothing selected
                else
                {
                    SCTAB   nTab = aViewData.CurrentTabForData();
                    ScRange aMarkRange;
                    if ( aViewData.GetSimpleArea( aMarkRange ) != SC_MARK_SIMPLE )
                        aMarkRange = rMark.GetMultiMarkArea();

                    SCCOL   nStartCol = aMarkRange.aStart.Col();
                    SCROW   nStartRow = aMarkRange.aStart.Row();
                    SCTAB   nStartTab = aMarkRange.aStart.Tab();
                    SCCOL   nEndCol = aMarkRange.aEnd.Col();
                    SCROW   nEndRow = aMarkRange.aEnd.Row();
                    SCTAB   nEndTab = aMarkRange.aEnd.Tab();

                    if ( nTab < nStartTab && nTab > nEndTab )
                        nTab = nStartTab;

                    ScSplitPos eUsedPart = aViewData.GetActivePart();

                    SCCOL nFixPosX = 0;
                    SCROW nFixPosY = 0;
                    if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX )
                    {
                        //  use right part
                        eUsedPart = (WhichV(eUsedPart)==SC_SPLIT_TOP) ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT;
                        nFixPosX = aViewData.GetFixPosX();
                        if ( nStartCol < nFixPosX )
                            nStartCol = nFixPosX;
                    }
                    if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX )
                    {
                        //  use bottom part
                        eUsedPart = (WhichH(eUsedPart)==SC_SPLIT_LEFT) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT;
                        nFixPosY = aViewData.GetFixPosY();
                        if ( nStartRow < nFixPosY )
                            nStartRow = nFixPosY;
                    }

                    if (pGridWin[eUsedPart])
                    {
                        //  Because scale is rounded to pixels, the only reliable way to find
                        //  the right scale is to check if a zoom fits

                        Size aWinSize = pGridWin[eUsedPart]->GetOutputSizePixel();

                        //  for frozen panes, use sum of both parts for calculation

                        if ( nFixPosX != 0 )
                            aWinSize.AdjustWidth(GetGridWidth( SC_SPLIT_LEFT ) );
                        if ( nFixPosY != 0 )
                            aWinSize.AdjustHeight(GetGridHeight( SC_SPLIT_TOP ) );

                        ScDocShell* pDocSh = aViewData.GetDocShell();
                        double nPPTX = ScGlobal::nScreenPPTX / pDocSh->GetOutputFactor();
                        double nPPTY = ScGlobal::nScreenPPTY;

                        sal_uInt16 nMin = MINZOOM;
                        sal_uInt16 nMax = MAXZOOM;
                        while ( nMax > nMin )
                        {
                            sal_uInt16 nTest = (nMin+nMax+1)/2;
                            if ( lcl_FitsInWindow(
                                        nPPTX, nPPTY, nTest, aWinSize.Width(), aWinSize.Height(),
                                        &rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow,
                                        nFixPosX, nFixPosY ) )
                                nMin = nTest;
                            else
                                nMax = nTest-1;
                        }
                        OSL_ENSURE( nMin == nMax, "Nesting is wrong" );
                        nZoom = nMin;

                        if ( nZoom != nOldZoom )
                        {
                            // scroll to block only in active split part
                            // (the part for which the size was calculated)

                            if ( nStartCol <= nEndCol )
                                aViewData.SetPosX( WhichH(eUsedPart), nStartCol );
                            if ( nStartRow <= nEndRow )
                                aViewData.SetPosY( WhichV(eUsedPart), nStartRow );
                        }
                    }
                }
            }
            break;

            case SvxZoomType::WHOLEPAGE:    // nZoom corresponds to the whole page or
            case SvxZoomType::PAGEWIDTH:    // nZoom corresponds to the page width
                {
                    SCTAB               nCurTab     = aViewData.CurrentTabForData();
                    ScDocument&         rDoc        = aViewData.GetDocument();
                    ScStyleSheetPool*   pStylePool  = rDoc.GetStyleSheetPool();
                    SfxStyleSheetBase*  pStyleSheet =
                                            pStylePool->Find( rDoc.GetPageStyle( nCurTab ),
                                                              SfxStyleFamily::Page );

                    OSL_ENSURE( pStyleSheet, "PageStyle not found :-/" );

                    if ( pStyleSheet )
                    {
                        ScPrintFunc aPrintFunc( *aViewData.GetDocShell(),
                                                aViewData.GetViewShell()->GetPrinter(true),
                                                nCurTab );

                        Size aPageSize = aPrintFunc.GetDataSize();

                        //  use the size of the largest GridWin for normal split,
                        //  or both combined for frozen panes, with the (document) size
                        //  of the frozen part added to the page size
                        //  (with frozen panes, the size of the individual parts
                        //  depends on the scale that is to be calculated)

                        if (!pGridWin[SC_SPLIT_BOTTOMLEFT])
                            return nZoom;

                        Size aWinSize = pGridWin[SC_SPLIT_BOTTOMLEFT]->GetOutputSizePixel();
                        ScSplitMode eHMode = aViewData.GetHSplitMode();
                        if ( eHMode != SC_SPLIT_NONE && pGridWin[SC_SPLIT_BOTTOMRIGHT] )
                        {
                            tools::Long nOtherWidth = pGridWin[SC_SPLIT_BOTTOMRIGHT]->
                                                        GetOutputSizePixel().Width();
                            if ( eHMode == SC_SPLIT_FIX )
                            {
                                aWinSize.AdjustWidth(nOtherWidth );
                                for ( SCCOL nCol = aViewData.GetPosX(SC_SPLIT_LEFT);
                                        nCol < aViewData.GetFixPosX(); nCol++ )
                                    aPageSize.AdjustWidth(rDoc.GetColWidth( nCol, nCurTab ) );
                            }
                            else if ( nOtherWidth > aWinSize.Width() )
                                aWinSize.setWidth( nOtherWidth );
                        }
                        ScSplitMode eVMode = aViewData.GetVSplitMode();
                        if ( eVMode != SC_SPLIT_NONE && pGridWin[SC_SPLIT_TOPLEFT] )
                        {
                            tools::Long nOtherHeight = pGridWin[SC_SPLIT_TOPLEFT]->
                                                        GetOutputSizePixel().Height();
                            if ( eVMode == SC_SPLIT_FIX )
                            {
                                aWinSize.AdjustHeight(nOtherHeight );
                                aPageSize.AdjustHeight(rDoc.GetRowHeight(
                                        aViewData.GetPosY(SC_SPLIT_TOP),
                                        aViewData.GetFixPosY()-1, nCurTab) );
                            }
                            else if ( nOtherHeight > aWinSize.Height() )
                                aWinSize.setHeight( nOtherHeight );
                        }

                        double nPPTX = ScGlobal::nScreenPPTX / aViewData.GetDocShell()->GetOutputFactor();
                        double nPPTY = ScGlobal::nScreenPPTY;

                        tools::Long nZoomX = static_cast<tools::Long>( aWinSize.Width() * 100 /
                                               ( aPageSize.Width() * nPPTX ) );
                        tools::Long nZoomY = static_cast<tools::Long>( aWinSize.Height() * 100 /
                                               ( aPageSize.Height() * nPPTY ) );

                        if (nZoomX > 0)
                            nZoom = static_cast<sal_uInt16>(nZoomX);

                        if (eType == SvxZoomType::WHOLEPAGE && nZoomY > 0 && nZoomY < nZoom)
                            nZoom = static_cast<sal_uInt16>(nZoomY);
                    }
                }
                break;

        default:
            OSL_FAIL("Unknown Zoom-Revision");
    }

    return nZoom;
}

// is called for instance when the view window is shifted:

void ScTabView::StopMarking()
{
    ScSplitPos eActive = aViewData.GetActivePart();
    if (pGridWin[eActive])
        pGridWin[eActive]->StopMarking();

    ScHSplitPos eH = WhichH(eActive);
    if (pColBar[eH])
        pColBar[eH]->StopMarking();

    ScVSplitPos eV = WhichV(eActive);
    if (pRowBar[eV])
        pRowBar[eV]->StopMarking();
}

void ScTabView::HideNoteOverlay()
{
    for (VclPtr<ScGridWindow> & pWin : pGridWin)
        if (pWin && pWin->IsVisible())
            pWin->HideNoteOverlay();
}

void ScTabView::MakeDrawLayer()
{
    if (pDrawView)
        return;

    aViewData.GetDocShell()->MakeDrawLayer();

    // pDrawView is set per Notify
    OSL_ENSURE(pDrawView,"ScTabView::MakeDrawLayer does not work");

    for(VclPtr<ScGridWindow> & pWin : pGridWin)
    {
        if(pWin)
        {
            pWin->DrawLayerCreated();
        }
    }
}

IMPL_STATIC_LINK_NOARG(ScTabView, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*)
{
    return GetpApp();
}

void ScTabView::ErrorMessage(TranslateId pGlobStrId)
{
    if (ScModule::get()->IsInExecuteDrop())
    {
        // #i28468# don't show error message when called from Drag&Drop, silently abort instead
        return;
    }

    StopMarking();      // if called by Focus from MouseButtonDown

    weld::Window* pParent = aViewData.GetDialogParent();
    weld::WaitObject aWaitOff( pParent );
    bool bFocus = pParent && pParent->has_focus();

    if (pGlobStrId && pGlobStrId == STR_PROTECTIONERR)
    {
        if (aViewData.GetDocShell()->IsReadOnly())
        {
            pGlobStrId = STR_READONLYERR;
        }
    }

    m_xMessageBox.reset(Application::CreateMessageDialog(pParent,
                                                         VclMessageType::Info, VclButtonsType::Ok,
                                                         ScResId(pGlobStrId)));

    if (comphelper::LibreOfficeKit::isActive())
        m_xMessageBox->SetInstallLOKNotifierHdl(LINK(this, ScTabView, InstallLOKNotifierHdl));

    weld::Window* pGrabOnClose = bFocus ? pParent : nullptr;
    m_xMessageBox->runAsync(m_xMessageBox, [this, pGrabOnClose](sal_Int32 /*nResult*/) {
        m_xMessageBox.reset();
        if (pGrabOnClose)
            pGrabOnClose->grab_focus();
    });
}

void ScTabView::UpdatePageBreakData( bool bForcePaint )
{
    std::unique_ptr<ScPageBreakData> pNewData;

    if (aViewData.IsPagebreakMode())
    {
        ScDocShell* pDocSh = aViewData.GetDocShell();
        ScDocument& rDoc = pDocSh->GetDocument();
        SCTAB nTab = aViewData.CurrentTabForData();

        sal_uInt16 nCount = rDoc.GetPrintRangeCount(nTab);
        if (!nCount)
            nCount = 1;
        pNewData.reset( new ScPageBreakData(nCount) );

        ScPrintFunc aPrintFunc( *pDocSh, pDocSh->GetPrinter(), nTab, 0,0,nullptr, nullptr, pNewData.get() );
        // ScPrintFunc fills the PageBreakData in ctor
        if ( nCount > 1 )
        {
            aPrintFunc.ResetBreaks(nTab);
            pNewData->AddPages();
        }

        // print area changed?
        if ( bForcePaint || ( pPageBreakData && !( *pPageBreakData == *pNewData ) ) )
            PaintGrid();
    }

    pPageBreakData = std::move(pNewData);
}

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