fdo#58988, fdo#58562: Populate draw clip document with data for charts.

Without populating the clip document, copying a chart (or charts) into
clipboard makes the charts lose all their referenced data, which causes
the pasted chart to appear empty.

Change-Id: I3675f76baed19b48cec403964c19df00725a044b
diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx
index 96df4d9..f4370a9 100644
--- a/sc/inc/column.hxx
+++ b/sc/inc/column.hxx
@@ -168,6 +168,7 @@ public:
    void        DeleteRange( SCSIZE nStartIndex, SCSIZE nEndIndex, sal_uInt16 nDelFlag );
    void        DeleteArea(SCROW nStartRow, SCROW nEndRow, sal_uInt16 nDelFlag );
    void CopyToClip(SCROW nRow1, SCROW nRow2, ScColumn& rColumn, bool bKeepScenarioFlags) const;
    void CopyStaticToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol);
    void        CopyFromClip(SCROW nRow1, SCROW nRow2, long nDy,
                                sal_uInt16 nInsFlag, bool bAsLink, bool bSkipAttrForEmpty, ScColumn& rColumn);
    void        StartListeningInArea( SCROW nRow1, SCROW nRow2 );
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index 7573a80..3874916 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -1029,6 +1029,16 @@ public:
                               const ScMarkData* pMarks = NULL, bool bAllTabs = false, bool bKeepScenarioFlags = false,
                               bool bIncludeObjects = false, bool bCloneNoteCaptions = true, bool bUseRangeForVBA = false );

    /**
     * Copy only raw cell values to another document.  Formula cells are
     * converted to raw cells.  No formatting info are copied.
     *
     * @param rSrcRange source range in the source document
     * @param nDestTab table in the clip document to copy to.
     * @param pDestDoc document to copy to
     */
    void CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument* pDestDoc);

    void            CopyTabToClip(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                                SCTAB nTab, ScDocument* pClipDoc = NULL);
    void            CopyBlockFromClip( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index fa03a7f..30eb97a 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -351,6 +351,7 @@ public:
                            bool bKeepScenarioFlags, bool bCloneNoteCaptions);
    void        CopyToClip(const ScRangeList& rRanges, ScTable* pTable,
                           bool bKeepScenarioFlags, bool bCloneNoteCaptions);
    void CopyStaticToDocument(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab);
    void        CopyFromClip(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SCsCOL nDx, SCsROW nDy,
                                sal_uInt16 nInsFlag, bool bAsLink, bool bSkipAttrForEmpty, ScTable* pTable);
    void        StartListeningInArea( SCCOL nCol1, SCROW nRow1,
diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx
index 9e116c3..6a2ac0f 100644
--- a/sc/qa/unit/ucalc.cxx
+++ b/sc/qa/unit/ucalc.cxx
@@ -117,7 +117,7 @@ public:
    void testRangeList();
    void testInput();
    void testCellFunctions();

    void testCopyToDocument();
    /**
     * Make sure the SHEETS function gets properly updated during sheet
     * insertion and removal.
@@ -268,6 +268,7 @@ public:
    CPPUNIT_TEST(testRangeList);
    CPPUNIT_TEST(testInput);
    CPPUNIT_TEST(testCellFunctions);
    CPPUNIT_TEST(testCopyToDocument);
    CPPUNIT_TEST(testSheetsFunc);
    CPPUNIT_TEST(testVolatileFunc);
    CPPUNIT_TEST(testFormulaDepTracking);
@@ -1153,6 +1154,34 @@ void Test::testCellFunctions()
    m_pDoc->DeleteTab(0);
}

void Test::testCopyToDocument()
{
    CPPUNIT_ASSERT_MESSAGE ("failed to insert sheet", m_pDoc->InsertTab (0, "src"));

    m_pDoc->SetString(0, 0, 0, "Header");
    m_pDoc->SetString(0, 1, 0, "1");
    m_pDoc->SetString(0, 2, 0, "2");
    m_pDoc->SetString(0, 3, 0, "3");
    m_pDoc->SetString(0, 4, 0, "=4/2");
    m_pDoc->CalcAll();

    // Copy statically to another document.

    ScDocument aDestDoc(SCDOCMODE_DOCUMENT);
    aDestDoc.InsertTab(0, "src");
    m_pDoc->CopyStaticToDocument(ScRange(0,1,0,0,3,0), 0, &aDestDoc); // Copy A2:A4
    m_pDoc->CopyStaticToDocument(ScAddress(0,0,0), 0, &aDestDoc); // Copy A1
    m_pDoc->CopyStaticToDocument(ScRange(0,4,0,0,7,0), 0, &aDestDoc); // Copy A5:A8

    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,0,0), aDestDoc.GetString(0,0,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,1,0), aDestDoc.GetString(0,1,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,2,0), aDestDoc.GetString(0,2,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,3,0), aDestDoc.GetString(0,3,0));
    CPPUNIT_ASSERT_EQUAL(m_pDoc->GetString(0,4,0), aDestDoc.GetString(0,4,0));

    m_pDoc->DeleteTab(0);
}

void Test::testSheetsFunc()
{
    rtl::OUString aTabName1("test1");
diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx
index aefe043..8520dc3 100644
--- a/sc/source/core/data/column.cxx
+++ b/sc/source/core/data/column.cxx
@@ -1191,6 +1191,111 @@ void ScColumn::CopyToClip(SCROW nRow1, SCROW nRow2, ScColumn& rColumn, bool bKee
    }
}

namespace {

class FindInRows : std::unary_function<ColEntry, bool>
{
    SCROW mnRow1;
    SCROW mnRow2;
public:
    FindInRows(SCROW nRow1, SCROW nRow2) : mnRow1(nRow1), mnRow2(nRow2) {}
    bool operator() (const ColEntry& rEntry)
    {
        return mnRow1 <= rEntry.nRow && rEntry.nRow <= mnRow2;
    }
};

class FindAboveRow : std::unary_function<ColEntry, bool>
{
    SCROW mnRow;
public:
    FindAboveRow(SCROW nRow) : mnRow(nRow) {}
    bool operator() (const ColEntry& rEntry)
    {
        return mnRow < rEntry.nRow;
    }
};

}

void ScColumn::CopyStaticToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol)
{
    if (nRow1 > nRow2)
        return;

    // First, clear the destination column for the row range specified.
    std::vector<ColEntry>::iterator it, itEnd;

    it = std::find_if(rDestCol.maItems.begin(), rDestCol.maItems.end(), FindInRows(nRow1, nRow2));
    if (it != rDestCol.maItems.end())
    {
        itEnd = std::find_if(it, rDestCol.maItems.end(), FindAboveRow(nRow2));
        rDestCol.maItems.erase(it, itEnd);
    }

    // Determine the range of cells in the original column that need to be copied.
    it = std::find_if(maItems.begin(), maItems.end(), FindInRows(nRow1, nRow2));
    if (it != maItems.end())
        itEnd = std::find_if(it, maItems.end(), FindAboveRow(nRow2));

    // Clone and staticize all cells that need to be copied.
    std::vector<ColEntry> aCopied;
    aCopied.reserve(std::distance(it, itEnd));
    for (; it != itEnd; ++it)
    {
        ColEntry aEntry;
        aEntry.nRow = it->nRow;
        switch (it->pCell->GetCellType())
        {
            case CELLTYPE_VALUE:
            {
                const ScValueCell& rCell = static_cast<const ScValueCell&>(*it->pCell);
                aEntry.pCell = new ScValueCell(rCell.GetValue());
                aCopied.push_back(aEntry);
            }
            break;
            case CELLTYPE_STRING:
            {
                const ScStringCell& rCell = static_cast<const ScStringCell&>(*it->pCell);
                aEntry.pCell = new ScStringCell(rCell.GetString());
                aCopied.push_back(aEntry);
            }
            break;
            case CELLTYPE_EDIT:
            {
                // Convert to a simple string cell.
                const ScEditCell& rCell = static_cast<const ScEditCell&>(*it->pCell);
                aEntry.pCell = new ScStringCell(rCell.GetString());
                aCopied.push_back(aEntry);
            }
            break;
            case CELLTYPE_FORMULA:
            {
                ScFormulaCell& rCell = static_cast<ScFormulaCell&>(*it->pCell);
                if (rCell.GetDirty() && pDocument->GetAutoCalc())
                    rCell.Interpret();

                if (rCell.GetErrorCode())
                    // Skip cells with error.
                    break;

                if (rCell.IsValue())
                    aEntry.pCell = new ScValueCell(rCell.GetValue());
                else
                    aEntry.pCell = new ScStringCell(rCell.GetString());
                aCopied.push_back(aEntry);
            }
            break;
            default:
                ; // ignore the rest.
        }
    }

    // Insert the cells into destination column.  At this point the
    // destination column shouldn't have any cells within the specified range.
    it = std::find_if(rDestCol.maItems.begin(), rDestCol.maItems.end(), FindAboveRow(nRow2));
    rDestCol.maItems.insert(it, aCopied.begin(), aCopied.end());
}

void ScColumn::CopyToColumn(
    SCROW nRow1, SCROW nRow2, sal_uInt16 nFlags, bool bMarked, ScColumn& rColumn,
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
index a3e5cfa..d2ff1ee 100644
--- a/sc/source/core/data/document.cxx
+++ b/sc/source/core/data/document.cxx
@@ -1956,6 +1956,21 @@ void ScDocument::CopyToClip(const ScClipParam& rClipParam,
    pClipDoc->ExtendMerge(aClipRange, true);
}

void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument* pDestDoc)
{
    if (!pDestDoc)
        return;

    ScTable* pSrcTab = rSrcRange.aStart.Tab() < static_cast<SCTAB>(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()] : NULL;
    ScTable* pDestTab = nDestTab < static_cast<SCTAB>(pDestDoc->maTabs.size()) ? pDestDoc->maTabs[nDestTab] : NULL;

    if (!pSrcTab || !pDestTab)
        return;

    pSrcTab->CopyStaticToDocument(
        rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(), pDestTab);
}

void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1,
                                SCCOL nCol2, SCROW nRow2,
                                SCTAB nTab, ScDocument* pClipDoc)
diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx
index 6087841c..2d909c0 100644
--- a/sc/source/core/data/table2.cxx
+++ b/sc/source/core/data/table2.cxx
@@ -653,6 +653,19 @@ void ScTable::CopyToClip(const ScRangeList& rRanges, ScTable* pTable,
    }
}

void ScTable::CopyStaticToDocument(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab)
{
    if (nCol1 > nCol2)
        return;

    for (SCCOL i = nCol1; i <= nCol2; ++i)
    {
        ScColumn& rSrcCol = aCol[i];
        ScColumn& rDestCol = pDestTab->aCol[i];
        rSrcCol.CopyStaticToDocument(nRow1, nRow2, rDestCol);
    }
}

void ScTable::CopyConditionalFormat( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
        SCsCOL nDx, SCsROW nDy, ScTable* pTable)
{
diff --git a/sc/source/ui/view/drawvie4.cxx b/sc/source/ui/view/drawvie4.cxx
index c49820b..a6289d0 100644
--- a/sc/source/ui/view/drawvie4.cxx
+++ b/sc/source/ui/view/drawvie4.cxx
@@ -42,6 +42,8 @@

#include <com/sun/star/embed/NoVisualAreaSizeException.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/embed/XComponentSupplier.hpp>

using namespace com::sun::star;

@@ -137,14 +139,183 @@ sal_Bool ScDrawView::BeginDrag( Window* pWindow, const Point& rStartPos )
    return bReturn;
}

namespace {

void getRangeFromOle2Object(const SdrOle2Obj& rObj, std::vector<OUString>& rRangeRep)
{
    if (!rObj.IsChart())
        // not a chart object.
        return;

    uno::Reference<embed::XEmbeddedObject> xObj = rObj.GetObjRef();
    if (!xObj.is())
        return;

    uno::Reference<embed::XComponentSupplier> xCompSupp(xObj, uno::UNO_QUERY);
    if (!xCompSupp.is())
        return;

    uno::Reference<chart2::XChartDocument> xChartDoc(xCompSupp->getComponent(), uno::UNO_QUERY);
    if (!xChartDoc.is())
        return;

    uno::Reference<chart2::data::XDataSource> xDataSource(xChartDoc, uno::UNO_QUERY);
    if (!xDataSource.is())
        return;

    // Get all data sources used in this chart.
    uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence> > xSeqs = xDataSource->getDataSequences();
    for (sal_Int32 i = 0, n = xSeqs.getLength(); i < n; ++i)
    {
        uno::Reference<chart2::data::XLabeledDataSequence> xLS = xSeqs[i];
        uno::Reference<chart2::data::XDataSequence> xSeq = xLS->getValues();
        if (xSeq.is())
        {
            OUString aRep = xSeq->getSourceRangeRepresentation();
            rRangeRep.push_back(aRep);
        }
        xSeq = xLS->getLabel();
        if (xSeq.is())
        {
            OUString aRep = xSeq->getSourceRangeRepresentation();
            rRangeRep.push_back(aRep);
        }
    }
}

/**
 * Get all cell ranges that are referenced by the selected chart objects.
 */
void getChartSourceRanges(ScDocument* pDoc, const SdrMarkList& rObjs, std::vector<ScRange>& rRanges)
{
    std::vector<OUString> aRangeReps;
    for (size_t i = 0, n = rObjs.GetMarkCount(); i < n; ++i)
    {
        const SdrMark* pMark = rObjs.GetMark(i);
        if (!pMark)
            continue;

        const SdrObject* pObj = pMark->GetMarkedSdrObj();
        if (!pObj)
            continue;

        switch (pObj->GetObjIdentifier())
        {
            case OBJ_OLE2:
                getRangeFromOle2Object(static_cast<const SdrOle2Obj&>(*pObj), aRangeReps);
            break;
            case OBJ_GRUP:
            {
                SdrObjListIter aIter(*pObj, IM_DEEPNOGROUPS);
                for (SdrObject* pSubObj = aIter.Next(); pSubObj; pSubObj = aIter.Next())
                {
                    if (pSubObj->GetObjIdentifier() != OBJ_OLE2)
                        continue;

                    getRangeFromOle2Object(static_cast<const SdrOle2Obj&>(*pSubObj), aRangeReps);
                }

            }
            break;
            default:
                ;
        }
    }

    // Compile all range representation strings into ranges.
    std::vector<OUString>::const_iterator it = aRangeReps.begin(), itEnd = aRangeReps.end();
    for (; it != itEnd; ++it)
    {
        ScRange aRange;
        ScAddress aAddr;
        if (aRange.Parse(*it, pDoc, pDoc->GetAddressConvention()) & SCA_VALID)
            rRanges.push_back(aRange);
        else if (aAddr.Parse(*it, pDoc, pDoc->GetAddressConvention()) & SCA_VALID)
            rRanges.push_back(aAddr);
    }
}

class InsertTabIndex : std::unary_function<ScRange, void>
{
    std::vector<SCTAB>& mrTabs;
public:
    InsertTabIndex(std::vector<SCTAB>& rTabs) : mrTabs(rTabs) {}
    void operator() (const ScRange& rRange)
    {
        mrTabs.push_back(rRange.aStart.Tab());
    }
};

class CopyRangeData : std::unary_function<ScRange, void>
{
    ScDocument* mpSrc;
    ScDocument* mpDest;
public:
    CopyRangeData(ScDocument* pSrc, ScDocument* pDest) : mpSrc(pSrc), mpDest(pDest) {}

    void operator() (const ScRange& rRange)
    {
        OUString aTabName;
        mpSrc->GetName(rRange.aStart.Tab(), aTabName);

        SCTAB nTab;
        if (!mpDest->GetTable(aTabName, nTab))
            // Sheet by this name doesn't exist.
            return;

        mpSrc->CopyStaticToDocument(rRange, nTab, mpDest);
    }
};

void copyChartRefDataToClipDoc(ScDocument* pSrcDoc, ScDocument* pClipDoc, const std::vector<ScRange>& rRanges)
{
    // Get a list of referenced table indices.
    std::vector<SCTAB> aTabs;
    std::for_each(rRanges.begin(), rRanges.end(), InsertTabIndex(aTabs));
    std::sort(aTabs.begin(), aTabs.end());
    aTabs.erase(std::unique(aTabs.begin(), aTabs.end()), aTabs.end());

    // Get table names.
    if (aTabs.empty())
        return;

    // Create sheets only for referenced source sheets.
    OUString aName;
    std::vector<SCTAB>::const_iterator it = aTabs.begin(), itEnd = aTabs.end();
    if (!pSrcDoc->GetName(*it, aName))
        return;

    pClipDoc->SetTabNameOnLoad(0, aName); // document initially has one sheet.

    for (++it; it != itEnd; ++it)
    {
        if (!pSrcDoc->GetName(*it, aName))
            return;

        pClipDoc->AppendTabOnLoad(aName);
    }

    std::for_each(rRanges.begin(), rRanges.end(), CopyRangeData(pSrcDoc, pClipDoc));
}

}

void ScDrawView::DoCopy()
{
    sal_Bool bAnyOle, bOneOle;
    const SdrMarkList& rMarkList = GetMarkedObjectList();
    CheckOle( rMarkList, bAnyOle, bOneOle );
    std::vector<ScRange> aRanges;
    getChartSourceRanges(pDoc, rMarkList, aRanges);

    // update ScGlobal::pDrawClipDocShellRef
    ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) );
    ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc(!aRanges.empty()) );
    if (ScGlobal::pDrawClipDocShellRef)
    {
        // Copy data referenced by the chart objects to the draw clip
        // document. We need to do this before GetMarkedObjModel() below.
        ScDocShellRef xDocSh = *ScGlobal::pDrawClipDocShellRef;
        ScDocument* pClipDoc = xDocSh->GetDocument();
        copyChartRefDataToClipDoc(pDoc, pClipDoc, aRanges);
    }
    SdrModel* pModel = GetMarkedObjModel();
    ScDrawLayer::SetGlobalDrawPersist(NULL);

@@ -152,7 +323,6 @@ void ScDrawView::DoCopy()
    //  there's no need to call SchDLL::Update for the charts in the clipboard doc.
    //  Update with the data (including NumberFormatter) from the live document would
    //  also store the NumberFormatter in the clipboard chart (#88749#)
    // lcl_RefreshChartData( pModel, pViewData->GetDocument() );

    ScDocShell* pDocSh = pViewData->GetDocShell();