3512 lines
114 KiB
C++
3512 lines
114 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <sal/config.h>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <chart2uno.hxx>
|
|
#include <miscuno.hxx>
|
|
#include <document.hxx>
|
|
#include <docsh.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <unonames.hxx>
|
|
#include <globstr.hrc>
|
|
#include <scresid.hxx>
|
|
#include <rangeutl.hxx>
|
|
#include <hints.hxx>
|
|
#include <unoreflist.hxx>
|
|
#include <compiler.hxx>
|
|
#include <reftokenhelper.hxx>
|
|
#include <chartlis.hxx>
|
|
#include <tokenuno.hxx>
|
|
#include <cellvalue.hxx>
|
|
#include <tokenarray.hxx>
|
|
#include <scmatrix.hxx>
|
|
#include <brdcst.hxx>
|
|
#include <mtvelements.hxx>
|
|
|
|
#include <formula/opcode.hxx>
|
|
#include <o3tl/safeint.hxx>
|
|
#include <svl/numformat.hxx>
|
|
#include <svl/sharedstring.hxx>
|
|
|
|
#include <vcl/svapp.hxx>
|
|
|
|
#include <com/sun/star/beans/UnknownPropertyException.hpp>
|
|
#include <com/sun/star/chart/ChartDataRowSource.hpp>
|
|
#include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
|
|
#include <com/sun/star/frame/XModel.hpp>
|
|
#include <comphelper/extract.hxx>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <comphelper/sequence.hxx>
|
|
|
|
#include <limits>
|
|
|
|
SC_SIMPLE_SERVICE_INFO( ScChart2DataProvider, u"ScChart2DataProvider"_ustr,
|
|
u"com.sun.star.chart2.data.DataProvider"_ustr)
|
|
SC_SIMPLE_SERVICE_INFO( ScChart2DataSource, u"ScChart2DataSource"_ustr,
|
|
u"com.sun.star.chart2.data.DataSource"_ustr)
|
|
SC_SIMPLE_SERVICE_INFO( ScChart2DataSequence, u"ScChart2DataSequence"_ustr,
|
|
u"com.sun.star.chart2.data.DataSequence"_ustr)
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::formula;
|
|
using ::com::sun::star::uno::Sequence;
|
|
using ::std::unique_ptr;
|
|
using ::std::vector;
|
|
using ::std::distance;
|
|
using ::std::shared_ptr;
|
|
|
|
namespace
|
|
{
|
|
std::span<const SfxItemPropertyMapEntry> lcl_GetDataProviderPropertyMap()
|
|
{
|
|
static const SfxItemPropertyMapEntry aDataProviderPropertyMap_Impl[] =
|
|
{
|
|
{ SC_UNONAME_INCLUDEHIDDENCELLS, 0, cppu::UnoType<bool>::get(), 0, 0 },
|
|
{ SC_UNONAME_USE_INTERNAL_DATA_PROVIDER, 0, cppu::UnoType<bool>::get(), 0, 0 },
|
|
};
|
|
return aDataProviderPropertyMap_Impl;
|
|
}
|
|
|
|
std::span<const SfxItemPropertyMapEntry> lcl_GetDataSequencePropertyMap()
|
|
{
|
|
static const SfxItemPropertyMapEntry aDataSequencePropertyMap_Impl[] =
|
|
{
|
|
{ SC_UNONAME_HIDDENVALUES, 0, cppu::UnoType<uno::Sequence<sal_Int32>>::get(), 0, 0 },
|
|
{ SC_UNONAME_ROLE, 0, cppu::UnoType<css::chart2::data::DataSequenceRole>::get(), 0, 0 },
|
|
{ SC_UNONAME_INCLUDEHIDDENCELLS, 0, cppu::UnoType<bool>::get(), 0, 0 },
|
|
};
|
|
return aDataSequencePropertyMap_Impl;
|
|
}
|
|
|
|
struct lcl_appendTableNumber
|
|
{
|
|
explicit lcl_appendTableNumber( OUStringBuffer & rBuffer ) :
|
|
m_rBuffer( rBuffer )
|
|
{}
|
|
void operator() ( SCTAB nTab )
|
|
{
|
|
// there is no append with SCTAB or sal_Int16
|
|
m_rBuffer.append( static_cast< sal_Int32 >( nTab ));
|
|
m_rBuffer.append( ' ' );
|
|
}
|
|
private:
|
|
OUStringBuffer & m_rBuffer;
|
|
};
|
|
|
|
OUString lcl_createTableNumberList( const ::std::vector< SCTAB > & rTableVector )
|
|
{
|
|
OUStringBuffer aBuffer;
|
|
::std::for_each( rTableVector.begin(), rTableVector.end(), lcl_appendTableNumber( aBuffer ));
|
|
// remove last trailing ' '
|
|
if( !aBuffer.isEmpty() )
|
|
aBuffer.setLength( aBuffer.getLength() - 1 );
|
|
return aBuffer.makeStringAndClear();
|
|
}
|
|
|
|
uno::Reference< frame::XModel > lcl_GetXModel( const ScDocument * pDoc )
|
|
{
|
|
uno::Reference< frame::XModel > xModel;
|
|
ScDocShell * pObjSh( pDoc ? pDoc->GetDocumentShell() : nullptr );
|
|
if( pObjSh )
|
|
xModel.set( pObjSh->GetModel());
|
|
return xModel;
|
|
}
|
|
|
|
struct TokenTable
|
|
{
|
|
SCROW mnRowCount;
|
|
SCCOL mnColCount;
|
|
vector<std::unique_ptr<FormulaToken>> maTokens;
|
|
|
|
// noncopyable
|
|
TokenTable(const TokenTable&) = delete;
|
|
const TokenTable& operator=(const TokenTable&) = delete;
|
|
|
|
TokenTable()
|
|
: mnRowCount(0)
|
|
, mnColCount(0)
|
|
{
|
|
}
|
|
|
|
void init( SCCOL nColCount, SCROW nRowCount )
|
|
{
|
|
mnColCount = nColCount;
|
|
mnRowCount = nRowCount;
|
|
maTokens.reserve(mnColCount*mnRowCount);
|
|
}
|
|
void clear()
|
|
{
|
|
for (auto & rToken : maTokens)
|
|
rToken.reset();
|
|
}
|
|
|
|
void push_back( std::unique_ptr<FormulaToken> pToken )
|
|
{
|
|
maTokens.push_back( std::move(pToken) );
|
|
OSL_ENSURE( maTokens.size()<= o3tl::make_unsigned( mnColCount*mnRowCount ), "too many tokens" );
|
|
}
|
|
|
|
sal_uInt32 getIndex(SCCOL nCol, SCROW nRow) const
|
|
{
|
|
OSL_ENSURE( nCol<mnColCount, "wrong column index" );
|
|
OSL_ENSURE( nRow<mnRowCount, "wrong row index" );
|
|
sal_uInt32 nRet = static_cast<sal_uInt32>(nCol*mnRowCount + nRow);
|
|
OSL_ENSURE( maTokens.size()>= o3tl::make_unsigned( mnColCount*mnRowCount ), "too few tokens" );
|
|
return nRet;
|
|
}
|
|
|
|
vector<ScTokenRef> getColRanges(const ScDocument* pDoc, SCCOL nCol) const;
|
|
vector<ScTokenRef> getRowRanges(const ScDocument* pDoc, SCROW nRow) const;
|
|
vector<ScTokenRef> getAllRanges(const ScDocument* pDoc) const;
|
|
};
|
|
|
|
vector<ScTokenRef> TokenTable::getColRanges(const ScDocument* pDoc, SCCOL nCol) const
|
|
{
|
|
if (nCol >= mnColCount)
|
|
return vector<ScTokenRef>();
|
|
if( mnRowCount<=0 )
|
|
return vector<ScTokenRef>();
|
|
|
|
vector<ScTokenRef> aTokens;
|
|
sal_uInt32 nLast = getIndex(nCol, mnRowCount-1);
|
|
for (sal_uInt32 i = getIndex(nCol, 0); i <= nLast; ++i)
|
|
{
|
|
FormulaToken* p = maTokens[i].get();
|
|
if (!p)
|
|
continue;
|
|
|
|
ScTokenRef pCopy(p->Clone());
|
|
ScRefTokenHelper::join(pDoc, aTokens, pCopy, ScAddress());
|
|
}
|
|
return aTokens;
|
|
}
|
|
|
|
vector<ScTokenRef> TokenTable::getRowRanges(const ScDocument* pDoc, SCROW nRow) const
|
|
{
|
|
if (nRow >= mnRowCount)
|
|
return vector<ScTokenRef>();
|
|
if( mnColCount<=0 )
|
|
return vector<ScTokenRef>();
|
|
|
|
vector<ScTokenRef> aTokens;
|
|
sal_uInt32 nLast = getIndex(mnColCount-1, nRow);
|
|
for (sal_uInt32 i = getIndex(0, nRow); i <= nLast; i += mnRowCount)
|
|
{
|
|
FormulaToken* p = maTokens[i].get();
|
|
if (!p)
|
|
continue;
|
|
|
|
ScTokenRef p2(p->Clone());
|
|
ScRefTokenHelper::join(pDoc, aTokens, p2, ScAddress());
|
|
}
|
|
return aTokens;
|
|
}
|
|
|
|
vector<ScTokenRef> TokenTable::getAllRanges(const ScDocument* pDoc) const
|
|
{
|
|
vector<ScTokenRef> aTokens;
|
|
sal_uInt32 nStop = mnColCount*mnRowCount;
|
|
for (sal_uInt32 i = 0; i < nStop; i++)
|
|
{
|
|
FormulaToken* p = maTokens[i].get();
|
|
if (!p)
|
|
continue;
|
|
|
|
ScTokenRef p2(p->Clone());
|
|
ScRefTokenHelper::join(pDoc, aTokens, p2, ScAddress());
|
|
}
|
|
return aTokens;
|
|
}
|
|
|
|
typedef std::map<SCROW, std::unique_ptr<FormulaToken>> FormulaTokenMap;
|
|
typedef std::map<sal_uInt32, FormulaTokenMap> FormulaTokenMapMap;
|
|
|
|
class Chart2PositionMap
|
|
{
|
|
public:
|
|
Chart2PositionMap(SCCOL nColCount, SCROW nRowCount,
|
|
bool bFillRowHeader, bool bFillColumnHeader, FormulaTokenMapMap& rCols,
|
|
ScDocument* pDoc );
|
|
~Chart2PositionMap();
|
|
|
|
SCCOL getDataColCount() const { return mnDataColCount; }
|
|
SCROW getDataRowCount() const { return mnDataRowCount; }
|
|
|
|
vector<ScTokenRef> getLeftUpperCornerRanges() const;
|
|
vector<ScTokenRef> getAllColHeaderRanges() const;
|
|
vector<ScTokenRef> getAllRowHeaderRanges() const;
|
|
|
|
vector<ScTokenRef> getColHeaderRanges(SCCOL nChartCol) const;
|
|
vector<ScTokenRef> getRowHeaderRanges(SCROW nChartRow) const;
|
|
|
|
vector<ScTokenRef> getDataColRanges(SCCOL nCol) const;
|
|
vector<ScTokenRef> getDataRowRanges(SCROW nRow) const;
|
|
|
|
private:
|
|
const ScDocument* mpDoc;
|
|
SCCOL mnDataColCount;
|
|
SCROW mnDataRowCount;
|
|
|
|
TokenTable maLeftUpperCorner; //nHeaderColCount*nHeaderRowCount
|
|
TokenTable maColHeaders; //mnDataColCount*nHeaderRowCount
|
|
TokenTable maRowHeaders; //nHeaderColCount*mnDataRowCount
|
|
TokenTable maData;//mnDataColCount*mnDataRowCount
|
|
};
|
|
|
|
Chart2PositionMap::Chart2PositionMap(SCCOL nAllColCount, SCROW nAllRowCount,
|
|
bool bFillRowHeader, bool bFillColumnHeader, FormulaTokenMapMap& rCols, ScDocument* pDoc)
|
|
{
|
|
mpDoc = pDoc;
|
|
// if bFillRowHeader is true, at least the first column serves as a row header.
|
|
// If more than one column is pure text all the first pure text columns are used as header.
|
|
// Likewise, if bFillColumnHeader is true, at least the first row serves as a column header.
|
|
// If more than one row is pure text all the first pure text rows are used as header.
|
|
|
|
SCROW nHeaderRowCount = (bFillColumnHeader && nAllColCount && nAllRowCount) ? 1 : 0;
|
|
SCCOL nHeaderColCount = (bFillRowHeader && nAllColCount && nAllRowCount) ? 1 : 0;
|
|
|
|
if( pDoc && (nHeaderColCount || nHeaderRowCount ) )
|
|
{
|
|
//check whether there is more than one text column or row that should be added to the headers
|
|
SCROW nMaxHeaderRow = nAllRowCount;
|
|
SCCOL nCol = 0;
|
|
for (auto it = rCols.begin(); it != rCols.end(); ++it, ++nCol)
|
|
{
|
|
// Skip header columns
|
|
if (nCol < nHeaderColCount)
|
|
continue;
|
|
|
|
const auto& rCol = *it;
|
|
|
|
bool bFoundValuesInCol = false;
|
|
bool bFoundAnythingInCol = false;
|
|
SCROW nRow = 0;
|
|
for (auto it2 = rCol.second.begin(); it2 != rCol.second.end(); ++it2, ++nRow)
|
|
{
|
|
const auto& rCell = *it2;
|
|
|
|
// Skip header rows
|
|
if (nRow < nHeaderRowCount || !rCell.second)
|
|
continue;
|
|
|
|
ScRange aRange;
|
|
bool bExternal = false;
|
|
StackVar eType = rCell.second->GetType();
|
|
if( eType==svExternal || eType==svExternalSingleRef || eType==svExternalDoubleRef || eType==svExternalName )
|
|
bExternal = true;//lllll todo correct?
|
|
ScTokenRef pSharedToken(rCell.second->Clone());
|
|
ScRefTokenHelper::getRangeFromToken(pDoc, aRange, pSharedToken, ScAddress(), bExternal);
|
|
SCCOL nCol1=0, nCol2=0;
|
|
SCROW nRow1=0, nRow2=0;
|
|
SCTAB nTab1=0, nTab2=0;
|
|
aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
|
|
if ( pDoc->HasValueData( nCol1, nRow1, nTab1 ) )
|
|
{
|
|
// Found some numeric data
|
|
bFoundValuesInCol = true;
|
|
nMaxHeaderRow = std::min(nMaxHeaderRow, nRow);
|
|
break;
|
|
}
|
|
if ( pDoc->HasData( nCol1, nRow1, nTab1 ) )
|
|
{
|
|
// Found some other data (non-numeric)
|
|
bFoundAnythingInCol = true;
|
|
}
|
|
else
|
|
{
|
|
// If cell is empty, it belongs to data
|
|
nMaxHeaderRow = std::min(nMaxHeaderRow, nRow);
|
|
}
|
|
}
|
|
|
|
if (nHeaderColCount && !bFoundValuesInCol && bFoundAnythingInCol && nCol == nHeaderColCount)
|
|
{
|
|
// There is no values in row, but some data. And this column is next to header
|
|
// So let's put it to header
|
|
nHeaderColCount++;
|
|
}
|
|
}
|
|
|
|
if (nHeaderRowCount)
|
|
{
|
|
nHeaderRowCount = nMaxHeaderRow;
|
|
}
|
|
}
|
|
|
|
mnDataColCount = nAllColCount - nHeaderColCount;
|
|
mnDataRowCount = nAllRowCount - nHeaderRowCount;
|
|
|
|
maLeftUpperCorner.init(nHeaderColCount,nHeaderRowCount);
|
|
maColHeaders.init(mnDataColCount,nHeaderRowCount);
|
|
maRowHeaders.init(nHeaderColCount,mnDataRowCount);
|
|
maData.init(mnDataColCount,mnDataRowCount);
|
|
|
|
FormulaTokenMapMap::iterator it1 = rCols.begin();
|
|
for (SCCOL nCol = 0; nCol < nAllColCount; ++nCol)
|
|
{
|
|
if (it1 != rCols.end())
|
|
{
|
|
FormulaTokenMap& rCol = it1->second;
|
|
FormulaTokenMap::iterator it2 = rCol.begin();
|
|
for (SCROW nRow = 0; nRow < nAllRowCount; ++nRow)
|
|
{
|
|
std::unique_ptr<FormulaToken> pToken;
|
|
if (it2 != rCol.end())
|
|
{
|
|
pToken = std::move(it2->second);
|
|
++it2;
|
|
}
|
|
|
|
if( nCol < nHeaderColCount )
|
|
{
|
|
if( nRow < nHeaderRowCount )
|
|
maLeftUpperCorner.push_back(std::move(pToken));
|
|
else
|
|
maRowHeaders.push_back(std::move(pToken));
|
|
}
|
|
else if( nRow < nHeaderRowCount )
|
|
maColHeaders.push_back(std::move(pToken));
|
|
else
|
|
maData.push_back(std::move(pToken));
|
|
}
|
|
++it1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Chart2PositionMap::~Chart2PositionMap()
|
|
{
|
|
maLeftUpperCorner.clear();
|
|
maColHeaders.clear();
|
|
maRowHeaders.clear();
|
|
maData.clear();
|
|
}
|
|
|
|
vector<ScTokenRef> Chart2PositionMap::getLeftUpperCornerRanges() const
|
|
{
|
|
return maLeftUpperCorner.getAllRanges(mpDoc);
|
|
}
|
|
vector<ScTokenRef> Chart2PositionMap::getAllColHeaderRanges() const
|
|
{
|
|
return maColHeaders.getAllRanges(mpDoc);
|
|
}
|
|
vector<ScTokenRef> Chart2PositionMap::getAllRowHeaderRanges() const
|
|
{
|
|
return maRowHeaders.getAllRanges(mpDoc);
|
|
}
|
|
vector<ScTokenRef> Chart2PositionMap::getColHeaderRanges(SCCOL nCol) const
|
|
{
|
|
return maColHeaders.getColRanges(mpDoc, nCol);
|
|
}
|
|
vector<ScTokenRef> Chart2PositionMap::getRowHeaderRanges(SCROW nRow) const
|
|
{
|
|
return maRowHeaders.getRowRanges(mpDoc, nRow);
|
|
}
|
|
|
|
vector<ScTokenRef> Chart2PositionMap::getDataColRanges(SCCOL nCol) const
|
|
{
|
|
return maData.getColRanges(mpDoc, nCol);
|
|
}
|
|
|
|
vector<ScTokenRef> Chart2PositionMap::getDataRowRanges(SCROW nRow) const
|
|
{
|
|
return maData.getRowRanges(mpDoc, nRow);
|
|
}
|
|
|
|
/**
|
|
* Designed to be a drop-in replacement for ScChartPositioner, in order to
|
|
* handle external references.
|
|
*/
|
|
class Chart2Positioner
|
|
{
|
|
enum GlueType
|
|
{
|
|
GLUETYPE_NA,
|
|
GLUETYPE_NONE,
|
|
GLUETYPE_COLS,
|
|
GLUETYPE_ROWS,
|
|
GLUETYPE_BOTH
|
|
};
|
|
|
|
public:
|
|
Chart2Positioner(const Chart2Positioner&) = delete;
|
|
const Chart2Positioner& operator=(const Chart2Positioner&) = delete;
|
|
|
|
Chart2Positioner(ScDocument* pDoc, const vector<ScTokenRef>& rRefTokens) :
|
|
mrRefTokens(rRefTokens),
|
|
meGlue(GLUETYPE_NA),
|
|
mnStartCol(0),
|
|
mnStartRow(0),
|
|
mpDoc(pDoc),
|
|
mbColHeaders(false),
|
|
mbRowHeaders(false),
|
|
mbDummyUpperLeft(false)
|
|
{
|
|
}
|
|
|
|
void setHeaders(bool bColHeaders, bool bRowHeaders)
|
|
{
|
|
mbColHeaders = bColHeaders;
|
|
mbRowHeaders = bRowHeaders;
|
|
}
|
|
|
|
Chart2PositionMap* getPositionMap()
|
|
{
|
|
createPositionMap();
|
|
return mpPositionMap.get();
|
|
}
|
|
|
|
private:
|
|
void invalidateGlue();
|
|
void glueState();
|
|
void calcGlueState(SCCOL nCols, SCROW nRows);
|
|
void createPositionMap();
|
|
|
|
private:
|
|
const vector<ScTokenRef>& mrRefTokens;
|
|
std::unique_ptr<Chart2PositionMap> mpPositionMap;
|
|
GlueType meGlue;
|
|
SCCOL mnStartCol;
|
|
SCROW mnStartRow;
|
|
ScDocument* mpDoc;
|
|
bool mbColHeaders:1;
|
|
bool mbRowHeaders:1;
|
|
bool mbDummyUpperLeft:1;
|
|
};
|
|
|
|
void Chart2Positioner::invalidateGlue()
|
|
{
|
|
meGlue = GLUETYPE_NA;
|
|
mpPositionMap.reset();
|
|
}
|
|
|
|
void Chart2Positioner::glueState()
|
|
{
|
|
if (meGlue != GLUETYPE_NA)
|
|
return;
|
|
|
|
mbDummyUpperLeft = false;
|
|
if (mrRefTokens.size() <= 1)
|
|
{
|
|
// Source data consists of only one data range.
|
|
const ScTokenRef& p = mrRefTokens.front();
|
|
ScComplexRefData aData;
|
|
if (ScRefTokenHelper::getDoubleRefDataFromToken(aData, p))
|
|
{
|
|
if (aData.Ref1.Tab() == aData.Ref2.Tab())
|
|
meGlue = GLUETYPE_NONE;
|
|
else
|
|
meGlue = GLUETYPE_COLS;
|
|
mnStartCol = aData.Ref1.Col();
|
|
mnStartRow = aData.Ref1.Row();
|
|
}
|
|
else
|
|
{
|
|
invalidateGlue();
|
|
mnStartCol = 0;
|
|
mnStartRow = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ScComplexRefData aData;
|
|
if (!ScRefTokenHelper::getDoubleRefDataFromToken(aData, mrRefTokens.front()))
|
|
{
|
|
SAL_WARN("sc", "Chart2Positioner::glueState getDoubleRefDataFromToken failed");
|
|
invalidateGlue();
|
|
mnStartCol = 0;
|
|
mnStartRow = 0;
|
|
return;
|
|
}
|
|
mnStartCol = aData.Ref1.Col();
|
|
mnStartRow = aData.Ref1.Row();
|
|
|
|
SCCOL nEndCol = 0;
|
|
SCROW nEndRow = 0;
|
|
for (const auto& rxToken : mrRefTokens)
|
|
{
|
|
ScRefTokenHelper::getDoubleRefDataFromToken(aData, rxToken);
|
|
SCCOLROW n1 = aData.Ref1.Col();
|
|
SCCOLROW n2 = aData.Ref2.Col();
|
|
if (n1 > mpDoc->MaxCol())
|
|
n1 = mpDoc->MaxCol();
|
|
if (n2 > mpDoc->MaxCol())
|
|
n2 = mpDoc->MaxCol();
|
|
if (n1 < mnStartCol)
|
|
mnStartCol = static_cast<SCCOL>(n1);
|
|
if (n2 > nEndCol)
|
|
nEndCol = static_cast<SCCOL>(n2);
|
|
|
|
n1 = aData.Ref1.Row();
|
|
n2 = aData.Ref2.Row();
|
|
if (n1 > mpDoc->MaxRow())
|
|
n1 = mpDoc->MaxRow();
|
|
if (n2 > mpDoc->MaxRow())
|
|
n2 = mpDoc->MaxRow();
|
|
|
|
if (n1 < mnStartRow)
|
|
mnStartRow = static_cast<SCROW>(n1);
|
|
if (n2 > nEndRow)
|
|
nEndRow = static_cast<SCROW>(n2);
|
|
}
|
|
|
|
if (mnStartCol == nEndCol)
|
|
{
|
|
// All source data is in a single column.
|
|
meGlue = GLUETYPE_ROWS;
|
|
return;
|
|
}
|
|
|
|
if (mnStartRow == nEndRow)
|
|
{
|
|
// All source data is in a single row.
|
|
meGlue = GLUETYPE_COLS;
|
|
return;
|
|
}
|
|
|
|
// total column size
|
|
SCCOL nC = nEndCol - mnStartCol + 1;
|
|
|
|
// total row size
|
|
SCROW nR = nEndRow - mnStartRow + 1;
|
|
|
|
// #i103540# prevent invalid vector size
|
|
if ((nC <= 0) || (nR <= 0))
|
|
{
|
|
invalidateGlue();
|
|
mnStartCol = 0;
|
|
mnStartRow = 0;
|
|
return;
|
|
}
|
|
|
|
calcGlueState(nC, nR);
|
|
}
|
|
|
|
enum State { Hole = 0, Occupied = 1, Free = 2, Glue = 3 };
|
|
|
|
void Chart2Positioner::calcGlueState(SCCOL nColSize, SCROW nRowSize)
|
|
{
|
|
// TODO: This code can use some space optimization. Using an array to
|
|
// store individual cell's states is terribly inefficient esp for large
|
|
// data ranges; let's use flat_segment_tree to reduce memory usage here.
|
|
|
|
sal_uInt32 nCR = static_cast<sal_uInt32>(nColSize*nRowSize);
|
|
|
|
vector<State> aCellStates(nCR, Hole);
|
|
|
|
// Mark all referenced cells "occupied".
|
|
for (const auto& rxToken : mrRefTokens)
|
|
{
|
|
ScComplexRefData aData;
|
|
ScRefTokenHelper::getDoubleRefDataFromToken(aData, rxToken);
|
|
auto nCol1 = aData.Ref1.Col() - mnStartCol;
|
|
auto nCol2 = aData.Ref2.Col() - mnStartCol;
|
|
SCROW nRow1 = aData.Ref1.Row() - mnStartRow;
|
|
SCROW nRow2 = aData.Ref2.Row() - mnStartRow;
|
|
for (SCCOLROW nCol = nCol1; nCol <= nCol2 && nCol >= 0; ++nCol)
|
|
for (SCCOLROW nRow = nRow1; nRow <= nRow2 && nRow >= 0; ++nRow)
|
|
{
|
|
size_t i = nCol*nRowSize + nRow;
|
|
aCellStates[i] = Occupied;
|
|
}
|
|
}
|
|
|
|
// If at least one cell in either the first column or first row is empty,
|
|
// we don't glue at all unless the whole column or row is empty; we expect
|
|
// all cells in the first column / row to be fully populated. If we have
|
|
// empty column or row, then we do glue by the column or row,
|
|
// respectively.
|
|
|
|
bool bGlue = true;
|
|
bool bGlueCols = false;
|
|
for (auto nCol = 0; bGlue && nCol < nColSize; ++nCol)
|
|
{
|
|
for (SCROW nRow = 0; bGlue && nRow < nRowSize; ++nRow)
|
|
{
|
|
size_t i = nCol*nRowSize + nRow;
|
|
if (aCellStates[i] == Occupied)
|
|
{
|
|
if (nCol == 0 || nRow == 0)
|
|
break;
|
|
|
|
bGlue = false;
|
|
}
|
|
else
|
|
aCellStates[i] = Free;
|
|
}
|
|
size_t nLast = (nCol+1)*nRowSize - 1; // index for the last cell in the column.
|
|
if (bGlue && aCellStates[nLast] == Free)
|
|
{
|
|
// Whole column is empty.
|
|
aCellStates[nLast] = Glue;
|
|
bGlueCols = true;
|
|
}
|
|
}
|
|
|
|
bool bGlueRows = false;
|
|
for (SCROW nRow = 0; bGlue && nRow < nRowSize; ++nRow)
|
|
{
|
|
size_t i = nRow;
|
|
for (SCCOL nCol = 0; bGlue && nCol < nColSize; ++nCol, i += nRowSize)
|
|
{
|
|
if (aCellStates[i] == Occupied)
|
|
{
|
|
if (nCol == 0 || nRow == 0)
|
|
break;
|
|
|
|
bGlue = false;
|
|
}
|
|
else
|
|
aCellStates[i] = Free;
|
|
}
|
|
i = (nColSize-1)*nRowSize + nRow; // index for the row position in the last column.
|
|
if (bGlue && aCellStates[i] == Free)
|
|
{
|
|
// Whole row is empty.
|
|
aCellStates[i] = Glue;
|
|
bGlueRows = true;
|
|
}
|
|
}
|
|
|
|
size_t i = 1;
|
|
for (sal_uInt32 n = 1; bGlue && n < nCR; ++n, ++i)
|
|
if (aCellStates[i] == Hole)
|
|
bGlue = false;
|
|
|
|
if (bGlue)
|
|
{
|
|
if (bGlueCols && bGlueRows)
|
|
meGlue = GLUETYPE_BOTH;
|
|
else if (bGlueRows)
|
|
meGlue = GLUETYPE_ROWS;
|
|
else
|
|
meGlue = GLUETYPE_COLS;
|
|
if (aCellStates.front() != Occupied)
|
|
mbDummyUpperLeft = true;
|
|
}
|
|
else
|
|
meGlue = GLUETYPE_NONE;
|
|
}
|
|
|
|
void Chart2Positioner::createPositionMap()
|
|
{
|
|
if (meGlue == GLUETYPE_NA && mpPositionMap)
|
|
mpPositionMap.reset();
|
|
|
|
if (mpPositionMap)
|
|
return;
|
|
|
|
glueState();
|
|
|
|
bool bNoGlue = (meGlue == GLUETYPE_NONE);
|
|
FormulaTokenMapMap aCols;
|
|
SCROW nNoGlueRow = 0;
|
|
for (const ScTokenRef& pToken : mrRefTokens)
|
|
{
|
|
bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
|
|
sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
|
|
svl::SharedString aTabName = svl::SharedString::getEmptyString();
|
|
if (bExternal)
|
|
aTabName = pToken->GetString();
|
|
|
|
ScComplexRefData aData;
|
|
if( !ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken) )
|
|
break;
|
|
const ScSingleRefData& s = aData.Ref1;
|
|
const ScSingleRefData& e = aData.Ref2;
|
|
SCCOL nCol1 = s.Col(), nCol2 = e.Col();
|
|
SCROW nRow1 = s.Row(), nRow2 = e.Row();
|
|
SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
|
|
assert(nTab2 < MAXTABCOUNT);
|
|
|
|
for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
|
|
{
|
|
// columns on secondary sheets are appended; we treat them as if
|
|
// all columns are on the same sheet. TODO: We can't assume that
|
|
// the column range is 16-bit; remove that restriction.
|
|
sal_uInt32 nInsCol = (static_cast<sal_uInt32>(nTab) << 16) |
|
|
(bNoGlue ? 0 : static_cast<sal_uInt32>(nCol1));
|
|
|
|
for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol)
|
|
{
|
|
FormulaTokenMap& rCol = aCols[nInsCol];
|
|
|
|
auto nInsRow = bNoGlue ? nNoGlueRow : nRow1;
|
|
for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow, ++nInsRow)
|
|
{
|
|
ScSingleRefData aCellData;
|
|
aCellData.InitFlags();
|
|
aCellData.SetFlag3D(true);
|
|
aCellData.SetColRel(false);
|
|
aCellData.SetRowRel(false);
|
|
aCellData.SetTabRel(false);
|
|
aCellData.SetAbsCol(nCol);
|
|
aCellData.SetAbsRow(nRow);
|
|
aCellData.SetAbsTab(nTab);
|
|
|
|
auto& rCell = rCol[nInsRow];
|
|
if (!rCell)
|
|
{
|
|
if (bExternal)
|
|
rCell.reset(new ScExternalSingleRefToken(nFileId, aTabName, aCellData));
|
|
else
|
|
rCell.reset(new ScSingleRefToken(mpDoc->GetSheetLimits(), aCellData));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nNoGlueRow += nRow2 - nRow1 + 1;
|
|
}
|
|
|
|
bool bFillRowHeader = mbRowHeaders;
|
|
bool bFillColumnHeader = mbColHeaders;
|
|
|
|
SCSIZE nAllColCount = static_cast<SCSIZE>(aCols.size());
|
|
SCSIZE nAllRowCount = 0;
|
|
if (!aCols.empty())
|
|
{
|
|
FormulaTokenMap& rCol = aCols.begin()->second;
|
|
if (mbDummyUpperLeft)
|
|
rCol.try_emplace( 0, nullptr ); // dummy for labeling
|
|
nAllRowCount = static_cast<SCSIZE>(rCol.size());
|
|
}
|
|
|
|
if( nAllColCount!=0 && nAllRowCount!=0 )
|
|
{
|
|
if (bNoGlue)
|
|
{
|
|
FormulaTokenMap& rFirstCol = aCols.begin()->second;
|
|
for (const auto& rFirstColEntry : rFirstCol)
|
|
{
|
|
SCROW nKey = rFirstColEntry.first;
|
|
for (auto& rEntry : aCols)
|
|
{
|
|
FormulaTokenMap& rCol = rEntry.second;
|
|
rCol.try_emplace( nKey, nullptr );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mpPositionMap.reset(
|
|
new Chart2PositionMap(
|
|
static_cast<SCCOL>(nAllColCount), static_cast<SCROW>(nAllRowCount),
|
|
bFillRowHeader, bFillColumnHeader, aCols, mpDoc));
|
|
}
|
|
|
|
/**
|
|
* Function object to create a range string from a token list.
|
|
*/
|
|
class Tokens2RangeString
|
|
{
|
|
public:
|
|
Tokens2RangeString(ScDocument& rDoc, FormulaGrammar::Grammar eGram, sal_Unicode cRangeSep) :
|
|
mpRangeStr(std::make_shared<OUStringBuffer>()),
|
|
mpDoc(&rDoc),
|
|
meGrammar(eGram),
|
|
mcRangeSep(cRangeSep),
|
|
mbFirst(true)
|
|
{
|
|
}
|
|
|
|
void operator() (const ScTokenRef& rToken)
|
|
{
|
|
ScCompiler aCompiler(*mpDoc, ScAddress(0,0,0), meGrammar);
|
|
OUString aStr;
|
|
aCompiler.CreateStringFromToken(aStr, rToken.get());
|
|
if (mbFirst)
|
|
mbFirst = false;
|
|
else
|
|
mpRangeStr->append(mcRangeSep);
|
|
mpRangeStr->append(aStr);
|
|
}
|
|
|
|
void getString(OUString& rStr)
|
|
{
|
|
rStr = mpRangeStr->makeStringAndClear();
|
|
}
|
|
|
|
private:
|
|
shared_ptr<OUStringBuffer> mpRangeStr;
|
|
ScDocument* mpDoc;
|
|
FormulaGrammar::Grammar meGrammar;
|
|
sal_Unicode mcRangeSep;
|
|
bool mbFirst;
|
|
};
|
|
|
|
/**
|
|
* Function object to convert a list of tokens into a string form suitable
|
|
* for ODF export. In ODF, a range is expressed as
|
|
*
|
|
* (start cell address):(end cell address)
|
|
*
|
|
* and each address doesn't include any '$' symbols.
|
|
*/
|
|
class Tokens2RangeStringXML
|
|
{
|
|
public:
|
|
explicit Tokens2RangeStringXML(ScDocument& rDoc) :
|
|
mpRangeStr(std::make_shared<OUStringBuffer>()),
|
|
mpDoc(&rDoc),
|
|
mbFirst(true)
|
|
{
|
|
}
|
|
|
|
void operator() (const ScTokenRef& rToken)
|
|
{
|
|
if (mbFirst)
|
|
mbFirst = false;
|
|
else
|
|
mpRangeStr->append(mcRangeSep);
|
|
|
|
ScTokenRef aStart, aEnd;
|
|
bool bValidToken = splitRangeToken(*mpDoc, rToken, aStart, aEnd);
|
|
// Check there is a valid reference in named range
|
|
if (!bValidToken && rToken->GetType() == svIndex && rToken->GetOpCode() == ocName)
|
|
{
|
|
ScRangeData* pNameRange = mpDoc->FindRangeNameBySheetAndIndex(rToken->GetSheet(), rToken->GetIndex());
|
|
if (pNameRange->HasReferences())
|
|
{
|
|
const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
|
|
bValidToken = splitRangeToken(*mpDoc, aTempToken, aStart, aEnd);
|
|
}
|
|
}
|
|
|
|
OSL_ENSURE(bValidToken, "invalid token");
|
|
if (!bValidToken)
|
|
return;
|
|
|
|
ScCompiler aCompiler(*mpDoc, ScAddress(0,0,0), FormulaGrammar::GRAM_ENGLISH);
|
|
{
|
|
OUString aStr;
|
|
aCompiler.CreateStringFromToken(aStr, aStart.get());
|
|
mpRangeStr->append(aStr);
|
|
}
|
|
mpRangeStr->append(mcAddrSep);
|
|
{
|
|
OUString aStr;
|
|
aCompiler.CreateStringFromToken(aStr, aEnd.get());
|
|
mpRangeStr->append(aStr);
|
|
}
|
|
}
|
|
|
|
void getString(OUString& rStr)
|
|
{
|
|
rStr = mpRangeStr->makeStringAndClear();
|
|
}
|
|
|
|
private:
|
|
static bool splitRangeToken(const ScDocument& rDoc, const ScTokenRef& pToken, ScTokenRef& rStart, ScTokenRef& rEnd)
|
|
{
|
|
ScComplexRefData aData;
|
|
bool bIsRefToken = ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken);
|
|
OSL_ENSURE(bIsRefToken, "invalid token");
|
|
if (!bIsRefToken)
|
|
return false;
|
|
bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
|
|
sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0;
|
|
const svl::SharedString aTabName = bExternal ? pToken->GetString() : svl::SharedString::getEmptyString();
|
|
|
|
// In saving to XML, we don't prepend address with '$'.
|
|
setRelative(aData.Ref1);
|
|
setRelative(aData.Ref2);
|
|
|
|
// In XML, the range must explicitly specify sheet name.
|
|
aData.Ref1.SetFlag3D(true);
|
|
aData.Ref2.SetFlag3D(true);
|
|
|
|
if (bExternal)
|
|
rStart.reset(new ScExternalSingleRefToken(nFileId, aTabName, aData.Ref1));
|
|
else
|
|
rStart.reset(new ScSingleRefToken(rDoc.GetSheetLimits(), aData.Ref1));
|
|
|
|
if (bExternal)
|
|
rEnd.reset(new ScExternalSingleRefToken(nFileId, aTabName, aData.Ref2));
|
|
else
|
|
rEnd.reset(new ScSingleRefToken(rDoc.GetSheetLimits(), aData.Ref2));
|
|
return true;
|
|
}
|
|
|
|
static void setRelative(ScSingleRefData& rData)
|
|
{
|
|
rData.SetColRel(true);
|
|
rData.SetRowRel(true);
|
|
rData.SetTabRel(true);
|
|
}
|
|
|
|
private:
|
|
shared_ptr<OUStringBuffer> mpRangeStr;
|
|
ScDocument* mpDoc;
|
|
static const sal_Unicode mcRangeSep = ' ';
|
|
static const sal_Unicode mcAddrSep = ':';
|
|
bool mbFirst;
|
|
};
|
|
|
|
void lcl_convertTokensToString(OUString& rStr, const vector<ScTokenRef>& rTokens, ScDocument& rDoc)
|
|
{
|
|
const sal_Unicode cRangeSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
FormulaGrammar::Grammar eGrammar = rDoc.GetGrammar();
|
|
Tokens2RangeString func(rDoc, eGrammar, cRangeSep);
|
|
func = ::std::for_each(rTokens.begin(), rTokens.end(), func);
|
|
func.getString(rStr);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
// DataProvider ==============================================================
|
|
|
|
ScChart2DataProvider::ScChart2DataProvider( ScDocument* pDoc )
|
|
: m_pDocument( pDoc)
|
|
, m_aPropSet(lcl_GetDataProviderPropertyMap())
|
|
, m_bIncludeHiddenCells( true)
|
|
{
|
|
if ( m_pDocument )
|
|
m_pDocument->AddUnoObject( *this);
|
|
}
|
|
|
|
ScChart2DataProvider::~ScChart2DataProvider()
|
|
{
|
|
SolarMutexGuard g;
|
|
|
|
if ( m_pDocument )
|
|
m_pDocument->RemoveUnoObject( *this);
|
|
}
|
|
|
|
void ScChart2DataProvider::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
|
|
{
|
|
if ( rHint.GetId() == SfxHintId::Dying )
|
|
{
|
|
m_pDocument = nullptr;
|
|
}
|
|
}
|
|
|
|
sal_Bool SAL_CALL ScChart2DataProvider::createDataSourcePossible( const uno::Sequence< beans::PropertyValue >& aArguments )
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if( ! m_pDocument )
|
|
return false;
|
|
|
|
OUString aRangeRepresentation;
|
|
for(const auto& rArgument : aArguments)
|
|
{
|
|
if ( rArgument.Name == "CellRangeRepresentation" )
|
|
{
|
|
rArgument.Value >>= aRangeRepresentation;
|
|
}
|
|
}
|
|
|
|
vector<ScTokenRef> aTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
return !aTokens.empty();
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
uno::Reference< chart2::data::XLabeledDataSequence > lcl_createLabeledDataSequenceFromTokens(
|
|
vector< ScTokenRef > && aValueTokens, vector< ScTokenRef > && aLabelTokens,
|
|
ScDocument* pDoc, bool bIncludeHiddenCells )
|
|
{
|
|
uno::Reference< chart2::data::XLabeledDataSequence > xResult;
|
|
bool bHasValues = !aValueTokens.empty();
|
|
bool bHasLabel = !aLabelTokens.empty();
|
|
if( bHasValues || bHasLabel )
|
|
{
|
|
try
|
|
{
|
|
const uno::Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
|
|
if ( xContext.is() )
|
|
{
|
|
xResult.set( chart2::data::LabeledDataSequence::create(xContext), uno::UNO_QUERY_THROW );
|
|
}
|
|
if ( bHasValues )
|
|
{
|
|
uno::Reference< chart2::data::XDataSequence > xSeq( new ScChart2DataSequence( pDoc, std::move(aValueTokens), bIncludeHiddenCells ) );
|
|
xResult->setValues( xSeq );
|
|
}
|
|
if ( bHasLabel )
|
|
{
|
|
//Labels should always include hidden cells, regardless of the bIncludeHiddenCells setting
|
|
uno::Reference< chart2::data::XDataSequence > xLabelSeq( new ScChart2DataSequence( pDoc, std::move(aLabelTokens), true ) );
|
|
xResult->setLabel( xLabelSeq );
|
|
}
|
|
}
|
|
catch( const uno::Exception& )
|
|
{
|
|
}
|
|
}
|
|
return xResult;
|
|
}
|
|
|
|
/**
|
|
* Check the current list of reference tokens, and add the upper left
|
|
* corner of the minimum range that encloses all ranges if certain
|
|
* conditions are met.
|
|
*
|
|
* @param rRefTokens list of reference tokens
|
|
*
|
|
* @return true if the corner was added, false otherwise.
|
|
*/
|
|
bool lcl_addUpperLeftCornerIfMissing(const ScDocument* pDoc, vector<ScTokenRef>& rRefTokens,
|
|
SCROW nCornerRowCount, SCCOL nCornerColumnCount)
|
|
{
|
|
using ::std::max;
|
|
using ::std::min;
|
|
|
|
if (rRefTokens.empty())
|
|
return false;
|
|
|
|
SCCOL nMinCol = pDoc->GetSheetLimits().GetMaxColCount();
|
|
SCROW nMinRow = pDoc->GetSheetLimits().GetMaxRowCount();
|
|
SCCOL nMaxCol = 0;
|
|
SCROW nMaxRow = 0;
|
|
SCTAB nTab = 0;
|
|
|
|
sal_uInt16 nFileId = 0;
|
|
svl::SharedString aExtTabName;
|
|
bool bExternal = false;
|
|
|
|
vector<ScTokenRef>::const_iterator itr = rRefTokens.begin(), itrEnd = rRefTokens.end();
|
|
|
|
// Get the first ref token.
|
|
ScTokenRef pToken = *itr;
|
|
switch (pToken->GetType())
|
|
{
|
|
case svSingleRef:
|
|
{
|
|
const ScSingleRefData& rData = *pToken->GetSingleRef();
|
|
nMinCol = rData.Col();
|
|
nMinRow = rData.Row();
|
|
nMaxCol = rData.Col();
|
|
nMaxRow = rData.Row();
|
|
nTab = rData.Tab();
|
|
}
|
|
break;
|
|
case svDoubleRef:
|
|
{
|
|
const ScComplexRefData& rData = *pToken->GetDoubleRef();
|
|
nMinCol = min(rData.Ref1.Col(), rData.Ref2.Col());
|
|
nMinRow = min(rData.Ref1.Row(), rData.Ref2.Row());
|
|
nMaxCol = max(rData.Ref1.Col(), rData.Ref2.Col());
|
|
nMaxRow = max(rData.Ref1.Row(), rData.Ref2.Row());
|
|
nTab = rData.Ref1.Tab();
|
|
}
|
|
break;
|
|
case svExternalSingleRef:
|
|
{
|
|
const ScSingleRefData& rData = *pToken->GetSingleRef();
|
|
nMinCol = rData.Col();
|
|
nMinRow = rData.Row();
|
|
nMaxCol = rData.Col();
|
|
nMaxRow = rData.Row();
|
|
nTab = rData.Tab();
|
|
nFileId = pToken->GetIndex();
|
|
aExtTabName = pToken->GetString();
|
|
bExternal = true;
|
|
}
|
|
break;
|
|
case svExternalDoubleRef:
|
|
{
|
|
const ScComplexRefData& rData = *pToken->GetDoubleRef();
|
|
nMinCol = min(rData.Ref1.Col(), rData.Ref2.Col());
|
|
nMinRow = min(rData.Ref1.Row(), rData.Ref2.Row());
|
|
nMaxCol = max(rData.Ref1.Col(), rData.Ref2.Col());
|
|
nMaxRow = max(rData.Ref1.Row(), rData.Ref2.Row());
|
|
nTab = rData.Ref1.Tab();
|
|
nFileId = pToken->GetIndex();
|
|
aExtTabName = pToken->GetString();
|
|
bExternal = true;
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
// Determine the minimum range enclosing all data ranges. Also make sure
|
|
// that they are all on the same table.
|
|
|
|
for (++itr; itr != itrEnd; ++itr)
|
|
{
|
|
pToken = *itr;
|
|
switch (pToken->GetType())
|
|
{
|
|
case svSingleRef:
|
|
{
|
|
const ScSingleRefData& rData = *pToken->GetSingleRef();
|
|
|
|
nMinCol = min(nMinCol, rData.Col());
|
|
nMinRow = min(nMinRow, rData.Row());
|
|
nMaxCol = max(nMaxCol, rData.Col());
|
|
nMaxRow = max(nMaxRow, rData.Row());
|
|
if (nTab != rData.Tab() || bExternal)
|
|
return false;
|
|
}
|
|
break;
|
|
case svDoubleRef:
|
|
{
|
|
const ScComplexRefData& rData = *pToken->GetDoubleRef();
|
|
|
|
nMinCol = min(nMinCol, rData.Ref1.Col());
|
|
nMinCol = min(nMinCol, rData.Ref2.Col());
|
|
nMinRow = min(nMinRow, rData.Ref1.Row());
|
|
nMinRow = min(nMinRow, rData.Ref2.Row());
|
|
|
|
nMaxCol = max(nMaxCol, rData.Ref1.Col());
|
|
nMaxCol = max(nMaxCol, rData.Ref2.Col());
|
|
nMaxRow = max(nMaxRow, rData.Ref1.Row());
|
|
nMaxRow = max(nMaxRow, rData.Ref2.Row());
|
|
|
|
if (nTab != rData.Ref1.Tab() || bExternal)
|
|
return false;
|
|
}
|
|
break;
|
|
case svExternalSingleRef:
|
|
{
|
|
if (!bExternal)
|
|
return false;
|
|
|
|
if (nFileId != pToken->GetIndex() || aExtTabName != pToken->GetString())
|
|
return false;
|
|
|
|
const ScSingleRefData& rData = *pToken->GetSingleRef();
|
|
|
|
nMinCol = min(nMinCol, rData.Col());
|
|
nMinRow = min(nMinRow, rData.Row());
|
|
nMaxCol = max(nMaxCol, rData.Col());
|
|
nMaxRow = max(nMaxRow, rData.Row());
|
|
}
|
|
break;
|
|
case svExternalDoubleRef:
|
|
{
|
|
if (!bExternal)
|
|
return false;
|
|
|
|
if (nFileId != pToken->GetIndex() || aExtTabName != pToken->GetString())
|
|
return false;
|
|
|
|
const ScComplexRefData& rData = *pToken->GetDoubleRef();
|
|
|
|
nMinCol = min(nMinCol, rData.Ref1.Col());
|
|
nMinCol = min(nMinCol, rData.Ref2.Col());
|
|
nMinRow = min(nMinRow, rData.Ref1.Row());
|
|
nMinRow = min(nMinRow, rData.Ref2.Row());
|
|
|
|
nMaxCol = max(nMaxCol, rData.Ref1.Col());
|
|
nMaxCol = max(nMaxCol, rData.Ref2.Col());
|
|
nMaxRow = max(nMaxRow, rData.Ref1.Row());
|
|
nMaxRow = max(nMaxRow, rData.Ref2.Row());
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
const auto & rSheetLimits = pDoc->GetSheetLimits();
|
|
if (nMinRow >= nMaxRow || nMinCol >= nMaxCol ||
|
|
nMinRow >= rSheetLimits.GetMaxRowCount() || nMinCol >= rSheetLimits.GetMaxColCount() ||
|
|
nMaxRow >= rSheetLimits.GetMaxRowCount() || nMaxCol >= rSheetLimits.GetMaxColCount())
|
|
{
|
|
// Invalid range. Bail out.
|
|
return false;
|
|
}
|
|
|
|
// Check if the following conditions are met:
|
|
|
|
// 1) The upper-left corner cell is not included.
|
|
// 2) The three adjacent cells of that corner cell are included.
|
|
|
|
bool bRight = false, bBottom = false, bDiagonal = false;
|
|
for (const auto& rxToken : rRefTokens)
|
|
{
|
|
switch (rxToken->GetType())
|
|
{
|
|
case svSingleRef:
|
|
case svExternalSingleRef:
|
|
{
|
|
const ScSingleRefData& rData = *rxToken->GetSingleRef();
|
|
if (rData.Col() == nMinCol && rData.Row() == nMinRow)
|
|
// The corner cell is contained.
|
|
return false;
|
|
|
|
if (rData.Col() == nMinCol+nCornerColumnCount && rData.Row() == nMinRow)
|
|
bRight = true;
|
|
|
|
if (rData.Col() == nMinCol && rData.Row() == nMinRow+nCornerRowCount)
|
|
bBottom = true;
|
|
|
|
if (rData.Col() == nMinCol+nCornerColumnCount && rData.Row() == nMinRow+nCornerRowCount)
|
|
bDiagonal = true;
|
|
}
|
|
break;
|
|
case svDoubleRef:
|
|
case svExternalDoubleRef:
|
|
{
|
|
const ScComplexRefData& rData = *rxToken->GetDoubleRef();
|
|
const ScSingleRefData& r1 = rData.Ref1;
|
|
const ScSingleRefData& r2 = rData.Ref2;
|
|
if (r1.Col() <= nMinCol && nMinCol <= r2.Col() &&
|
|
r1.Row() <= nMinRow && nMinRow <= r2.Row())
|
|
// The corner cell is contained.
|
|
return false;
|
|
|
|
if (r1.Col() <= nMinCol+nCornerColumnCount && nMinCol+nCornerColumnCount <= r2.Col() &&
|
|
r1.Row() <= nMinRow && nMinRow <= r2.Row())
|
|
bRight = true;
|
|
|
|
if (r1.Col() <= nMinCol && nMinCol <= r2.Col() &&
|
|
r1.Row() <= nMinRow+nCornerRowCount && nMinRow+nCornerRowCount <= r2.Row())
|
|
bBottom = true;
|
|
|
|
if (r1.Col() <= nMinCol+nCornerColumnCount && nMinCol+nCornerColumnCount <= r2.Col() &&
|
|
r1.Row() <= nMinRow+nCornerRowCount && nMinRow+nCornerRowCount <= r2.Row())
|
|
bDiagonal = true;
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
if (!bRight || !bBottom || !bDiagonal)
|
|
// Not all the adjacent cells are included. Bail out.
|
|
return false;
|
|
|
|
ScSingleRefData aData;
|
|
aData.InitFlags();
|
|
aData.SetFlag3D(true);
|
|
aData.SetAbsCol(nMinCol);
|
|
aData.SetAbsRow(nMinRow);
|
|
aData.SetAbsTab(nTab);
|
|
|
|
if( nCornerRowCount==1 && nCornerColumnCount==1 )
|
|
{
|
|
if (bExternal)
|
|
{
|
|
ScTokenRef pCorner(
|
|
new ScExternalSingleRefToken(nFileId, std::move(aExtTabName), aData));
|
|
ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
|
|
}
|
|
else
|
|
{
|
|
ScTokenRef pCorner(new ScSingleRefToken(pDoc->GetSheetLimits(), aData));
|
|
ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScSingleRefData aDataEnd(aData);
|
|
aDataEnd.IncCol(nCornerColumnCount-1);
|
|
aDataEnd.IncRow(nCornerRowCount-1);
|
|
ScComplexRefData r;
|
|
r.Ref1=aData;
|
|
r.Ref2=aDataEnd;
|
|
if (bExternal)
|
|
{
|
|
ScTokenRef pCorner(
|
|
new ScExternalDoubleRefToken(nFileId, std::move(aExtTabName), r));
|
|
ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
|
|
}
|
|
else
|
|
{
|
|
ScTokenRef pCorner(new ScDoubleRefToken(pDoc->GetSheetLimits(), r));
|
|
ScRefTokenHelper::join(pDoc, rRefTokens, pCorner, ScAddress());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define SHRINK_RANGE_THRESHOLD 10000
|
|
|
|
class ShrinkRefTokenToDataRange
|
|
{
|
|
ScDocument* mpDoc;
|
|
public:
|
|
explicit ShrinkRefTokenToDataRange(ScDocument* pDoc) : mpDoc(pDoc) {}
|
|
void operator() (const ScTokenRef& rRef)
|
|
{
|
|
if (ScRefTokenHelper::isExternalRef(rRef))
|
|
return;
|
|
|
|
// Don't assume an ScDoubleRefToken if it isn't. It can be at least an
|
|
// ScSingleRefToken, then there isn't anything to shrink.
|
|
if (rRef->GetType() != svDoubleRef)
|
|
return;
|
|
|
|
ScComplexRefData& rData = *rRef->GetDoubleRef();
|
|
ScSingleRefData& s = rData.Ref1;
|
|
ScSingleRefData& e = rData.Ref2;
|
|
|
|
if(abs((e.Col()-s.Col())*(e.Row()-s.Row())) < SHRINK_RANGE_THRESHOLD)
|
|
return;
|
|
|
|
SCCOL nMinCol = mpDoc->MaxCol(), nMaxCol = 0;
|
|
SCROW nMinRow = mpDoc->MaxRow(), nMaxRow = 0;
|
|
|
|
// Determine the smallest range that encompasses the data ranges of all sheets.
|
|
SCTAB nTab1 = s.Tab(), nTab2 = e.Tab();
|
|
for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
|
|
{
|
|
SCCOL nCol1 = 0, nCol2 = mpDoc->MaxCol();
|
|
SCROW nRow1 = 0, nRow2 = mpDoc->MaxRow();
|
|
mpDoc->ShrinkToDataArea(nTab, nCol1, nRow1, nCol2, nRow2);
|
|
nMinCol = std::min(nMinCol, nCol1);
|
|
nMinRow = std::min(nMinRow, nRow1);
|
|
nMaxCol = std::max(nMaxCol, nCol2);
|
|
nMaxRow = std::max(nMaxRow, nRow2);
|
|
}
|
|
|
|
// Shrink range to the data range if applicable.
|
|
if (s.Col() < nMinCol)
|
|
s.SetAbsCol(nMinCol);
|
|
if (s.Row() < nMinRow)
|
|
s.SetAbsRow(nMinRow);
|
|
if (e.Col() > nMaxCol)
|
|
e.SetAbsCol(nMaxCol);
|
|
if (e.Row() > nMaxRow)
|
|
e.SetAbsRow(nMaxRow);
|
|
}
|
|
};
|
|
|
|
void shrinkToDataRange(ScDocument* pDoc, vector<ScTokenRef>& rRefTokens)
|
|
{
|
|
std::for_each(rRefTokens.begin(), rRefTokens.end(), ShrinkRefTokenToDataRange(pDoc));
|
|
}
|
|
|
|
}
|
|
|
|
uno::Reference< chart2::data::XDataSource> SAL_CALL
|
|
ScChart2DataProvider::createDataSource(
|
|
const uno::Sequence< beans::PropertyValue >& aArguments )
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if ( ! m_pDocument )
|
|
throw uno::RuntimeException();
|
|
|
|
uno::Reference< chart2::data::XDataSource> xResult;
|
|
bool bLabel = true;
|
|
bool bCategories = false;
|
|
bool bOrientCol = true;
|
|
OUString aRangeRepresentation;
|
|
uno::Sequence< sal_Int32 > aSequenceMapping;
|
|
bool bTimeBased = false;
|
|
for(const auto& rArgument : aArguments)
|
|
{
|
|
if ( rArgument.Name == "DataRowSource" )
|
|
{
|
|
chart::ChartDataRowSource eSource = chart::ChartDataRowSource_COLUMNS;
|
|
if( ! (rArgument.Value >>= eSource))
|
|
{
|
|
sal_Int32 nSource(0);
|
|
if( rArgument.Value >>= nSource )
|
|
eSource = static_cast< chart::ChartDataRowSource >( nSource );
|
|
}
|
|
bOrientCol = (eSource == chart::ChartDataRowSource_COLUMNS);
|
|
}
|
|
else if ( rArgument.Name == "FirstCellAsLabel" )
|
|
{
|
|
bLabel = ::cppu::any2bool(rArgument.Value);
|
|
}
|
|
else if ( rArgument.Name == "HasCategories" )
|
|
{
|
|
bCategories = ::cppu::any2bool(rArgument.Value);
|
|
}
|
|
else if ( rArgument.Name == "CellRangeRepresentation" )
|
|
{
|
|
rArgument.Value >>= aRangeRepresentation;
|
|
}
|
|
else if ( rArgument.Name == "SequenceMapping" )
|
|
{
|
|
rArgument.Value >>= aSequenceMapping;
|
|
}
|
|
else if ( rArgument.Name == "TimeBased" )
|
|
{
|
|
rArgument.Value >>= bTimeBased;
|
|
}
|
|
}
|
|
|
|
vector<ScTokenRef> aRefTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aRefTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
if (aRefTokens.empty())
|
|
// Invalid range representation. Bail out.
|
|
throw lang::IllegalArgumentException();
|
|
|
|
SCTAB nTimeBasedStart = MAXTAB;
|
|
SCTAB nTimeBasedEnd = 0;
|
|
if(bTimeBased)
|
|
{
|
|
// limit to first sheet
|
|
for(const auto& rxToken : aRefTokens)
|
|
{
|
|
if (rxToken->GetType() != svDoubleRef)
|
|
continue;
|
|
|
|
ScComplexRefData& rData = *rxToken->GetDoubleRef();
|
|
ScSingleRefData& s = rData.Ref1;
|
|
ScSingleRefData& e = rData.Ref2;
|
|
|
|
nTimeBasedStart = std::min(nTimeBasedStart, s.Tab());
|
|
nTimeBasedEnd = std::min(nTimeBasedEnd, e.Tab());
|
|
|
|
if(s.Tab() != e.Tab())
|
|
e.SetAbsTab(s.Tab());
|
|
}
|
|
}
|
|
|
|
if(!bTimeBased)
|
|
shrinkToDataRange(m_pDocument, aRefTokens);
|
|
|
|
if (bLabel)
|
|
lcl_addUpperLeftCornerIfMissing(m_pDocument, aRefTokens, 1, 1); //#i90669#
|
|
|
|
bool bColHeaders = (bOrientCol ? bLabel : bCategories );
|
|
bool bRowHeaders = (bOrientCol ? bCategories : bLabel );
|
|
|
|
Chart2Positioner aChPositioner(m_pDocument, aRefTokens);
|
|
aChPositioner.setHeaders(bColHeaders, bRowHeaders);
|
|
|
|
const Chart2PositionMap* pChartMap = aChPositioner.getPositionMap();
|
|
if (!pChartMap)
|
|
// No chart position map instance. Bail out.
|
|
return xResult;
|
|
|
|
rtl::Reference<ScChart2DataSource> pDS;
|
|
::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSeqs;
|
|
|
|
// Fill Categories
|
|
if( bCategories )
|
|
{
|
|
vector<ScTokenRef> aValueTokens;
|
|
if (bOrientCol)
|
|
aValueTokens = pChartMap->getAllRowHeaderRanges();
|
|
else
|
|
aValueTokens = pChartMap->getAllColHeaderRanges();
|
|
|
|
vector<ScTokenRef> aLabelTokens(
|
|
pChartMap->getLeftUpperCornerRanges());
|
|
|
|
uno::Reference< chart2::data::XLabeledDataSequence > xCategories = lcl_createLabeledDataSequenceFromTokens(
|
|
std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, m_bIncludeHiddenCells ); //ownership of pointers is transferred!
|
|
if ( xCategories.is() )
|
|
{
|
|
aSeqs.push_back( xCategories );
|
|
}
|
|
}
|
|
|
|
// Fill Series (values and label)
|
|
sal_Int32 nCount = bOrientCol ? pChartMap->getDataColCount() : pChartMap->getDataRowCount();
|
|
for (sal_Int32 i = 0; i < nCount; ++i)
|
|
{
|
|
vector<ScTokenRef> aValueTokens;
|
|
vector<ScTokenRef> aLabelTokens;
|
|
if (bOrientCol)
|
|
{
|
|
aValueTokens = pChartMap->getDataColRanges(static_cast<SCCOL>(i));
|
|
aLabelTokens = pChartMap->getColHeaderRanges(static_cast<SCCOL>(i));
|
|
}
|
|
else
|
|
{
|
|
aValueTokens = pChartMap->getDataRowRanges(static_cast<SCROW>(i));
|
|
aLabelTokens = pChartMap->getRowHeaderRanges(static_cast<SCROW>(i));
|
|
}
|
|
uno::Reference< chart2::data::XLabeledDataSequence > xChartSeries = lcl_createLabeledDataSequenceFromTokens(
|
|
std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, m_bIncludeHiddenCells ); //ownership of pointers is transferred!
|
|
if ( xChartSeries.is() && xChartSeries->getValues().is() && xChartSeries->getValues()->getData().hasElements() )
|
|
{
|
|
aSeqs.push_back( xChartSeries );
|
|
}
|
|
}
|
|
|
|
pDS = new ScChart2DataSource(m_pDocument);
|
|
|
|
//reorder labeled sequences according to aSequenceMapping
|
|
::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSeqVector;
|
|
aSeqVector.reserve(aSeqs.size());
|
|
for (auto const& aSeq : aSeqs)
|
|
{
|
|
aSeqVector.push_back(aSeq);
|
|
}
|
|
|
|
for (const sal_Int32 nNewIndex : aSequenceMapping)
|
|
{
|
|
// note: assuming that the values in the sequence mapping are always non-negative
|
|
::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > >::size_type nOldIndex( static_cast< sal_uInt32 >( nNewIndex ) );
|
|
if( nOldIndex < aSeqVector.size() )
|
|
{
|
|
pDS->AddLabeledSequence( aSeqVector[nOldIndex] );
|
|
aSeqVector[nOldIndex] = nullptr;
|
|
}
|
|
}
|
|
|
|
for(const uno::Reference< chart2::data::XLabeledDataSequence >& xSeq : aSeqVector)
|
|
{
|
|
if ( xSeq.is() )
|
|
{
|
|
pDS->AddLabeledSequence( xSeq );
|
|
}
|
|
}
|
|
|
|
xResult.set( pDS );
|
|
return xResult;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
/**
|
|
* Function object to create a list of table numbers from a token list.
|
|
*/
|
|
class InsertTabNumber
|
|
{
|
|
public:
|
|
InsertTabNumber() :
|
|
mpTabNumVector(std::make_shared<vector<SCTAB>>())
|
|
{
|
|
}
|
|
|
|
void operator() (const ScTokenRef& pToken) const
|
|
{
|
|
if (!ScRefTokenHelper::isRef(pToken))
|
|
return;
|
|
|
|
const ScSingleRefData& r = *pToken->GetSingleRef();
|
|
mpTabNumVector->push_back(r.Tab());
|
|
}
|
|
|
|
void getVector(vector<SCTAB>& rVector)
|
|
{
|
|
mpTabNumVector->swap(rVector);
|
|
}
|
|
private:
|
|
shared_ptr< vector<SCTAB> > mpTabNumVector;
|
|
};
|
|
|
|
class RangeAnalyzer
|
|
{
|
|
public:
|
|
RangeAnalyzer();
|
|
void initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& rTokens );
|
|
void analyzeRange( sal_Int32& rnDataInRows, sal_Int32& rnDataInCols,
|
|
bool& rbRowSourceAmbiguous ) const;
|
|
bool inSameSingleRow( const RangeAnalyzer& rOther );
|
|
bool inSameSingleColumn( const RangeAnalyzer& rOther );
|
|
SCROW getRowCount() const { return mnRowCount; }
|
|
SCCOL getColumnCount() const { return mnColumnCount; }
|
|
|
|
private:
|
|
bool mbEmpty;
|
|
bool mbAmbiguous;
|
|
SCROW mnRowCount;
|
|
SCCOL mnColumnCount;
|
|
|
|
SCCOL mnStartColumn;
|
|
SCROW mnStartRow;
|
|
};
|
|
|
|
RangeAnalyzer::RangeAnalyzer()
|
|
: mbEmpty(true)
|
|
, mbAmbiguous(false)
|
|
, mnRowCount(0)
|
|
, mnColumnCount(0)
|
|
, mnStartColumn(-1)
|
|
, mnStartRow(-1)
|
|
{
|
|
}
|
|
|
|
void RangeAnalyzer::initRangeAnalyzer( const ScDocument* pDoc, const vector<ScTokenRef>& rTokens )
|
|
{
|
|
mnRowCount=0;
|
|
mnColumnCount=0;
|
|
mnStartColumn = -1;
|
|
mnStartRow = -1;
|
|
mbAmbiguous=false;
|
|
if( rTokens.empty() )
|
|
{
|
|
mbEmpty=true;
|
|
return;
|
|
}
|
|
mbEmpty=false;
|
|
|
|
for (const ScTokenRef& aRefToken : rTokens)
|
|
{
|
|
StackVar eVar = aRefToken->GetType();
|
|
if (eVar == svDoubleRef || eVar == svExternalDoubleRef)
|
|
{
|
|
const ScComplexRefData& r = *aRefToken->GetDoubleRef();
|
|
if (r.Ref1.Tab() == r.Ref2.Tab())
|
|
{
|
|
mnColumnCount = std::max<SCCOL>(mnColumnCount, static_cast<SCCOL>(abs(r.Ref2.Col() - r.Ref1.Col())+1));
|
|
mnRowCount = std::max<SCROW>(mnRowCount, static_cast<SCROW>(abs(r.Ref2.Row() - r.Ref1.Row())+1));
|
|
if( mnStartColumn == -1 )
|
|
{
|
|
mnStartColumn = r.Ref1.Col();
|
|
mnStartRow = r.Ref1.Row();
|
|
}
|
|
else
|
|
{
|
|
if (mnStartColumn != r.Ref1.Col() && mnStartRow != r.Ref1.Row())
|
|
mbAmbiguous=true;
|
|
}
|
|
}
|
|
else
|
|
mbAmbiguous=true;
|
|
}
|
|
else if (eVar == svSingleRef || eVar == svExternalSingleRef)
|
|
{
|
|
const ScSingleRefData& r = *aRefToken->GetSingleRef();
|
|
mnColumnCount = std::max<SCCOL>( mnColumnCount, 1);
|
|
mnRowCount = std::max<SCROW>( mnRowCount, 1);
|
|
if( mnStartColumn == -1 )
|
|
{
|
|
mnStartColumn = r.Col();
|
|
mnStartRow = r.Row();
|
|
}
|
|
else
|
|
{
|
|
if (mnStartColumn != r.Col() && mnStartRow != r.Row())
|
|
mbAmbiguous=true;
|
|
}
|
|
}
|
|
else if (eVar == svIndex && aRefToken->GetOpCode() == ocName)
|
|
{
|
|
ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(aRefToken->GetSheet(), aRefToken->GetIndex());
|
|
ScRange aRange;
|
|
if (pNameRange->IsReference(aRange))
|
|
{
|
|
mnColumnCount = std::max<SCCOL>(mnColumnCount, static_cast<SCCOL>(abs(aRange.aEnd.Col() - aRange.aStart.Col()) + 1));
|
|
mnRowCount = std::max<SCROW>(mnRowCount, static_cast<SCROW>(abs(aRange.aEnd.Row() - aRange.aStart.Row()) + 1));
|
|
if (mnStartColumn == -1)
|
|
{
|
|
mnStartColumn = aRange.aStart.Col();
|
|
mnStartRow = aRange.aStart.Row();
|
|
}
|
|
else
|
|
{
|
|
if (mnStartColumn != aRange.aStart.Col() && mnStartRow != aRange.aStart.Row())
|
|
mbAmbiguous = true;
|
|
}
|
|
}
|
|
else
|
|
mbAmbiguous = true;
|
|
}
|
|
else
|
|
mbAmbiguous=true;
|
|
}
|
|
}
|
|
|
|
void RangeAnalyzer::analyzeRange( sal_Int32& rnDataInRows,
|
|
sal_Int32& rnDataInCols,
|
|
bool& rbRowSourceAmbiguous ) const
|
|
{
|
|
if(!mbEmpty && !mbAmbiguous)
|
|
{
|
|
if( mnRowCount==1 && mnColumnCount>1 )
|
|
++rnDataInRows;
|
|
else if( mnColumnCount==1 && mnRowCount>1 )
|
|
++rnDataInCols;
|
|
else if( mnRowCount>1 && mnColumnCount>1 )
|
|
rbRowSourceAmbiguous = true;
|
|
}
|
|
else if( !mbEmpty )
|
|
rbRowSourceAmbiguous = true;
|
|
}
|
|
|
|
bool RangeAnalyzer::inSameSingleRow( const RangeAnalyzer& rOther )
|
|
{
|
|
return mnStartRow==rOther.mnStartRow &&
|
|
mnRowCount==1 && rOther.mnRowCount==1;
|
|
}
|
|
|
|
bool RangeAnalyzer::inSameSingleColumn( const RangeAnalyzer& rOther )
|
|
{
|
|
return mnStartColumn==rOther.mnStartColumn &&
|
|
mnColumnCount==1 && rOther.mnColumnCount==1;
|
|
}
|
|
|
|
std::pair<OUString, OUString> constructKey(const uno::Reference< chart2::data::XLabeledDataSequence>& xNew)
|
|
{
|
|
std::pair<OUString, OUString> aKey;
|
|
if( xNew->getLabel().is() )
|
|
aKey.first = xNew->getLabel()->getSourceRangeRepresentation();
|
|
if( xNew->getValues().is() )
|
|
aKey.second = xNew->getValues()->getSourceRangeRepresentation();
|
|
return aKey;
|
|
}
|
|
|
|
|
|
} //end anonymous namespace
|
|
|
|
uno::Sequence< beans::PropertyValue > SAL_CALL ScChart2DataProvider::detectArguments(
|
|
const uno::Reference< chart2::data::XDataSource >& xDataSource )
|
|
{
|
|
::std::vector< beans::PropertyValue > aResult;
|
|
bool bRowSourceDetected = false;
|
|
bool bFirstCellAsLabel = false;
|
|
bool bHasCategories = false;
|
|
OUString sRangeRep;
|
|
|
|
bool bHasCategoriesLabels = false;
|
|
vector<ScTokenRef> aAllCategoriesValuesTokens;
|
|
vector<ScTokenRef> aAllSeriesLabelTokens;
|
|
|
|
chart::ChartDataRowSource eRowSource = chart::ChartDataRowSource_COLUMNS;
|
|
|
|
vector<ScTokenRef> aAllTokens;
|
|
|
|
// parse given data source and collect infos
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
OSL_ENSURE( m_pDocument, "No Document -> no detectArguments" );
|
|
if(!m_pDocument ||!xDataSource.is())
|
|
return comphelper::containerToSequence( aResult );
|
|
|
|
sal_Int32 nDataInRows = 0;
|
|
sal_Int32 nDataInCols = 0;
|
|
bool bRowSourceAmbiguous = false;
|
|
|
|
const Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aSequences( xDataSource->getDataSequences());
|
|
const sal_Int32 nCount( aSequences.getLength());
|
|
RangeAnalyzer aPrevLabel,aPrevValues;
|
|
for( const uno::Reference< chart2::data::XLabeledDataSequence >& xLS : aSequences )
|
|
{
|
|
if( xLS.is() )
|
|
{
|
|
bool bThisIsCategories = false;
|
|
if(!bHasCategories)
|
|
{
|
|
uno::Reference< beans::XPropertySet > xSeqProp( xLS->getValues(), uno::UNO_QUERY );
|
|
OUString aRole;
|
|
if( xSeqProp.is() && (xSeqProp->getPropertyValue(u"Role"_ustr) >>= aRole) &&
|
|
aRole == "categories" )
|
|
bThisIsCategories = bHasCategories = true;
|
|
}
|
|
|
|
RangeAnalyzer aLabel,aValues;
|
|
// label
|
|
uno::Reference< chart2::data::XDataSequence > xLabel( xLS->getLabel());
|
|
if( xLabel.is())
|
|
{
|
|
bFirstCellAsLabel = true;
|
|
vector<ScTokenRef> aTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aTokens, xLabel->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
aLabel.initRangeAnalyzer(m_pDocument, aTokens);
|
|
for (const auto& rxToken : aTokens)
|
|
{
|
|
if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName)
|
|
{
|
|
ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex());
|
|
if (pNameRange->HasReferences())
|
|
{
|
|
const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress());
|
|
}
|
|
else
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
|
|
}
|
|
else
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
|
|
if(!bThisIsCategories)
|
|
ScRefTokenHelper::join(m_pDocument, aAllSeriesLabelTokens, rxToken, ScAddress());
|
|
}
|
|
if(bThisIsCategories)
|
|
bHasCategoriesLabels=true;
|
|
}
|
|
// values
|
|
uno::Reference< chart2::data::XDataSequence > xValues( xLS->getValues());
|
|
if( xValues.is())
|
|
{
|
|
vector<ScTokenRef> aTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aTokens, xValues->getSourceRangeRepresentation(), *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
aValues.initRangeAnalyzer(m_pDocument, aTokens);
|
|
for (const auto& rxToken : aTokens)
|
|
{
|
|
if (rxToken->GetType() == svIndex && rxToken->GetOpCode() == ocName)
|
|
{
|
|
ScRangeData* pNameRange = m_pDocument->FindRangeNameBySheetAndIndex(rxToken->GetSheet(), rxToken->GetIndex());
|
|
if (pNameRange->HasReferences())
|
|
{
|
|
const ScTokenRef aTempToken = pNameRange->GetCode()->FirstToken();
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, aTempToken, ScAddress());
|
|
}
|
|
else
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
|
|
}
|
|
else
|
|
ScRefTokenHelper::join(m_pDocument, aAllTokens, rxToken, ScAddress());
|
|
if(bThisIsCategories)
|
|
ScRefTokenHelper::join(m_pDocument, aAllCategoriesValuesTokens, rxToken, ScAddress());
|
|
}
|
|
}
|
|
//detect row source
|
|
if(!bThisIsCategories || nCount==1) //categories might span multiple rows *and* columns, so they should be used for detection only if nothing else is available
|
|
{
|
|
if (!bRowSourceAmbiguous)
|
|
{
|
|
aValues.analyzeRange(nDataInRows,nDataInCols,bRowSourceAmbiguous);
|
|
aLabel.analyzeRange(nDataInRows,nDataInCols,bRowSourceAmbiguous);
|
|
if (nDataInRows > 1 && nDataInCols > 1)
|
|
bRowSourceAmbiguous = true;
|
|
else if( !bRowSourceAmbiguous && !nDataInRows && !nDataInCols )
|
|
{
|
|
if( aValues.inSameSingleColumn( aLabel ) )
|
|
nDataInCols++;
|
|
else if( aValues.inSameSingleRow( aLabel ) )
|
|
nDataInRows++;
|
|
else
|
|
{
|
|
//#i86188# also detect a single column split into rows correctly
|
|
if( aValues.inSameSingleColumn( aPrevValues ) )
|
|
nDataInRows++;
|
|
else if( aValues.inSameSingleRow( aPrevValues ) )
|
|
nDataInCols++;
|
|
else if( aLabel.inSameSingleColumn( aPrevLabel ) )
|
|
nDataInRows++;
|
|
else if( aLabel.inSameSingleRow( aPrevLabel ) )
|
|
nDataInCols++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
aPrevValues=aValues;
|
|
aPrevLabel=aLabel;
|
|
}
|
|
}
|
|
|
|
if (!bRowSourceAmbiguous)
|
|
{
|
|
bRowSourceDetected = true;
|
|
eRowSource = ( nDataInCols > 0
|
|
? chart::ChartDataRowSource_COLUMNS
|
|
: chart::ChartDataRowSource_ROWS );
|
|
}
|
|
else
|
|
{
|
|
// set DataRowSource to the better of the two ambiguities
|
|
eRowSource = ( nDataInRows > nDataInCols
|
|
? chart::ChartDataRowSource_ROWS
|
|
: chart::ChartDataRowSource_COLUMNS );
|
|
}
|
|
|
|
}
|
|
|
|
// TableNumberList
|
|
{
|
|
vector<SCTAB> aTableNumVector;
|
|
InsertTabNumber func;
|
|
func = ::std::for_each(aAllTokens.begin(), aAllTokens.end(), func);
|
|
func.getVector(aTableNumVector);
|
|
aResult.emplace_back( "TableNumberList", -1,
|
|
uno::Any( lcl_createTableNumberList( aTableNumVector ) ),
|
|
beans::PropertyState_DIRECT_VALUE );
|
|
}
|
|
|
|
if( bRowSourceDetected )
|
|
{
|
|
// DataRowSource (calculated before)
|
|
aResult.emplace_back( "DataRowSource", -1,
|
|
uno::Any( eRowSource ), beans::PropertyState_DIRECT_VALUE );
|
|
// HasCategories
|
|
aResult.emplace_back( "HasCategories", -1,
|
|
uno::Any( bHasCategories ), beans::PropertyState_DIRECT_VALUE );
|
|
// FirstCellAsLabel
|
|
aResult.emplace_back( "FirstCellAsLabel", -1,
|
|
uno::Any( bFirstCellAsLabel ), beans::PropertyState_DIRECT_VALUE );
|
|
}
|
|
|
|
// Add the left upper corner to the range if it is missing.
|
|
if (bRowSourceDetected && bFirstCellAsLabel && bHasCategories && !bHasCategoriesLabels )
|
|
{
|
|
RangeAnalyzer aTop,aLeft;
|
|
if( eRowSource==chart::ChartDataRowSource_COLUMNS )
|
|
{
|
|
aTop.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens);
|
|
aLeft.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens);
|
|
}
|
|
else
|
|
{
|
|
aTop.initRangeAnalyzer(m_pDocument, aAllCategoriesValuesTokens);
|
|
aLeft.initRangeAnalyzer(m_pDocument, aAllSeriesLabelTokens);
|
|
}
|
|
lcl_addUpperLeftCornerIfMissing(m_pDocument, aAllTokens, aTop.getRowCount(), aLeft.getColumnCount());//e.g. #i91212#
|
|
}
|
|
|
|
// Get range string.
|
|
lcl_convertTokensToString(sRangeRep, aAllTokens, *m_pDocument);
|
|
|
|
// add cell range property
|
|
aResult.emplace_back( "CellRangeRepresentation", -1,
|
|
uno::Any( sRangeRep ), beans::PropertyState_DIRECT_VALUE );
|
|
|
|
//Sequence Mapping
|
|
bool const bSequencesReordered = true;//todo detect this above or detect this sequence mapping cheaper ...
|
|
if( bSequencesReordered && bRowSourceDetected )
|
|
{
|
|
bool bDifferentIndexes = false;
|
|
|
|
std::vector< sal_Int32 > aSequenceMappingVector;
|
|
|
|
uno::Reference< chart2::data::XDataSource > xCompareDataSource;
|
|
try
|
|
{
|
|
xCompareDataSource.set( createDataSource( comphelper::containerToSequence( aResult ) ) );
|
|
}
|
|
catch( const lang::IllegalArgumentException & )
|
|
{
|
|
// creation of data source to compare didn't work, so we cannot
|
|
// create a sequence mapping
|
|
}
|
|
|
|
if( xDataSource.is() && xCompareDataSource.is() )
|
|
{
|
|
const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > aOldSequences =
|
|
xCompareDataSource->getDataSequences();
|
|
const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > aNewSequences =
|
|
xDataSource->getDataSequences();
|
|
|
|
std::map<std::pair<OUString, OUString>,sal_Int32> aOldEntryToIndex;
|
|
for( sal_Int32 nIndex = 0, n = aOldSequences.getLength(); nIndex < n; nIndex++ )
|
|
{
|
|
const uno::Reference< chart2::data::XLabeledDataSequence>& xOld( aOldSequences[nIndex] );
|
|
if( xOld.is() )
|
|
{
|
|
std::pair<OUString, OUString> aKey = constructKey(xOld);
|
|
aOldEntryToIndex[aKey] = nIndex;
|
|
}
|
|
}
|
|
|
|
for( sal_Int32 nNewIndex = 0, n = aNewSequences.getLength(); nNewIndex < n; nNewIndex++ )
|
|
{
|
|
const uno::Reference< chart2::data::XLabeledDataSequence>& xNew( aNewSequences[nNewIndex] );
|
|
if( !xNew.is() )
|
|
continue;
|
|
|
|
std::pair<OUString, OUString> aKey = constructKey(xNew);
|
|
if (aOldEntryToIndex.find(aKey) == aOldEntryToIndex.end())
|
|
continue;
|
|
|
|
sal_Int32 nOldIndex = aOldEntryToIndex[aKey];
|
|
if( nOldIndex != nNewIndex )
|
|
bDifferentIndexes = true;
|
|
|
|
aSequenceMappingVector.push_back(nOldIndex);
|
|
}
|
|
}
|
|
|
|
if( bDifferentIndexes && !aSequenceMappingVector.empty() )
|
|
{
|
|
aResult.emplace_back( "SequenceMapping", -1,
|
|
uno::Any( comphelper::containerToSequence(aSequenceMappingVector) )
|
|
, beans::PropertyState_DIRECT_VALUE );
|
|
}
|
|
}
|
|
|
|
return comphelper::containerToSequence( aResult );
|
|
}
|
|
|
|
sal_Bool SAL_CALL ScChart2DataProvider::createDataSequenceByRangeRepresentationPossible( const OUString& aRangeRepresentation )
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if( ! m_pDocument )
|
|
return false;
|
|
|
|
vector<ScTokenRef> aTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
return !aTokens.empty();
|
|
}
|
|
|
|
uno::Reference< chart2::data::XDataSequence > SAL_CALL
|
|
ScChart2DataProvider::createDataSequenceByRangeRepresentation(
|
|
const OUString& aRangeRepresentation )
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
uno::Reference< chart2::data::XDataSequence > xResult;
|
|
|
|
OSL_ENSURE( m_pDocument, "No Document -> no createDataSequenceByRangeRepresentation" );
|
|
if(!m_pDocument || aRangeRepresentation.isEmpty())
|
|
return xResult;
|
|
|
|
vector<ScTokenRef> aRefTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aRefTokens, aRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
if (aRefTokens.empty())
|
|
return xResult;
|
|
|
|
shrinkToDataRange(m_pDocument, aRefTokens);
|
|
|
|
xResult.set(new ScChart2DataSequence(m_pDocument, std::move(aRefTokens), m_bIncludeHiddenCells));
|
|
|
|
return xResult;
|
|
}
|
|
|
|
uno::Reference<chart2::data::XDataSequence> SAL_CALL
|
|
ScChart2DataProvider::createDataSequenceByValueArray(
|
|
const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/,
|
|
const OUString& /*aRoleQualifier*/ )
|
|
{
|
|
return uno::Reference<chart2::data::XDataSequence>();
|
|
}
|
|
|
|
uno::Reference< sheet::XRangeSelection > SAL_CALL ScChart2DataProvider::getRangeSelection()
|
|
{
|
|
uno::Reference< sheet::XRangeSelection > xResult;
|
|
|
|
uno::Reference< frame::XModel > xModel( lcl_GetXModel( m_pDocument ));
|
|
if( xModel.is())
|
|
xResult.set( xModel->getCurrentController(), uno::UNO_QUERY );
|
|
|
|
return xResult;
|
|
}
|
|
|
|
sal_Bool SAL_CALL ScChart2DataProvider::createDataSequenceByFormulaTokensPossible(
|
|
const Sequence<sheet::FormulaToken>& aTokens )
|
|
{
|
|
if (!aTokens.hasElements())
|
|
return false;
|
|
|
|
ScTokenArray aCode(*m_pDocument);
|
|
if (!ScTokenConversion::ConvertToTokenArray(*m_pDocument, aCode, aTokens))
|
|
return false;
|
|
|
|
sal_uInt16 n = aCode.GetLen();
|
|
if (!n)
|
|
return false;
|
|
|
|
formula::FormulaTokenArrayPlainIterator aIter(aCode);
|
|
const formula::FormulaToken* pFirst = aIter.First();
|
|
const formula::FormulaToken* pLast = aCode.GetArray()[n-1];
|
|
for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
|
|
{
|
|
switch (p->GetType())
|
|
{
|
|
case svSep:
|
|
{
|
|
switch (p->GetOpCode())
|
|
{
|
|
case ocSep:
|
|
// separators are allowed.
|
|
break;
|
|
case ocOpen:
|
|
if (p != pFirst)
|
|
// open paran is allowed only as the first token.
|
|
return false;
|
|
break;
|
|
case ocClose:
|
|
if (p != pLast)
|
|
// close paren is allowed only as the last token.
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case svSingleRef:
|
|
case svDoubleRef:
|
|
case svExternalSingleRef:
|
|
case svExternalDoubleRef:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uno::Reference<chart2::data::XDataSequence> SAL_CALL
|
|
ScChart2DataProvider::createDataSequenceByFormulaTokens(
|
|
const Sequence<sheet::FormulaToken>& aTokens )
|
|
{
|
|
uno::Reference<chart2::data::XDataSequence> xResult;
|
|
if (!aTokens.hasElements())
|
|
return xResult;
|
|
|
|
ScTokenArray aCode(*m_pDocument);
|
|
if (!ScTokenConversion::ConvertToTokenArray(*m_pDocument, aCode, aTokens))
|
|
return xResult;
|
|
|
|
sal_uInt16 n = aCode.GetLen();
|
|
if (!n)
|
|
return xResult;
|
|
|
|
vector<ScTokenRef> aRefTokens;
|
|
formula::FormulaTokenArrayPlainIterator aIter(aCode);
|
|
const formula::FormulaToken* pFirst = aIter.First();
|
|
const formula::FormulaToken* pLast = aCode.GetArray()[n-1];
|
|
for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
|
|
{
|
|
switch (p->GetType())
|
|
{
|
|
case svSep:
|
|
{
|
|
switch (p->GetOpCode())
|
|
{
|
|
case ocSep:
|
|
// separators are allowed.
|
|
break;
|
|
case ocOpen:
|
|
if (p != pFirst)
|
|
// open paran is allowed only as the first token.
|
|
throw lang::IllegalArgumentException();
|
|
break;
|
|
case ocClose:
|
|
if (p != pLast)
|
|
// close paren is allowed only as the last token.
|
|
throw lang::IllegalArgumentException();
|
|
break;
|
|
default:
|
|
throw lang::IllegalArgumentException();
|
|
}
|
|
}
|
|
break;
|
|
case svIndex:
|
|
case svString:
|
|
case svSingleRef:
|
|
case svDoubleRef:
|
|
case svExternalSingleRef:
|
|
case svExternalDoubleRef:
|
|
{
|
|
ScTokenRef pNew(p->Clone());
|
|
aRefTokens.push_back(pNew);
|
|
}
|
|
break;
|
|
default:
|
|
throw lang::IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
if (aRefTokens.empty())
|
|
return xResult;
|
|
|
|
shrinkToDataRange(m_pDocument, aRefTokens);
|
|
|
|
xResult.set(new ScChart2DataSequence(m_pDocument, std::move(aRefTokens), m_bIncludeHiddenCells));
|
|
return xResult;
|
|
}
|
|
|
|
// XRangeXMLConversion ---------------------------------------------------
|
|
|
|
OUString SAL_CALL ScChart2DataProvider::convertRangeToXML( const OUString& sRangeRepresentation )
|
|
{
|
|
OUString aRet;
|
|
if (!m_pDocument)
|
|
return aRet;
|
|
|
|
if (sRangeRepresentation.isEmpty())
|
|
// Empty data range is allowed.
|
|
return aRet;
|
|
|
|
vector<ScTokenRef> aRefTokens;
|
|
const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep);
|
|
ScRefTokenHelper::compileRangeRepresentation(
|
|
aRefTokens, sRangeRepresentation, *m_pDocument, cSep, m_pDocument->GetGrammar(), true);
|
|
if (aRefTokens.empty())
|
|
{
|
|
SAL_WARN("sc", "convertRangeToXML throw IllegalArgumentException from input of: " << sRangeRepresentation);
|
|
throw lang::IllegalArgumentException();
|
|
}
|
|
|
|
Tokens2RangeStringXML converter(*m_pDocument);
|
|
converter = ::std::for_each(aRefTokens.begin(), aRefTokens.end(), converter);
|
|
converter.getString(aRet);
|
|
|
|
return aRet;
|
|
}
|
|
|
|
OUString SAL_CALL ScChart2DataProvider::convertRangeFromXML( const OUString& sXMLRange )
|
|
{
|
|
if (!m_pDocument)
|
|
{
|
|
// #i74062# When loading flat XML, this is called before the referenced sheets are in the document,
|
|
// so the conversion has to take place directly with the strings, without looking up the sheets.
|
|
|
|
OUStringBuffer sRet;
|
|
sal_Int32 nOffset = 0;
|
|
while( nOffset >= 0 )
|
|
{
|
|
OUString sToken;
|
|
ScRangeStringConverter::GetTokenByOffset( sToken, sXMLRange, nOffset );
|
|
if( nOffset >= 0 )
|
|
{
|
|
// convert one address (remove dots)
|
|
|
|
OUString aUIString(sToken);
|
|
|
|
sal_Int32 nIndex = ScRangeStringConverter::IndexOf( sToken, ':', 0 );
|
|
if ( nIndex >= 0 && nIndex < aUIString.getLength() - 1 &&
|
|
aUIString[nIndex + 1] == '.' )
|
|
aUIString = aUIString.replaceAt( nIndex + 1, 1, u"" );
|
|
|
|
if ( aUIString[0] == '.' )
|
|
aUIString = aUIString.copy( 1 );
|
|
|
|
if( !sRet.isEmpty() )
|
|
sRet.append( ';' );
|
|
sRet.append( aUIString );
|
|
}
|
|
}
|
|
|
|
return sRet.makeStringAndClear();
|
|
}
|
|
|
|
OUString aRet;
|
|
ScRangeStringConverter::GetStringFromXMLRangeString(aRet, sXMLRange, *m_pDocument);
|
|
return aRet;
|
|
}
|
|
|
|
// DataProvider XPropertySet -------------------------------------------------
|
|
|
|
uno::Reference< beans::XPropertySetInfo> SAL_CALL
|
|
ScChart2DataProvider::getPropertySetInfo()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
static uno::Reference<beans::XPropertySetInfo> aRef =
|
|
new SfxItemPropertySetInfo( m_aPropSet.getPropertyMap() );
|
|
return aRef;
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataProvider::setPropertyValue(
|
|
const OUString& rPropertyName, const uno::Any& rValue)
|
|
{
|
|
if ( rPropertyName != SC_UNONAME_INCLUDEHIDDENCELLS )
|
|
throw beans::UnknownPropertyException(rPropertyName);
|
|
|
|
if ( !(rValue >>= m_bIncludeHiddenCells))
|
|
throw lang::IllegalArgumentException();
|
|
|
|
}
|
|
|
|
uno::Any SAL_CALL ScChart2DataProvider::getPropertyValue(
|
|
const OUString& rPropertyName)
|
|
{
|
|
uno::Any aRet;
|
|
if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
|
|
aRet <<= m_bIncludeHiddenCells;
|
|
else if (rPropertyName == SC_UNONAME_USE_INTERNAL_DATA_PROVIDER)
|
|
{
|
|
// This is a read-only property.
|
|
aRet <<= m_pDocument->PastingDrawFromOtherDoc();
|
|
}
|
|
else
|
|
throw beans::UnknownPropertyException(rPropertyName);
|
|
return aRet;
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataProvider::addPropertyChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XPropertyChangeListener>& /*xListener*/)
|
|
{
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataProvider::removePropertyChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XPropertyChangeListener>& /*rListener*/)
|
|
{
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataProvider::addVetoableChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
|
|
{
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataProvider::removeVetoableChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/ )
|
|
{
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
// DataSource ================================================================
|
|
|
|
ScChart2DataSource::ScChart2DataSource( ScDocument* pDoc)
|
|
: m_pDocument( pDoc)
|
|
{
|
|
if ( m_pDocument )
|
|
m_pDocument->AddUnoObject( *this);
|
|
}
|
|
|
|
ScChart2DataSource::~ScChart2DataSource()
|
|
{
|
|
SolarMutexGuard g;
|
|
|
|
if ( m_pDocument )
|
|
m_pDocument->RemoveUnoObject( *this);
|
|
}
|
|
|
|
void ScChart2DataSource::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
|
|
{
|
|
if ( rHint.GetId() == SfxHintId::Dying )
|
|
{
|
|
m_pDocument = nullptr;
|
|
}
|
|
}
|
|
|
|
uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence> > SAL_CALL
|
|
ScChart2DataSource::getDataSequences()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
return comphelper::containerToSequence(m_aLabeledSequences);
|
|
}
|
|
|
|
void ScChart2DataSource::AddLabeledSequence(const uno::Reference < chart2::data::XLabeledDataSequence >& xNew)
|
|
{
|
|
m_aLabeledSequences.push_back(xNew);
|
|
}
|
|
|
|
// DataSequence ==============================================================
|
|
|
|
ScChart2DataSequence::Item::Item()
|
|
: mfValue(std::numeric_limits<double>::quiet_NaN())
|
|
, mbIsValue(false)
|
|
{
|
|
}
|
|
|
|
ScChart2DataSequence::HiddenRangeListener::HiddenRangeListener(ScChart2DataSequence& rParent) :
|
|
mrParent(rParent)
|
|
{
|
|
}
|
|
|
|
ScChart2DataSequence::HiddenRangeListener::~HiddenRangeListener()
|
|
{
|
|
}
|
|
|
|
void ScChart2DataSequence::HiddenRangeListener::notify()
|
|
{
|
|
mrParent.setDataChangedHint(true);
|
|
}
|
|
|
|
ScChart2DataSequence::ScChart2DataSequence( ScDocument* pDoc,
|
|
vector<ScTokenRef>&& rTokens,
|
|
bool bIncludeHiddenCells )
|
|
: m_xDataArray(new std::vector<Item>)
|
|
, m_bIncludeHiddenCells( bIncludeHiddenCells)
|
|
, m_nObjectId( 0 )
|
|
, m_pDocument( pDoc)
|
|
, m_aTokens(std::move(rTokens))
|
|
, m_aPropSet(lcl_GetDataSequencePropertyMap())
|
|
, m_bGotDataChangedHint(false)
|
|
, m_bExtDataRebuildQueued(false)
|
|
, mbTimeBased(false)
|
|
, mnTimeBasedStart(0)
|
|
, mnTimeBasedEnd(0)
|
|
, mnCurrentTab(0)
|
|
{
|
|
if ( m_pDocument )
|
|
{
|
|
m_pDocument->AddUnoObject( *this);
|
|
m_nObjectId = m_pDocument->GetNewUnoId();
|
|
}
|
|
// FIXME: real implementation of identifier and it's mapping to ranges.
|
|
// Reuse ScChartListener?
|
|
|
|
// BM: don't use names of named ranges but the UI range strings
|
|
// String aStr;
|
|
// rRangeList->Format( aStr, ScRefFlags::RANGE_ABS_3D, m_pDocument );
|
|
// m_aIdentifier = aStr;
|
|
|
|
// m_aIdentifier = "ID_";
|
|
// static sal_Int32 nID = 0;
|
|
// m_aIdentifier += OUString::valueOf( ++nID);
|
|
}
|
|
|
|
/** called from Clone() */
|
|
ScChart2DataSequence::ScChart2DataSequence(ScDocument* pDoc, const ScChart2DataSequence& r)
|
|
: m_xDataArray(r.m_xDataArray)
|
|
, m_aHiddenValues(r.m_aHiddenValues)
|
|
, m_aRole(r.m_aRole)
|
|
, m_bIncludeHiddenCells( r.m_bIncludeHiddenCells)
|
|
, m_nObjectId( 0 )
|
|
, m_pDocument( pDoc)
|
|
, m_aPropSet(lcl_GetDataSequencePropertyMap())
|
|
, m_bGotDataChangedHint(false)
|
|
, m_bExtDataRebuildQueued(false)
|
|
, mbTimeBased(false)
|
|
, mnTimeBasedStart(0)
|
|
, mnTimeBasedEnd(0)
|
|
, mnCurrentTab(0)
|
|
{
|
|
assert(pDoc);
|
|
|
|
// Clone tokens.
|
|
m_aTokens.reserve(r.m_aTokens.size());
|
|
for (const auto& rxToken : r.m_aTokens)
|
|
{
|
|
ScTokenRef p(rxToken->Clone());
|
|
m_aTokens.push_back(p);
|
|
}
|
|
|
|
m_pDocument->AddUnoObject( *this);
|
|
m_nObjectId = m_pDocument->GetNewUnoId();
|
|
|
|
if (r.m_oRangeIndices)
|
|
m_oRangeIndices = *r.m_oRangeIndices;
|
|
|
|
if (!r.m_pExtRefListener)
|
|
return;
|
|
|
|
// Re-register all external files that the old instance was
|
|
// listening to.
|
|
|
|
ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
|
|
m_pExtRefListener.reset(new ExternalRefListener(*this, m_pDocument));
|
|
const std::unordered_set<sal_uInt16>& rFileIds = r.m_pExtRefListener->getAllFileIds();
|
|
for (const auto& rFileId : rFileIds)
|
|
{
|
|
pRefMgr->addLinkListener(rFileId, m_pExtRefListener.get());
|
|
m_pExtRefListener->addFileId(rFileId);
|
|
}
|
|
}
|
|
|
|
ScChart2DataSequence::~ScChart2DataSequence()
|
|
{
|
|
SolarMutexGuard g;
|
|
|
|
if ( m_pDocument )
|
|
{
|
|
m_pDocument->RemoveUnoObject( *this);
|
|
if (m_pHiddenListener)
|
|
{
|
|
ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
|
|
if (pCLC)
|
|
pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
|
|
}
|
|
StopListeningToAllExternalRefs();
|
|
}
|
|
|
|
m_pValueListener.reset();
|
|
}
|
|
|
|
void ScChart2DataSequence::RefChanged()
|
|
{
|
|
if( !m_pValueListener || m_aValueListeners.empty() )
|
|
return;
|
|
|
|
m_pValueListener->EndListeningAll();
|
|
|
|
if( !m_pDocument )
|
|
return;
|
|
|
|
ScChartListenerCollection* pCLC = nullptr;
|
|
if (m_pHiddenListener)
|
|
{
|
|
pCLC = m_pDocument->GetChartListenerCollection();
|
|
if (pCLC)
|
|
pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
|
|
}
|
|
|
|
for (const auto& rxToken : m_aTokens)
|
|
{
|
|
ScRange aRange;
|
|
if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
|
|
continue;
|
|
|
|
m_pDocument->StartListeningArea(aRange, false, m_pValueListener.get());
|
|
if (pCLC)
|
|
pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
|
|
}
|
|
}
|
|
|
|
void ScChart2DataSequence::BuildDataCache()
|
|
{
|
|
m_bExtDataRebuildQueued = false;
|
|
|
|
if (!m_xDataArray->empty())
|
|
return;
|
|
|
|
StopListeningToAllExternalRefs();
|
|
|
|
::std::vector<sal_Int32> aHiddenValues;
|
|
sal_Int32 nDataCount = 0;
|
|
|
|
for (const auto& rxToken : m_aTokens)
|
|
{
|
|
if (ScRefTokenHelper::isExternalRef(rxToken))
|
|
{
|
|
nDataCount += FillCacheFromExternalRef(rxToken);
|
|
}
|
|
else
|
|
{
|
|
ScRange aRange;
|
|
if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
|
|
continue;
|
|
|
|
SCCOL nLastCol = -1;
|
|
SCROW nLastRow = -1;
|
|
for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
|
|
{
|
|
for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
|
|
{
|
|
sc::ColumnBlockPosition hint;
|
|
m_pDocument->InitColumnBlockPosition( hint, nTab, nCol );
|
|
for (SCROW nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
|
|
{
|
|
if (nRow == aRange.aEnd.Row())
|
|
{
|
|
// Excel behavior: if the last row is the totals row, the data
|
|
// is not added to the chart. If it's not the last row, the data
|
|
// is added like normal.
|
|
const auto* pData = m_pDocument->GetDBAtCursor(
|
|
nCol, nRow, nTab,
|
|
ScDBDataPortion::AREA
|
|
);
|
|
if (pData && pData->HasTotals())
|
|
{
|
|
ScRange aTempRange;
|
|
pData->GetArea(aTempRange);
|
|
if (aTempRange.aEnd.Row() == nRow)
|
|
{
|
|
// Current row is totals row, skip
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
bool bColHidden = m_pDocument->ColHidden(nCol, nTab, nullptr, &nLastCol);
|
|
bool bRowHidden = m_pDocument->RowHidden(nRow, nTab, nullptr, &nLastRow);
|
|
|
|
if (bColHidden || bRowHidden)
|
|
{
|
|
// hidden cell
|
|
aHiddenValues.push_back(nDataCount-1);
|
|
|
|
if( !m_bIncludeHiddenCells )
|
|
continue;
|
|
}
|
|
|
|
Item aItem;
|
|
|
|
ScAddress aAdr(nCol, nRow, nTab);
|
|
aItem.maString = m_pDocument->GetString(aAdr);
|
|
|
|
ScRefCellValue aCell(*m_pDocument, aAdr, hint);
|
|
switch (aCell.getType())
|
|
{
|
|
case CELLTYPE_VALUE:
|
|
aItem.mfValue = aCell.getValue();
|
|
aItem.mbIsValue = true;
|
|
break;
|
|
case CELLTYPE_FORMULA:
|
|
{
|
|
ScFormulaCell* pFCell = aCell.getFormula();
|
|
FormulaError nErr = pFCell->GetErrCode();
|
|
if (nErr != FormulaError::NONE)
|
|
break;
|
|
|
|
if (pFCell->IsValue())
|
|
{
|
|
aItem.mfValue = pFCell->GetValue();
|
|
aItem.mbIsValue = true;
|
|
}
|
|
}
|
|
break;
|
|
case CELLTYPE_EDIT:
|
|
case CELLTYPE_NONE:
|
|
case CELLTYPE_STRING:
|
|
default:
|
|
; // do nothing
|
|
}
|
|
|
|
aItem.mAddress = ScAddress(nCol, nRow, nTab);
|
|
|
|
m_xDataArray->push_back(std::move(aItem));
|
|
++nDataCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// convert the hidden cell list to sequence.
|
|
m_aHiddenValues.realloc(aHiddenValues.size());
|
|
std::copy(
|
|
aHiddenValues.begin(), aHiddenValues.end(), m_aHiddenValues.getArray());
|
|
|
|
// Clear the data series cache when the array is re-built.
|
|
m_aMixedDataCache.realloc(0);
|
|
}
|
|
|
|
void ScChart2DataSequence::RebuildDataCache()
|
|
{
|
|
if (!m_bExtDataRebuildQueued)
|
|
{
|
|
m_xDataArray.reset(new std::vector<Item>);
|
|
m_pDocument->BroadcastUno(ScHint(SfxHintId::ScDataChanged, ScAddress()));
|
|
m_bExtDataRebuildQueued = true;
|
|
m_bGotDataChangedHint = true;
|
|
}
|
|
}
|
|
|
|
sal_Int32 ScChart2DataSequence::FillCacheFromExternalRef(const ScTokenRef& pToken)
|
|
{
|
|
ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
|
|
ScRange aRange;
|
|
if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, pToken, ScAddress(), true))
|
|
return 0;
|
|
|
|
sal_uInt16 nFileId = pToken->GetIndex();
|
|
OUString aTabName = pToken->GetString().getString();
|
|
ScExternalRefCache::TokenArrayRef pArray = pRefMgr->getDoubleRefTokens(nFileId, aTabName, aRange, nullptr);
|
|
if (!pArray)
|
|
// no external data exists for this range.
|
|
return 0;
|
|
|
|
// Start listening for this external document.
|
|
ExternalRefListener* pExtRefListener = GetExtRefListener();
|
|
pRefMgr->addLinkListener(nFileId, pExtRefListener);
|
|
pExtRefListener->addFileId(nFileId);
|
|
|
|
m_xDataArray.reset(new std::vector<Item>);
|
|
ScExternalRefCache::TableTypeRef pTable = pRefMgr->getCacheTable(nFileId, aTabName, false);
|
|
sal_Int32 nDataCount = 0;
|
|
FormulaTokenArrayPlainIterator aIter(*pArray);
|
|
for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
|
|
{
|
|
// Cached external range is always represented as a single
|
|
// matrix token, although that might change in the future when
|
|
// we introduce a new token type to store multi-table range
|
|
// data.
|
|
|
|
if (p->GetType() != svMatrix)
|
|
{
|
|
OSL_FAIL("Cached array is not a matrix token.");
|
|
continue;
|
|
}
|
|
|
|
const ScMatrix* pMat = p->GetMatrix();
|
|
SCSIZE nCSize, nRSize;
|
|
pMat->GetDimensions(nCSize, nRSize);
|
|
for (SCSIZE nC = 0; nC < nCSize; ++nC)
|
|
{
|
|
for (SCSIZE nR = 0; nR < nRSize; ++nR)
|
|
{
|
|
if (pMat->IsValue(nC, nR) || pMat->IsBoolean(nC, nR))
|
|
{
|
|
Item aItem;
|
|
|
|
aItem.mbIsValue = true;
|
|
aItem.mfValue = pMat->GetDouble(nC, nR);
|
|
|
|
SvNumberFormatter* pFormatter = m_pDocument->GetFormatTable();
|
|
if (pFormatter)
|
|
{
|
|
const double fVal = aItem.mfValue;
|
|
const Color* pColor = nullptr;
|
|
sal_uInt32 nFmt = 0;
|
|
if (pTable)
|
|
{
|
|
// Get the correct format index from the cache.
|
|
SCCOL nCol = aRange.aStart.Col() + static_cast<SCCOL>(nC);
|
|
SCROW nRow = aRange.aStart.Row() + static_cast<SCROW>(nR);
|
|
pTable->getCell(nCol, nRow, &nFmt);
|
|
}
|
|
pFormatter->GetOutputString(fVal, nFmt, aItem.maString, &pColor);
|
|
}
|
|
|
|
m_xDataArray->push_back(std::move(aItem));
|
|
++nDataCount;
|
|
}
|
|
else if (pMat->IsStringOrEmpty(nC, nR))
|
|
{
|
|
Item aItem;
|
|
|
|
aItem.mbIsValue = false;
|
|
aItem.maString = pMat->GetString(nC, nR).getString();
|
|
|
|
m_xDataArray->push_back(std::move(aItem));
|
|
++nDataCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nDataCount;
|
|
}
|
|
|
|
void ScChart2DataSequence::UpdateTokensFromRanges(const ScRangeList& rRanges)
|
|
{
|
|
if (!m_oRangeIndices)
|
|
return;
|
|
|
|
for ( size_t i = 0, nCount = rRanges.size(); i < nCount; ++i )
|
|
{
|
|
ScTokenRef pToken;
|
|
const ScRange & rRange = rRanges[i];
|
|
|
|
ScRefTokenHelper::getTokenFromRange(m_pDocument, pToken, rRange);
|
|
sal_uInt32 nOrigPos = (*m_oRangeIndices)[i];
|
|
m_aTokens[nOrigPos] = std::move(pToken);
|
|
}
|
|
|
|
RefChanged();
|
|
|
|
// any change of the range address is broadcast to value (modify) listeners
|
|
if ( !m_aValueListeners.empty() )
|
|
m_bGotDataChangedHint = true;
|
|
}
|
|
|
|
ScChart2DataSequence::ExternalRefListener* ScChart2DataSequence::GetExtRefListener()
|
|
{
|
|
if (!m_pExtRefListener)
|
|
m_pExtRefListener.reset(new ExternalRefListener(*this, m_pDocument));
|
|
|
|
return m_pExtRefListener.get();
|
|
}
|
|
|
|
void ScChart2DataSequence::StopListeningToAllExternalRefs()
|
|
{
|
|
if (!m_pExtRefListener)
|
|
return;
|
|
|
|
const std::unordered_set<sal_uInt16>& rFileIds = m_pExtRefListener->getAllFileIds();
|
|
ScExternalRefManager* pRefMgr = m_pDocument->GetExternalRefManager();
|
|
for (const auto& rFileId : rFileIds)
|
|
pRefMgr->removeLinkListener(rFileId, m_pExtRefListener.get());
|
|
|
|
m_pExtRefListener.reset();
|
|
}
|
|
|
|
|
|
void ScChart2DataSequence::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
|
|
{
|
|
if ( rHint.GetId() == SfxHintId::ScUpdateRef )
|
|
{
|
|
// Create a range list from the token list, have the range list
|
|
// updated, and bring the change back to the token list.
|
|
|
|
ScRangeList aRanges;
|
|
m_oRangeIndices.emplace();
|
|
vector<ScTokenRef>::const_iterator itrBeg = m_aTokens.begin(), itrEnd = m_aTokens.end();
|
|
for (vector<ScTokenRef>::const_iterator itr = itrBeg ;itr != itrEnd; ++itr)
|
|
{
|
|
if (!ScRefTokenHelper::isExternalRef(*itr))
|
|
{
|
|
ScRange aRange;
|
|
ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, *itr, ScAddress());
|
|
aRanges.push_back(aRange);
|
|
sal_uInt32 nPos = distance(itrBeg, itr);
|
|
m_oRangeIndices->push_back(nPos);
|
|
}
|
|
}
|
|
|
|
assert(m_oRangeIndices->size() == aRanges.size() &&
|
|
"range list and range index list have different sizes.");
|
|
|
|
unique_ptr<ScRangeList> pUndoRanges;
|
|
if ( m_pDocument->HasUnoRefUndo() )
|
|
pUndoRanges.reset(new ScRangeList(aRanges));
|
|
|
|
const ScUpdateRefHint& rRef = static_cast<const ScUpdateRefHint&>(rHint);
|
|
bool bChanged = aRanges.UpdateReference(
|
|
rRef.GetMode(), m_pDocument, rRef.GetRange(), rRef.GetDx(), rRef.GetDy(), rRef.GetDz());
|
|
|
|
if (bChanged)
|
|
{
|
|
// TODO: This should be an assert, but tdf#144537 triggers it.
|
|
SAL_WARN_IF(m_oRangeIndices->size() == aRanges.size(),
|
|
"sc.ui", "range list and range index list have different sizes after the reference update.");
|
|
|
|
// Bring the change back from the range list to the token list.
|
|
UpdateTokensFromRanges(aRanges);
|
|
|
|
if (pUndoRanges)
|
|
m_pDocument->AddUnoRefChange(m_nObjectId, *pUndoRanges);
|
|
}
|
|
}
|
|
else if ( rHint.GetId() == SfxHintId::ScUnoRefUndo )
|
|
{
|
|
auto pUndoHint = static_cast<const ScUnoRefUndoHint*>(&rHint);
|
|
do
|
|
{
|
|
if (pUndoHint->GetObjectId() != m_nObjectId)
|
|
break;
|
|
|
|
// The hint object provides the old ranges. Restore the old state
|
|
// from these ranges.
|
|
|
|
if (!m_oRangeIndices || m_oRangeIndices->empty())
|
|
{
|
|
assert(false && " faulty range indices");
|
|
break;
|
|
}
|
|
|
|
const ScRangeList& rRanges = pUndoHint->GetRanges();
|
|
|
|
size_t nCount = rRanges.size();
|
|
if (nCount != m_oRangeIndices->size())
|
|
{
|
|
assert(false && "range count and range index count differ.");
|
|
break;
|
|
}
|
|
|
|
UpdateTokensFromRanges(rRanges);
|
|
}
|
|
while (false);
|
|
}
|
|
else
|
|
{
|
|
const SfxHintId nId = rHint.GetId();
|
|
if ( nId ==SfxHintId::Dying )
|
|
{
|
|
m_pDocument = nullptr;
|
|
}
|
|
else if ( nId == SfxHintId::DataChanged )
|
|
{
|
|
// delayed broadcast as in ScCellRangesBase
|
|
|
|
if ( m_bGotDataChangedHint && m_pDocument )
|
|
{
|
|
m_xDataArray.reset(new std::vector<Item>);
|
|
lang::EventObject aEvent;
|
|
aEvent.Source = getXWeak();
|
|
|
|
if( m_pDocument )
|
|
{
|
|
for (const uno::Reference<util::XModifyListener> & xListener: m_aValueListeners)
|
|
m_pDocument->AddUnoListenerCall( xListener, aEvent );
|
|
}
|
|
|
|
m_bGotDataChangedHint = false;
|
|
}
|
|
}
|
|
else if ( nId == SfxHintId::ScCalcAll )
|
|
{
|
|
// broadcast from DoHardRecalc - set m_bGotDataChangedHint
|
|
// (SfxHintId::DataChanged follows separately)
|
|
|
|
if ( !m_aValueListeners.empty() )
|
|
m_bGotDataChangedHint = true;
|
|
}
|
|
else if (nId == SfxHintId::ScClearCache)
|
|
{
|
|
// necessary after import
|
|
m_xDataArray.reset(new std::vector<Item>);
|
|
}
|
|
}
|
|
}
|
|
|
|
IMPL_LINK( ScChart2DataSequence, ValueListenerHdl, const SfxHint&, rHint, void )
|
|
{
|
|
if ( m_pDocument && (rHint.GetId() == SfxHintId::ScDataChanged) )
|
|
{
|
|
// This may be called several times for a single change, if several formulas
|
|
// in the range are notified. So only a flag is set that is checked when
|
|
// SfxHintId::DataChanged is received.
|
|
|
|
setDataChangedHint(true);
|
|
}
|
|
}
|
|
|
|
ScChart2DataSequence::ExternalRefListener::ExternalRefListener(
|
|
ScChart2DataSequence& rParent, ScDocument* pDoc) :
|
|
mrParent(rParent),
|
|
mpDoc(pDoc)
|
|
{
|
|
}
|
|
|
|
ScChart2DataSequence::ExternalRefListener::~ExternalRefListener()
|
|
{
|
|
if (!mpDoc || mpDoc->IsInDtorClear())
|
|
// The document is being destroyed. Do nothing.
|
|
return;
|
|
|
|
// Make sure to remove all pointers to this object.
|
|
mpDoc->GetExternalRefManager()->removeLinkListener(this);
|
|
}
|
|
|
|
void ScChart2DataSequence::ExternalRefListener::notify(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType)
|
|
{
|
|
switch (eType)
|
|
{
|
|
case ScExternalRefManager::LINK_MODIFIED:
|
|
{
|
|
if (maFileIds.count(nFileId))
|
|
// We are listening to this external document.
|
|
mrParent.RebuildDataCache();
|
|
}
|
|
break;
|
|
case ScExternalRefManager::LINK_BROKEN:
|
|
maFileIds.erase(nFileId);
|
|
break;
|
|
case ScExternalRefManager::OH_NO_WE_ARE_GOING_TO_DIE:
|
|
mpDoc = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ScChart2DataSequence::ExternalRefListener::addFileId(sal_uInt16 nFileId)
|
|
{
|
|
maFileIds.insert(nFileId);
|
|
}
|
|
|
|
uno::Sequence< uno::Any> SAL_CALL ScChart2DataSequence::getData()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if ( !m_pDocument)
|
|
throw uno::RuntimeException();
|
|
|
|
BuildDataCache();
|
|
|
|
if (!m_aMixedDataCache.hasElements())
|
|
{
|
|
// Build a cache for the 1st time...
|
|
|
|
sal_Int32 nCount = m_xDataArray->size();
|
|
m_aMixedDataCache.realloc(nCount);
|
|
uno::Any* pArr = m_aMixedDataCache.getArray();
|
|
for (const Item &rItem : *m_xDataArray)
|
|
{
|
|
if (rItem.mbIsValue)
|
|
*pArr <<= rItem.mfValue;
|
|
else if (rItem.maString.isEmpty())
|
|
{
|
|
ScRefCellValue aCell(*m_pDocument, rItem.mAddress);
|
|
if (aCell.isEmpty())
|
|
*pArr = uno::Any();
|
|
else
|
|
*pArr <<= rItem.maString;
|
|
}
|
|
else
|
|
*pArr <<= rItem.maString;
|
|
++pArr;
|
|
}
|
|
}
|
|
return m_aMixedDataCache;
|
|
}
|
|
|
|
// XNumericalDataSequence --------------------------------------------------
|
|
|
|
uno::Sequence< double > SAL_CALL ScChart2DataSequence::getNumericalData()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if ( !m_pDocument)
|
|
throw uno::RuntimeException();
|
|
|
|
BuildDataCache();
|
|
|
|
sal_Int32 nCount = m_xDataArray->size();
|
|
uno::Sequence<double> aSeq(nCount);
|
|
double* pArr = aSeq.getArray();
|
|
for (const Item& rItem : *m_xDataArray)
|
|
{
|
|
*pArr = rItem.mbIsValue ? rItem.mfValue : std::numeric_limits<double>::quiet_NaN();
|
|
++pArr;
|
|
}
|
|
|
|
return aSeq;
|
|
}
|
|
|
|
// XTextualDataSequence --------------------------------------------------
|
|
|
|
uno::Sequence< OUString > SAL_CALL ScChart2DataSequence::getTextualData()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
uno::Sequence<OUString> aSeq;
|
|
if ( !m_pDocument )
|
|
throw uno::RuntimeException();
|
|
|
|
BuildDataCache();
|
|
|
|
sal_Int32 nCount = m_xDataArray->size();
|
|
if ( nCount > 0 )
|
|
{
|
|
aSeq = uno::Sequence<OUString>(nCount);
|
|
OUString* pArr = aSeq.getArray();
|
|
for (const Item& rItem : *m_xDataArray)
|
|
{
|
|
*pArr = rItem.maString;
|
|
++pArr;
|
|
}
|
|
}
|
|
else if ( m_aTokens.front() )
|
|
{
|
|
if( m_aTokens.front()->GetType() == svString )
|
|
{
|
|
aSeq = uno::Sequence<OUString> { m_aTokens.front()->GetString().getString() };
|
|
}
|
|
}
|
|
|
|
return aSeq;
|
|
}
|
|
|
|
OUString SAL_CALL ScChart2DataSequence::getSourceRangeRepresentation()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
OUString aStr;
|
|
OSL_ENSURE( m_pDocument, "No Document -> no SourceRangeRepresentation" );
|
|
if (m_pDocument)
|
|
lcl_convertTokensToString(aStr, m_aTokens, *m_pDocument);
|
|
|
|
return aStr;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* This function object is used to accumulatively count the numbers of
|
|
* columns and rows in all reference tokens.
|
|
*/
|
|
class AccumulateRangeSize
|
|
{
|
|
public:
|
|
AccumulateRangeSize(const ScDocument* pDoc) :
|
|
mpDoc(pDoc), mnCols(0), mnRows(0) {}
|
|
|
|
void operator() (const ScTokenRef& pToken)
|
|
{
|
|
ScRange r;
|
|
bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
|
|
ScRefTokenHelper::getRangeFromToken(mpDoc, r, pToken, ScAddress(), bExternal);
|
|
r.PutInOrder();
|
|
mnCols += r.aEnd.Col() - r.aStart.Col() + 1;
|
|
mnRows += r.aEnd.Row() - r.aStart.Row() + 1;
|
|
}
|
|
|
|
SCCOL getCols() const { return mnCols; }
|
|
SCROW getRows() const { return mnRows; }
|
|
private:
|
|
const ScDocument* mpDoc;
|
|
SCCOL mnCols;
|
|
SCROW mnRows;
|
|
};
|
|
|
|
/**
|
|
* This function object is used to generate label strings from a list of
|
|
* reference tokens.
|
|
*/
|
|
class GenerateLabelStrings
|
|
{
|
|
public:
|
|
GenerateLabelStrings(const ScDocument* pDoc, sal_Int32 nSize, chart2::data::LabelOrigin eOrigin, bool bColumn) :
|
|
mpDoc(pDoc),
|
|
mpLabels(std::make_shared<Sequence<OUString>>(nSize)),
|
|
meOrigin(eOrigin),
|
|
mnCount(0),
|
|
mbColumn(bColumn) {}
|
|
|
|
void operator() (const ScTokenRef& pToken)
|
|
{
|
|
bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
|
|
ScRange aRange;
|
|
ScRefTokenHelper::getRangeFromToken(mpDoc, aRange, pToken, ScAddress(), bExternal);
|
|
OUString* pArr = mpLabels->getArray();
|
|
if (mbColumn)
|
|
{
|
|
for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol)
|
|
{
|
|
if ( meOrigin != chart2::data::LabelOrigin_LONG_SIDE)
|
|
{
|
|
OUString aString = ScResId(STR_COLUMN) + " ";
|
|
ScAddress aPos( nCol, 0, 0 );
|
|
OUString aColStr(aPos.Format(ScRefFlags::COL_VALID));
|
|
aString += aColStr;
|
|
pArr[mnCount] = aString;
|
|
}
|
|
else //only indices for categories
|
|
pArr[mnCount] = OUString::number( mnCount+1 );
|
|
++mnCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (sal_Int32 nRow = aRange.aStart.Row(); nRow <= aRange.aEnd.Row(); ++nRow)
|
|
{
|
|
if (meOrigin != chart2::data::LabelOrigin_LONG_SIDE)
|
|
pArr[mnCount] = ScResId(STR_ROW) + " " + OUString::number(nRow + 1);
|
|
else //only indices for categories
|
|
pArr[mnCount] = OUString::number( mnCount+1 );
|
|
++mnCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Sequence<OUString>& getLabels() const { return *mpLabels; }
|
|
|
|
private:
|
|
const ScDocument* mpDoc;
|
|
shared_ptr< Sequence<OUString> > mpLabels;
|
|
chart2::data::LabelOrigin meOrigin;
|
|
sal_Int32 mnCount;
|
|
bool mbColumn;
|
|
};
|
|
|
|
}
|
|
|
|
uno::Sequence< OUString > SAL_CALL ScChart2DataSequence::generateLabel(chart2::data::LabelOrigin eOrigin)
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
if ( !m_pDocument)
|
|
throw uno::RuntimeException();
|
|
|
|
// Determine the total size of all ranges.
|
|
AccumulateRangeSize func(m_pDocument);
|
|
func = ::std::for_each(m_aTokens.begin(), m_aTokens.end(), func);
|
|
SCCOL nCols = func.getCols();
|
|
SCROW nRows = func.getRows();
|
|
|
|
// Determine whether this is column-major or row-major.
|
|
bool bColumn = true;
|
|
if ((eOrigin == chart2::data::LabelOrigin_SHORT_SIDE) ||
|
|
(eOrigin == chart2::data::LabelOrigin_LONG_SIDE))
|
|
{
|
|
if (nRows > nCols)
|
|
{
|
|
bColumn = eOrigin == chart2::data::LabelOrigin_SHORT_SIDE;
|
|
}
|
|
else if (nCols > nRows)
|
|
{
|
|
bColumn = eOrigin != chart2::data::LabelOrigin_SHORT_SIDE;
|
|
}
|
|
else
|
|
return Sequence<OUString>();
|
|
}
|
|
|
|
// Generate label strings based on the info so far.
|
|
sal_Int32 nCount = bColumn ? nCols : nRows;
|
|
GenerateLabelStrings genLabels(m_pDocument, nCount, eOrigin, bColumn);
|
|
genLabels = ::std::for_each(m_aTokens.begin(), m_aTokens.end(), genLabels);
|
|
Sequence<OUString> aSeq = genLabels.getLabels();
|
|
|
|
return aSeq;
|
|
}
|
|
|
|
namespace {
|
|
|
|
sal_uInt32 getDisplayNumberFormat(const ScDocument* pDoc, const ScAddress& rPos)
|
|
{
|
|
sal_uInt32 nFormat = pDoc->GetNumberFormat(ScRange(rPos)); // original format from cell.
|
|
return nFormat;
|
|
}
|
|
|
|
}
|
|
|
|
::sal_Int32 SAL_CALL ScChart2DataSequence::getNumberFormatKeyByIndex( ::sal_Int32 nIndex )
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
BuildDataCache();
|
|
|
|
if (nIndex == -1)
|
|
{
|
|
// return format of first non-empty cell
|
|
// TODO: use nicer heuristic
|
|
for (const Item& rItem : *m_xDataArray)
|
|
{
|
|
ScRefCellValue aCell(*m_pDocument, rItem.mAddress);
|
|
if (!aCell.isEmpty() && aCell.hasNumeric())
|
|
{
|
|
return static_cast<sal_Int32>(getDisplayNumberFormat(m_pDocument, rItem.mAddress));
|
|
}
|
|
}
|
|
|
|
// we could not find a non-empty cell
|
|
return 0;
|
|
}
|
|
|
|
if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= m_xDataArray->size())
|
|
{
|
|
SAL_WARN("sc.ui", "Passed invalid index to getNumberFormatKeyByIndex(). Will return default value '0'.");
|
|
return 0;
|
|
}
|
|
|
|
return static_cast<sal_Int32>(getDisplayNumberFormat(m_pDocument, m_xDataArray->at(nIndex).mAddress));
|
|
}
|
|
|
|
// XCloneable ================================================================
|
|
|
|
uno::Reference< util::XCloneable > SAL_CALL ScChart2DataSequence::createClone()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
|
|
rtl::Reference<ScChart2DataSequence> p(new ScChart2DataSequence(m_pDocument, *this));
|
|
return p;
|
|
}
|
|
|
|
// XModifyBroadcaster ========================================================
|
|
|
|
void SAL_CALL ScChart2DataSequence::addModifyListener( const uno::Reference< util::XModifyListener >& aListener )
|
|
{
|
|
// like ScCellRangesBase::addModifyListener
|
|
SolarMutexGuard aGuard;
|
|
if (m_aTokens.empty())
|
|
return;
|
|
|
|
ScRangeList aRanges;
|
|
ScRefTokenHelper::getRangeListFromTokens(m_pDocument, aRanges, m_aTokens, ScAddress());
|
|
m_aValueListeners.emplace_back( aListener );
|
|
|
|
if ( m_aValueListeners.size() != 1 )
|
|
return;
|
|
|
|
if (!m_pValueListener)
|
|
m_pValueListener.reset(new ScLinkListener( LINK( this, ScChart2DataSequence, ValueListenerHdl ) ));
|
|
|
|
if (!m_pHiddenListener)
|
|
m_pHiddenListener.reset(new HiddenRangeListener(*this));
|
|
|
|
if( m_pDocument )
|
|
{
|
|
ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
|
|
for (const auto& rxToken : m_aTokens)
|
|
{
|
|
ScRange aRange;
|
|
if (!ScRefTokenHelper::getRangeFromToken(m_pDocument, aRange, rxToken, ScAddress()))
|
|
continue;
|
|
|
|
m_pDocument->StartListeningArea( aRange, false, m_pValueListener.get() );
|
|
if (pCLC)
|
|
pCLC->StartListeningHiddenRange(aRange, m_pHiddenListener.get());
|
|
}
|
|
}
|
|
|
|
acquire(); // don't lose this object (one ref for all listeners)
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::removeModifyListener( const uno::Reference< util::XModifyListener >& aListener )
|
|
{
|
|
// like ScCellRangesBase::removeModifyListener
|
|
|
|
SolarMutexGuard aGuard;
|
|
if (m_aTokens.empty())
|
|
return;
|
|
|
|
rtl::Reference<ScChart2DataSequence> xSelfHold(this); // in case the listeners have the last ref
|
|
|
|
sal_uInt16 nCount = m_aValueListeners.size();
|
|
for ( sal_uInt16 n=nCount; n--; )
|
|
{
|
|
uno::Reference<util::XModifyListener>& rObj = m_aValueListeners[n];
|
|
if ( rObj == aListener )
|
|
{
|
|
m_aValueListeners.erase( m_aValueListeners.begin() + n );
|
|
|
|
if ( m_aValueListeners.empty() )
|
|
{
|
|
if (m_pValueListener)
|
|
m_pValueListener->EndListeningAll();
|
|
|
|
if (m_pHiddenListener && m_pDocument)
|
|
{
|
|
ScChartListenerCollection* pCLC = m_pDocument->GetChartListenerCollection();
|
|
if (pCLC)
|
|
pCLC->EndListeningHiddenRange(m_pHiddenListener.get());
|
|
}
|
|
|
|
release(); // release the ref for the listeners
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// DataSequence XPropertySet -------------------------------------------------
|
|
|
|
uno::Reference< beans::XPropertySetInfo> SAL_CALL
|
|
ScChart2DataSequence::getPropertySetInfo()
|
|
{
|
|
SolarMutexGuard aGuard;
|
|
static uno::Reference<beans::XPropertySetInfo> aRef =
|
|
new SfxItemPropertySetInfo( m_aPropSet.getPropertyMap() );
|
|
return aRef;
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::setPropertyValue(
|
|
const OUString& rPropertyName, const uno::Any& rValue)
|
|
{
|
|
if ( rPropertyName == SC_UNONAME_ROLE )
|
|
{
|
|
if ( !(rValue >>= m_aRole))
|
|
throw lang::IllegalArgumentException();
|
|
}
|
|
else if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
|
|
{
|
|
bool bOldValue = m_bIncludeHiddenCells;
|
|
if ( !(rValue >>= m_bIncludeHiddenCells))
|
|
throw lang::IllegalArgumentException();
|
|
if( bOldValue != m_bIncludeHiddenCells )
|
|
m_xDataArray.reset(new std::vector<Item>);//data array is dirty now
|
|
}
|
|
else if( rPropertyName == "TimeBased" )
|
|
{
|
|
bool bTimeBased = mbTimeBased;
|
|
rValue>>= bTimeBased;
|
|
mbTimeBased = bTimeBased;
|
|
}
|
|
else
|
|
throw beans::UnknownPropertyException(rPropertyName);
|
|
// TODO: support optional properties
|
|
}
|
|
|
|
uno::Any SAL_CALL ScChart2DataSequence::getPropertyValue(const OUString& rPropertyName)
|
|
{
|
|
uno::Any aRet;
|
|
if ( rPropertyName == SC_UNONAME_ROLE )
|
|
aRet <<= m_aRole;
|
|
else if ( rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS )
|
|
aRet <<= m_bIncludeHiddenCells;
|
|
else if ( rPropertyName == SC_UNONAME_HIDDENVALUES )
|
|
{
|
|
// This property is read-only thus cannot be set externally via
|
|
// setPropertyValue(...).
|
|
BuildDataCache();
|
|
aRet <<= m_aHiddenValues;
|
|
}
|
|
else if (rPropertyName == SC_UNONAME_TIME_BASED)
|
|
{
|
|
aRet <<= mbTimeBased;
|
|
}
|
|
else if (rPropertyName == SC_UNONAME_HAS_STRING_LABEL)
|
|
{
|
|
// Read-only property. It returns whether or not the label value is a
|
|
// direct user input, rather than an indirect reference.
|
|
bool bHasStringLabel = false;
|
|
if (m_aTokens.size() == 1)
|
|
{
|
|
const formula::FormulaToken& rToken = *m_aTokens[0];
|
|
bHasStringLabel = rToken.GetType() == formula::svString;
|
|
}
|
|
aRet <<= bHasStringLabel;
|
|
}
|
|
else
|
|
throw beans::UnknownPropertyException(rPropertyName);
|
|
// TODO: support optional properties
|
|
return aRet;
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::addPropertyChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XPropertyChangeListener>& /*xListener*/)
|
|
{
|
|
// FIXME: real implementation
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::removePropertyChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XPropertyChangeListener>& /*rListener*/)
|
|
{
|
|
// FIXME: real implementation
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::addVetoableChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
|
|
{
|
|
// FIXME: real implementation
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void SAL_CALL ScChart2DataSequence::removeVetoableChangeListener(
|
|
const OUString& /*rPropertyName*/,
|
|
const uno::Reference< beans::XVetoableChangeListener>& /*rListener*/)
|
|
{
|
|
// FIXME: real implementation
|
|
OSL_FAIL( "Not yet implemented" );
|
|
}
|
|
|
|
void ScChart2DataSequence::setDataChangedHint(bool b)
|
|
{
|
|
m_bGotDataChangedHint = b;
|
|
}
|
|
|
|
sal_Bool ScChart2DataSequence::switchToNext(sal_Bool bWrap)
|
|
{
|
|
if(!mbTimeBased)
|
|
return true;
|
|
|
|
if(mnCurrentTab >= mnTimeBasedEnd)
|
|
{
|
|
if(bWrap)
|
|
setToPointInTime(0);
|
|
return false;
|
|
}
|
|
|
|
for(const auto& rxToken : m_aTokens)
|
|
{
|
|
if (rxToken->GetType() != svDoubleRef)
|
|
continue;
|
|
|
|
ScComplexRefData& rData = *rxToken->GetDoubleRef();
|
|
ScSingleRefData& s = rData.Ref1;
|
|
ScSingleRefData& e = rData.Ref2;
|
|
|
|
s.IncTab(1);
|
|
e.IncTab(1);
|
|
}
|
|
|
|
++mnCurrentTab;
|
|
|
|
RebuildDataCache();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScChart2DataSequence::setRange(sal_Int32 nStart, sal_Int32 nEnd)
|
|
{
|
|
mnTimeBasedStart = nStart;
|
|
mnTimeBasedEnd = nEnd;
|
|
mnCurrentTab = mnTimeBasedStart;
|
|
}
|
|
|
|
sal_Bool ScChart2DataSequence::setToPointInTime(sal_Int32 nPoint)
|
|
{
|
|
if(nPoint > mnTimeBasedEnd - mnTimeBasedStart)
|
|
return false;
|
|
|
|
SCTAB nTab = mnTimeBasedStart + nPoint;
|
|
for(const auto& rxToken : m_aTokens)
|
|
{
|
|
if (rxToken->GetType() != svDoubleRef)
|
|
continue;
|
|
|
|
ScComplexRefData& rData = *rxToken->GetDoubleRef();
|
|
ScSingleRefData& s = rData.Ref1;
|
|
ScSingleRefData& e = rData.Ref2;
|
|
|
|
s.SetAbsTab(nTab);
|
|
e.SetAbsTab(nTab);
|
|
}
|
|
|
|
mnCurrentTab = nTab;
|
|
|
|
RebuildDataCache();
|
|
|
|
return true;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|