tdf#137625 sc: autofill user list sequences in merged cells

Improve FillAuto, FillAnalyse to continue linear sequences
of user list values in merged cells. (User lists are special
string lists with values like day names Monday, Tuesday, ...)

If values are user list values, but not in linear sequence, then
the autofill will just fill with FILL_SIMPLE (pattern repeat).

Note: the unit test depends on English system locale settings,
check it in Tools->Options->LibreOffice Calc->Sort list,
otherwise extend Sort list with English day names for manual
testing.

Co-authored-by: Tibor Nagy (NISZ)

Change-Id: I7a1da5e82a18ba8ebd24af7e4b89c7651f3ec24a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/104690
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sc/qa/unit/copy_paste_test.cxx b/sc/qa/unit/copy_paste_test.cxx
index 527afcc..131954a 100644
--- a/sc/qa/unit/copy_paste_test.cxx
+++ b/sc/qa/unit/copy_paste_test.cxx
@@ -47,6 +47,7 @@ public:
    void testTdf88782_autofillLinearNumbersInMergedCells();
    void tdf137621_autofillMergedBool();
    void tdf137205_autofillDatesInMergedCells();
    void tdf137625_autofillMergedUserlist();

    CPPUNIT_TEST_SUITE(ScCopyPasteTest);
    CPPUNIT_TEST(testCopyPasteXLS);
@@ -60,6 +61,7 @@ public:
    CPPUNIT_TEST(testTdf88782_autofillLinearNumbersInMergedCells);
    CPPUNIT_TEST(tdf137621_autofillMergedBool);
    CPPUNIT_TEST(tdf137205_autofillDatesInMergedCells);
    CPPUNIT_TEST(tdf137625_autofillMergedUserlist);
    CPPUNIT_TEST_SUITE_END();

private:
@@ -751,6 +753,61 @@ void ScCopyPasteTest::tdf137205_autofillDatesInMergedCells()
    }
}

void ScCopyPasteTest::tdf137625_autofillMergedUserlist()
{
    ScDocShellRef xDocSh = loadDocAndSetupModelViewController("tdf137625_autofillMergedUserlist.", FORMAT_ODS, true);
    ScDocument& rDoc = xDocSh->GetDocument();

    // Get the document controller
    ScTabViewShell* pView = xDocSh->GetBestViewShell(false);
    CPPUNIT_ASSERT(pView != nullptr);

    // fillauto userlist, these areas contain only merged cells
    pView->FillAuto(FILL_TO_RIGHT, 7, 5, 12, 7, 6);   //H6:M8
    pView->FillAuto(FILL_TO_LEFT, 7, 5, 12, 7, 6);    //H6:M8
    pView->FillAuto(FILL_TO_BOTTOM, 1, 20, 3, 23, 4); //B21:D24
    pView->FillAuto(FILL_TO_TOP, 1, 20, 3, 23, 4);    //B21:D24

    // compare the results of fill-right / -left with the reference stored in the test file
    // this compares the whole area blindly, for specific test cases, check the test file
    for (int nCol = 1; nCol <= 18; nCol++)
    {
        for (int nRow = 5; nRow <= 7; nRow++)
        {
            CellType nType1 = rDoc.GetCellType(ScAddress(nCol, nRow, 0));
            CellType nType2 = rDoc.GetCellType(ScAddress(nCol, nRow + 4, 0));
            double* pValue1 = rDoc.GetValueCell(ScAddress(nCol, nRow, 0));
            double* pValue2 = rDoc.GetValueCell(ScAddress(nCol, nRow + 4, 0));

            CPPUNIT_ASSERT_EQUAL(nType1, nType2);
            if (pValue2 != nullptr)
                CPPUNIT_ASSERT_EQUAL(*pValue1, *pValue2);   //cells with userlist value
            else
                CPPUNIT_ASSERT_EQUAL(pValue1, pValue2);     //empty cells
        }
    }

    // compare the results of fill-up / -down
    for (int nCol = 1; nCol <= 3; nCol++)
    {
        for (int nRow = 16; nRow <= 27; nRow++)
        {
            CellType nType1 = rDoc.GetCellType(ScAddress(nCol, nRow, 0));
            CellType nType2 = rDoc.GetCellType(ScAddress(nCol + 4, nRow, 0));
            double* pValue1 = rDoc.GetValueCell(ScAddress(nCol, nRow, 0));
            double* pValue2 = rDoc.GetValueCell(ScAddress(nCol + 4, nRow, 0));

            CPPUNIT_ASSERT_EQUAL(nType1, nType2);
            if (pValue2 != nullptr)
                CPPUNIT_ASSERT_EQUAL(*pValue1, *pValue2);   //cells with userlist value
            else
                CPPUNIT_ASSERT_EQUAL(pValue1, pValue2);     //empty cells
        }
    }
}



ScCopyPasteTest::ScCopyPasteTest()
      : ScBootstrapFixture( "sc/qa/unit/data" )
{
diff --git a/sc/qa/unit/data/ods/tdf137625_autofillMergedUserlist.ods b/sc/qa/unit/data/ods/tdf137625_autofillMergedUserlist.ods
new file mode 100644
index 0000000..aee3815
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf137625_autofillMergedUserlist.ods
Binary files differ
diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx
index d947e16..7d55603 100644
--- a/sc/source/core/data/table4.cxx
+++ b/sc/source/core/data/table4.cxx
@@ -420,8 +420,59 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
            }
            else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
            {
                // TODO: check / handle if it is a sequence of userlist string
                // or if the strings are composition of a number sequence and a constant string
                OUString aStr;
                GetString(nColCurr, nRowCurr, aStr);

                bool bAllSame = true;
                for (SCSIZE i = 0; i < nValueCount; ++i)
                {
                    OUString aTestStr;
                    GetString(static_cast<SCCOL>(nCol1 + rNonOverlappedCellIdx[i] * nAddX),
                              static_cast<SCROW>(nRow1 + rNonOverlappedCellIdx[i] * nAddY),
                              aTestStr);
                    if (aStr != aTestStr)
                    {
                        bAllSame = false;
                        break;
                    }
                }
                if (bAllSame && nValueCount > 1)
                    return;

                rListData = const_cast<ScUserListData*>(ScGlobal::GetUserList()->GetData(aStr));
                if (rListData)
                {
                    bool bMatchCase = false;
                    (void)rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
                    size_t nListStrCount = rListData->GetSubCount();
                    sal_uInt16 nPrevListIndex, nInc = 0;
                    for (SCSIZE i = 1; i < nValueCount && rListData; i++)
                    {
                        nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
                        nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
                        GetString(nColCurr, nRowCurr, aStr);

                        nPrevListIndex = rListIndex;
                        if (!rListData->GetSubIndex(aStr, rListIndex, bMatchCase))
                            rListData = nullptr;
                        else
                        {
                            sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
                            if (nIncCurr < 0)
                                nIncCurr += nListStrCount;
                            if (i == 1)
                                nInc = nIncCurr;
                            else if (nInc != nIncCurr)
                                rListData = nullptr;
                        }
                    }
                    if (rListData) {
                        rInc = nInc;
                        rSkipOverlappedCells = true;
                        return;
                    }
                }
                // TODO: check / handle if it is a string containing a number
            }
        }
    }
@@ -997,37 +1048,94 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        if (pListData)
        {
            sal_uInt16 nListCount = pListData->GetSubCount();
            if ( !bPositive )
            if (bSkipOverlappedCells)
            {
                //  nListIndex of FillAnalyse points to the last entry -> adjust
                sal_uLong nSub = nISrcStart - nISrcEnd;
                for (sal_uLong i=0; i<nSub; i++)
                int nFillerCount = 1 + ( nISrcEnd - nISrcStart ) * (bPositive ? 1 : -1);
                std::vector<bool> aIsNonEmptyCell(nFillerCount, false);
                SCCOLROW nLastValueIdx;
                if (bPositive)
                {
                    if (nListIndex == 0) nListIndex = nListCount;
                    --nListIndex;
                    nLastValueIdx = nISrcEnd - (nFillerCount - 1 - aNonOverlappedCellIdx.back());
                    for (auto i : aNonOverlappedCellIdx)
                        aIsNonEmptyCell[i] = true;
                }
                else
                {
                    nLastValueIdx = nISrcEnd + aNonOverlappedCellIdx[0];
                    for (auto i : aNonOverlappedCellIdx)
                        aIsNonEmptyCell[nFillerCount - 1 - i] = true;
                }

                OUString aStr;
                if (bVertical)
                    GetString(rOuter, nLastValueIdx, aStr);
                else
                    GetString(nLastValueIdx, rOuter, aStr);

                bool bMatchCase = false;
                (void)pListData->GetSubIndex(aStr, nListIndex, bMatchCase);

                sal_Int32 nFillerIdx = 0;
                rInner = nIStart;
                while (true)
                {
                    if (aIsNonEmptyCell[nFillerIdx])
                    {
                        if (bPositive)
                        {
                            nListIndex += nInc;
                            if (nListIndex >= nListCount) nListIndex -= nListCount;
                        }
                        else
                        {
                            if (nListIndex < nInc) nListIndex += nListCount;
                            nListIndex -= nInc;
                        }
                        aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));

                    }
                    if (rInner == nIEnd) break;
                    nFillerIdx = (nFillerIdx + 1) % nFillerCount;
                    if (bPositive)
                        ++rInner;
                    else
                        --rInner;
                }
            }

            rInner = nIStart;
            while (true)        // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
            else
            {
                if(!ColHidden(nCol) && !RowHidden(nRow))
                if (!bPositive)
                {
                    if (bPositive)
                    {
                        ++nListIndex;
                        if (nListIndex >= nListCount) nListIndex = 0;
                    }
                    else
                    //  nListIndex of FillAnalyse points to the last entry -> adjust
                    sal_uLong nSub = nISrcStart - nISrcEnd;
                    for (sal_uLong i = 0; i < nSub; i++)
                    {
                        if (nListIndex == 0) nListIndex = nListCount;
                        --nListIndex;
                    }
                    aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
                }

                if (rInner == nIEnd) break;
                if (bPositive) ++rInner; else --rInner;
                rInner = nIStart;
                while (true)        // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
                {
                    if (!ColHidden(nCol) && !RowHidden(nRow))
                    {
                        if (bPositive)
                        {
                            ++nListIndex;
                            if (nListIndex >= nListCount) nListIndex = 0;
                        }
                        else
                        {
                            if (nListIndex == 0) nListIndex = nListCount;
                            --nListIndex;
                        }
                        aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
                    }

                    if (rInner == nIEnd) break;
                    if (bPositive) ++rInner; else --rInner;
                }
            }
            if(pProgress)
            {