sticky end col/row anchor for range references, tdf#92779

In range references referring more than one column/row where the end
col/row points to the maximum column or row number that col/row is not
decremented when the range is shrunken. Incrementing an end col/row is
not done past the maximum column or row number, so such references do
not yield #REF! errors anymore. This is also done in named expressions
if the end col/row is an absolute reference.

Change-Id: Iafa2d62abd3e816a1c56c3166af92807e55b75ce
diff --git a/sc/inc/address.hxx b/sc/inc/address.hxx
index aa700d9..4bf5ee5 100644
--- a/sc/inc/address.hxx
+++ b/sc/inc/address.hxx
@@ -542,6 +542,21 @@ public:

    ScRange Intersection( const ScRange& rOther ) const;

    /// If maximum end column should not be adapted during reference update.
    inline bool IsEndColSticky() const;
    /// If maximum end row should not be adapted during reference update.
    inline bool IsEndRowSticky() const;

    /** Increment or decrement end column unless sticky or until it becomes
        sticky. Checks if the range encompasses at least two columns so should
        be called before adjusting the start column. */
    void IncEndColSticky( SCsCOL nDelta );

    /** Increment or decrement end row unless sticky or until it becomes
        sticky. Checks if the range encompasses at least two rows so should
        be called before adjusting the start row. */
    void IncEndRowSticky( SCsROW nDelta );

    inline bool operator==( const ScRange& rRange ) const;
    inline bool operator!=( const ScRange& rRange ) const;
    inline bool operator<( const ScRange& rRange ) const;
@@ -562,6 +577,18 @@ inline void ScRange::GetVars( SCCOL& nCol1, SCROW& nRow1, SCTAB& nTab1,
    aEnd.GetVars( nCol2, nRow2, nTab2 );
}

inline bool ScRange::IsEndColSticky() const
{
    // Only in an actual column range, i.e. not if both columns are MAXCOL.
    return aEnd.Col() == MAXCOL && aStart.Col() < aEnd.Col();
}

inline bool ScRange::IsEndRowSticky() const
{
    // Only in an actual row range, i.e. not if both rows are MAXROW.
    return aEnd.Row() == MAXROW && aStart.Row() < aEnd.Row();
}

inline bool ScRange::operator==( const ScRange& rRange ) const
{
    return ( (aStart == rRange.aStart) && (aEnd == rRange.aEnd) );
diff --git a/sc/inc/refdata.hxx b/sc/inc/refdata.hxx
index b96acb7..5f3e762 100644
--- a/sc/inc/refdata.hxx
+++ b/sc/inc/refdata.hxx
@@ -180,6 +180,16 @@ struct ScComplexRefData
    ScComplexRefData& Extend( const ScSingleRefData & rRef, const ScAddress & rPos );
    ScComplexRefData& Extend( const ScComplexRefData & rRef, const ScAddress & rPos );

    /** Increment or decrement end column unless or until sticky.
        @see ScRange::IncEndColSticky()
        @return TRUE if changed. */
    bool IncEndColSticky( SCCOL nDelta, const ScAddress& rPos );

    /** Increment or decrement end row unless or until sticky.
        @see ScRange::IncEndRowSticky()
        @return TRUE if changed. */
    bool IncEndRowSticky( SCROW nDelta, const ScAddress& rPos );

#if DEBUG_FORMULA_COMPILER
    void Dump( int nIndent = 0 ) const;
#endif
diff --git a/sc/source/core/tool/address.cxx b/sc/source/core/tool/address.cxx
index f69a822..b7f9a5b 100644
--- a/sc/source/core/tool/address.cxx
+++ b/sc/source/core/tool/address.cxx
@@ -2125,13 +2125,67 @@ bool ScAddress::Move( SCsCOL dx, SCsROW dy, SCsTAB dz, ScDocument* pDoc )

bool ScRange::Move( SCsCOL dx, SCsROW dy, SCsTAB dz, ScDocument* pDoc )
{
    bool bColRange = (aStart.Col() < aEnd.Col());
    bool bRowRange = (aStart.Row() < aEnd.Row());
    if (dy && aStart.Row() == 0 && aEnd.Row() == MAXROW)
        dy = 0;     // Entire column not to be moved.
    if (dx && aStart.Col() == 0 && aEnd.Col() == MAXCOL)
        dx = 0;     // Entire row not to be moved.
    bool b = aStart.Move( dx, dy, dz, pDoc );
    b &= aEnd.Move( dx, dy, dz, pDoc );
    return b;
    bool b1 = aStart.Move( dx, dy, dz, pDoc );
    if (dx && aEnd.Col() == MAXCOL)
        dx = 0;     // End column sticky.
    if (dy && aEnd.Row() == MAXROW)
        dy = 0;     // End row sticky.
    SCTAB nOldTab = aEnd.Tab();
    bool b2 = aEnd.Move( dx, dy, dz, pDoc );
    if (!b2)
    {
        // End column or row of a range may have become sticky.
        bColRange = (!dx || (bColRange && aEnd.Col() == MAXCOL));
        bRowRange = (!dy || (bRowRange && aEnd.Row() == MAXROW));
        b2 = bColRange && bRowRange && (aEnd.Tab() - nOldTab == dz);
    }
    return b1 && b2;
}

void ScRange::IncEndColSticky( SCsCOL nDelta )
{
    SCCOL nCol = aEnd.Col();
    if (aStart.Col() >= nCol)
    {
        // Less than two columns => not sticky.
        aEnd.IncCol( nDelta);
        return;
    }

    if (nCol == MAXCOL)
        // already sticky
        return;

    if (nCol < MAXCOL)
        aEnd.SetCol( ::std::min( static_cast<SCCOL>(nCol + nDelta), MAXCOL));
    else
        aEnd.IncCol( nDelta);   // was greater than MAXCOL, caller should know..
}

void ScRange::IncEndRowSticky( SCsROW nDelta )
{
    SCROW nRow = aEnd.Row();
    if (aStart.Row() >= nRow)
    {
        // Less than two rows => not sticky.
        aEnd.IncRow( nDelta);
        return;
    }

    if (nRow == MAXROW)
        // already sticky
        return;

    if (nRow < MAXROW)
        aEnd.SetRow( ::std::min( static_cast<SCROW>(nRow + nDelta), MAXROW));
    else
        aEnd.IncRow( nDelta);   // was greater than MAXROW, caller should know..
}

OUString ScAddress::GetColRowString( bool bAbsolute,
diff --git a/sc/source/core/tool/refdata.cxx b/sc/source/core/tool/refdata.cxx
index 0878cb1..9811c08 100644
--- a/sc/source/core/tool/refdata.cxx
+++ b/sc/source/core/tool/refdata.cxx
@@ -478,6 +478,64 @@ void ScComplexRefData::PutInOrder( const ScAddress& rPos )
    ScSingleRefData::PutInOrder( Ref1, Ref2, rPos);
}

bool ScComplexRefData::IncEndColSticky( SCCOL nDelta, const ScAddress& rPos )
{
    SCCOL nCol1 = Ref1.IsColRel() ? Ref1.Col() + rPos.Col() : Ref1.Col();
    SCCOL nCol2 = Ref2.IsColRel() ? Ref2.Col() + rPos.Col() : Ref2.Col();
    if (nCol1 >= nCol2)
    {
        // Less than two columns => not sticky.
        Ref2.IncCol( nDelta);
        return true;
    }

    if (nCol2 == MAXCOL)
        // already sticky
        return false;

    if (nCol2 < MAXCOL)
    {
        SCCOL nCol = ::std::min( static_cast<SCCOL>(nCol2 + nDelta), MAXCOL);
        if (Ref2.IsColRel())
            Ref2.SetRelCol( nCol - rPos.Col());
        else
            Ref2.SetAbsCol( nCol);
    }
    else
        Ref2.IncCol( nDelta);   // was greater than MAXCOL, caller should know..

    return true;
}

bool ScComplexRefData::IncEndRowSticky( SCROW nDelta, const ScAddress& rPos )
{
    SCROW nRow1 = Ref1.IsRowRel() ? Ref1.Row() + rPos.Row() : Ref1.Row();
    SCROW nRow2 = Ref2.IsRowRel() ? Ref2.Row() + rPos.Row() : Ref2.Row();
    if (nRow1 >= nRow2)
    {
        // Less than two rows => not sticky.
        Ref2.IncRow( nDelta);
        return true;
    }

    if (nRow2 == MAXROW)
        // already sticky
        return false;

    if (nRow2 < MAXROW)
    {
        SCROW nRow = ::std::min( static_cast<SCROW>(nRow2 + nDelta), MAXROW);
        if (Ref2.IsRowRel())
            Ref2.SetRelRow( nRow - rPos.Row());
        else
            Ref2.SetAbsRow( nRow);
    }
    else
        Ref2.IncRow( nDelta);   // was greater than MAXROW, caller should know..

    return true;
}

#if DEBUG_FORMULA_COMPILER
void ScComplexRefData::Dump( int nIndent ) const
{
diff --git a/sc/source/core/tool/refupdat.cxx b/sc/source/core/tool/refupdat.cxx
index 8ca5373..602a636 100644
--- a/sc/source/core/tool/refupdat.cxx
+++ b/sc/source/core/tool/refupdat.cxx
@@ -231,6 +231,13 @@ ScRefUpdateRes ScRefUpdate::Update( ScDocument* pDoc, UpdateRefMode eUpdateRefMo
                theCol1 = oldCol1;
                theCol2 = oldCol2;
            }
            else if (oldCol2 == MAXCOL && oldCol1 < MAXCOL)
            {
                // End was sticky, but start may have been moved. Only on range.
                theCol2 = oldCol2;
            }
            // Else, if (bCut2 && theCol2 == MAXCOL) then end becomes sticky,
            // but currently there's nothing to do.
        }
        if ( nDy && (theCol1 >= nCol1) && (theCol2 <= nCol2) &&
                    (theTab1 >= nTab1) && (theTab2 <= nTab2))
@@ -256,6 +263,13 @@ ScRefUpdateRes ScRefUpdate::Update( ScDocument* pDoc, UpdateRefMode eUpdateRefMo
                theRow1 = oldRow1;
                theRow2 = oldRow2;
            }
            else if (oldRow2 == MAXROW && oldRow1 < MAXROW)
            {
                // End was sticky, but start may have been moved. Only on range.
                theRow2 = oldRow2;
            }
            // Else, if (bCut2 && theRow2 == MAXROW) then end becomes sticky,
            // but currently there's nothing to do.
        }
        if ( nDz && (theCol1 >= nCol1) && (theCol2 <= nCol2) &&
                    (theRow1 >= nRow1) && (theRow2 <= nRow2) )
diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx
index 41be86a..49bbe2e 100644
--- a/sc/source/core/tool/token.cxx
+++ b/sc/source/core/tool/token.cxx
@@ -2599,22 +2599,30 @@ bool shrinkRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const Sc
                // The reference range is truncated on the left.
                SCCOL nOffset = rDeletedRange.aStart.Col() - rRefRange.aStart.Col();
                SCCOL nDelta = rRefRange.aStart.Col() - rDeletedRange.aEnd.Col() - 1;
                rRefRange.IncEndColSticky(nDelta+nOffset);
                rRefRange.aStart.IncCol(nOffset);
                rRefRange.aEnd.IncCol(nDelta+nOffset);
            }
        }
        else if (rDeletedRange.aEnd.Col() < rRefRange.aEnd.Col())
        {
            if (rRefRange.IsEndColSticky())
                // Sticky end not affected.
                return false;

            // Reference is deleted in the middle. Move the last column
            // position to the left.
            SCCOL nDelta = rDeletedRange.aStart.Col() - rDeletedRange.aEnd.Col() - 1;
            rRefRange.aEnd.IncCol(nDelta);
            rRefRange.IncEndColSticky(nDelta);
        }
        else
        {
            if (rRefRange.IsEndColSticky())
                // Sticky end not affected.
                return false;

            // The reference range is truncated on the right.
            SCCOL nDelta = rDeletedRange.aStart.Col() - rRefRange.aEnd.Col() - 1;
            rRefRange.aEnd.IncCol(nDelta);
            rRefRange.IncEndColSticky(nDelta);
        }
        return true;
    }
@@ -2642,22 +2650,30 @@ bool shrinkRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const Sc
                // The reference range is truncated on the top.
                SCCOL nOffset = rDeletedRange.aStart.Row() - rRefRange.aStart.Row();
                SCCOL nDelta = rRefRange.aStart.Row() - rDeletedRange.aEnd.Row() - 1;
                rRefRange.IncEndRowSticky(nDelta+nOffset);
                rRefRange.aStart.IncRow(nOffset);
                rRefRange.aEnd.IncRow(nDelta+nOffset);
            }
        }
        else if (rDeletedRange.aEnd.Row() < rRefRange.aEnd.Row())
        {
            if (rRefRange.IsEndRowSticky())
                // Sticky end not affected.
                return false;

            // Reference is deleted in the middle. Move the last row
            // position upward.
            SCCOL nDelta = rDeletedRange.aStart.Row() - rDeletedRange.aEnd.Row() - 1;
            rRefRange.aEnd.IncRow(nDelta);
            rRefRange.IncEndRowSticky(nDelta);
        }
        else
        {
            if (rRefRange.IsEndRowSticky())
                // Sticky end not affected.
                return false;

            // The reference range is truncated on the bottom.
            SCCOL nDelta = rDeletedRange.aStart.Row() - rRefRange.aEnd.Row() - 1;
            rRefRange.aEnd.IncRow(nDelta);
            rRefRange.IncEndRowSticky(nDelta);
        }
        return true;
    }
@@ -2695,9 +2711,13 @@ bool expandRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const Sc
                return false;
        }

        if (rRefRange.IsEndColSticky())
            // Sticky end not affected.
            return false;

        // Move the last column position to the right.
        SCCOL nDelta = rSelectedRange.aEnd.Col() - rSelectedRange.aStart.Col() + 1;
        rRefRange.aEnd.IncCol(nDelta);
        rRefRange.IncEndColSticky(nDelta);
        return true;
    }
    else if (rCxt.mnRowDelta > 0)
@@ -2724,9 +2744,13 @@ bool expandRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const Sc
                return false;
        }

        if (rRefRange.IsEndRowSticky())
            // Sticky end not affected.
            return false;

        // Move the last row position down.
        SCROW nDelta = rSelectedRange.aEnd.Row() - rSelectedRange.aStart.Row() + 1;
        rRefRange.aEnd.IncRow(nDelta);
        rRefRange.IncEndRowSticky(nDelta);
        return true;
    }
    return false;
@@ -2767,9 +2791,13 @@ bool expandRangeByEdge( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, co
            // Selected range is not immediately adjacent. Bail out.
            return false;

        if (rRefRange.IsEndColSticky())
            // Sticky end not affected.
            return false;

        // Move the last column position to the right.
        SCCOL nDelta = rSelectedRange.aEnd.Col() - rSelectedRange.aStart.Col() + 1;
        rRefRange.aEnd.IncCol(nDelta);
        rRefRange.IncEndColSticky(nDelta);
        return true;
    }
    else if (rCxt.mnRowDelta > 0)
@@ -2790,9 +2818,13 @@ bool expandRangeByEdge( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, co
            // Selected range is not immediately adjacent. Bail out.
            return false;

        if (rRefRange.IsEndRowSticky())
            // Sticky end not affected.
            return false;

        // Move the last row position down.
        SCROW nDelta = rSelectedRange.aEnd.Row() - rSelectedRange.aStart.Row() + 1;
        rRefRange.aEnd.IncRow(nDelta);
        rRefRange.IncEndRowSticky(nDelta);
        return true;
    }

@@ -3272,7 +3304,8 @@ void ScTokenArray::MoveReferenceRowReorder( const ScAddress& rPos, SCTAB nTab, S
namespace {

bool adjustSingleRefInName(
    ScSingleRefData& rRef, const sc::RefUpdateContext& rCxt, const ScAddress& rPos )
    ScSingleRefData& rRef, const sc::RefUpdateContext& rCxt, const ScAddress& rPos,
    ScComplexRefData* pEndOfComplex )
{
    ScAddress aAbs = rRef.toAbs(rPos);

@@ -3292,8 +3325,16 @@ bool adjustSingleRefInName(
        // Adjust absolute column reference.
        if (rCxt.maRange.aStart.Col() <= rRef.Col() && rRef.Col() <= rCxt.maRange.aEnd.Col())
        {
            rRef.IncCol(rCxt.mnColDelta);
            bChanged = true;
            if (pEndOfComplex)
            {
                if (pEndOfComplex->IncEndColSticky( rCxt.mnColDelta, rPos))
                    bChanged = true;
            }
            else
            {
                rRef.IncCol(rCxt.mnColDelta);
                bChanged = true;
            }
        }
    }

@@ -3302,8 +3343,16 @@ bool adjustSingleRefInName(
        // Adjust absolute row reference.
        if (rCxt.maRange.aStart.Row() <= rRef.Row() && rRef.Row() <= rCxt.maRange.aEnd.Row())
        {
            rRef.IncRow(rCxt.mnRowDelta);
            bChanged = true;
            if (pEndOfComplex)
            {
                if (pEndOfComplex->IncEndRowSticky( rCxt.mnRowDelta, rPos))
                    bChanged = true;
            }
            else
            {
                rRef.IncRow(rCxt.mnRowDelta);
                bChanged = true;
            }
        }
    }

@@ -3330,20 +3379,21 @@ bool adjustDoubleRefInName(
        {
            // Selection intersects the referenced range. Only expand the
            // bottom position.
            rRef.Ref2.IncRow(rCxt.mnRowDelta);
            rRef.IncEndRowSticky(rCxt.mnRowDelta, rPos);
            return true;
        }
    }

    if ((rCxt.mnRowDelta && rRef.IsEntireCol()) || (rCxt.mnColDelta && rRef.IsEntireRow()))
    {
        // References to entire col/row are not to be adjusted in the other axis.
        sc::RefUpdateContext aCxt( rCxt.mrDoc);
        // We only need a few parameters of RefUpdateContext.
        aCxt.maRange = rCxt.maRange;
        aCxt.mnColDelta = rCxt.mnColDelta;
        aCxt.mnRowDelta = rCxt.mnRowDelta;
        aCxt.mnTabDelta = rCxt.mnTabDelta;

        // References to entire col/row are not to be adjusted in the other axis.
        if (aCxt.mnRowDelta && rRef.IsEntireCol())
            aCxt.mnRowDelta = 0;
        if (aCxt.mnColDelta && rRef.IsEntireRow())
@@ -3352,18 +3402,20 @@ bool adjustDoubleRefInName(
            // early bailout
            return bRefChanged;

        if (adjustSingleRefInName(rRef.Ref1, aCxt, rPos))
        // Ref2 before Ref1 for sticky ends.
        if (adjustSingleRefInName(rRef.Ref2, aCxt, rPos, &rRef))
            bRefChanged = true;

        if (adjustSingleRefInName(rRef.Ref2, aCxt, rPos))
        if (adjustSingleRefInName(rRef.Ref1, aCxt, rPos, nullptr))
            bRefChanged = true;
    }
    else
    {
        if (adjustSingleRefInName(rRef.Ref1, rCxt, rPos))
        // Ref2 before Ref1 for sticky ends.
        if (adjustSingleRefInName(rRef.Ref2, rCxt, rPos, &rRef))
            bRefChanged = true;

        if (adjustSingleRefInName(rRef.Ref2, rCxt, rPos))
        if (adjustSingleRefInName(rRef.Ref1, rCxt, rPos, nullptr))
            bRefChanged = true;
    }

@@ -3396,7 +3448,7 @@ sc::RefUpdateResult ScTokenArray::AdjustReferenceInName(
                case svSingleRef:
                    {
                        ScSingleRefData& rRef = *p->GetSingleRef();
                        if (adjustSingleRefInName(rRef, rCxt, rPos))
                        if (adjustSingleRefInName(rRef, rCxt, rPos, nullptr))
                            aRes.mbReferenceModified = true;
                    }
                    break;
@@ -3449,19 +3501,23 @@ sc::RefUpdateResult ScTokenArray::AdjustReferenceInName(

                            if (aAbs.aStart.Row() < aDeleted.aStart.Row())
                            {
                                if (aDeleted.aEnd.Row() < aAbs.aEnd.Row())
                                    // Deleted in the middle.  Make the reference shorter.
                                    rRef.Ref2.IncRow(rCxt.mnRowDelta);
                                else
                                    // Deleted at tail end.  Cut off the lower part.
                                    rRef.Ref2.SetAbsRow(aDeleted.aStart.Row()-1);
                                if (!aAbs.IsEndRowSticky())
                                {
                                    if (aDeleted.aEnd.Row() < aAbs.aEnd.Row())
                                        // Deleted in the middle.  Make the reference shorter.
                                        rRef.Ref2.IncRow(rCxt.mnRowDelta);
                                    else
                                        // Deleted at tail end.  Cut off the lower part.
                                        rRef.Ref2.SetAbsRow(aDeleted.aStart.Row()-1);
                                }
                            }
                            else
                            {
                                // Deleted at the top.  Cut the top off and shift up.
                                rRef.Ref1.SetAbsRow(aDeleted.aEnd.Row()+1);
                                rRef.Ref1.IncRow(rCxt.mnRowDelta);
                                rRef.Ref2.IncRow(rCxt.mnRowDelta);
                                if (!aAbs.IsEndRowSticky())
                                    rRef.Ref2.IncRow(rCxt.mnRowDelta);
                            }

                            aRes.mbReferenceModified = true;