/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SC_SIMPLE_SERVICE_INFO( ScChart2DataProvider, "ScChart2DataProvider", "com.sun.star.chart2.data.DataProvider") SC_SIMPLE_SERVICE_INFO( ScChart2DataSource, "ScChart2DataSource", "com.sun.star.chart2.data.DataSource") SC_SIMPLE_SERVICE_INFO( ScChart2DataSequence, "ScChart2DataSequence", "com.sun.star.chart2.data.DataSequence") 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 { const SfxItemPropertyMapEntry* lcl_GetDataProviderPropertyMap() { static const SfxItemPropertyMapEntry aDataProviderPropertyMap_Impl[] = { { OUString(SC_UNONAME_INCLUDEHIDDENCELLS), 0, cppu::UnoType::get(), 0, 0 }, { OUString(SC_UNONAME_USE_INTERNAL_DATA_PROVIDER), 0, cppu::UnoType::get(), 0, 0 }, { OUString(), 0, css::uno::Type(), 0, 0 } }; return aDataProviderPropertyMap_Impl; } const SfxItemPropertyMapEntry* lcl_GetDataSequencePropertyMap() { static const SfxItemPropertyMapEntry aDataSequencePropertyMap_Impl[] = { {OUString(SC_UNONAME_HIDDENVALUES), 0, cppu::UnoType>::get(), 0, 0 }, {OUString(SC_UNONAME_ROLE), 0, cppu::UnoType::get(), 0, 0 }, {OUString(SC_UNONAME_INCLUDEHIDDENCELLS), 0, cppu::UnoType::get(), 0, 0 }, { OUString(), 0, css::uno::Type(), 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; SfxObjectShell * pObjSh( pDoc ? pDoc->GetDocumentShell() : nullptr ); if( pObjSh ) xModel.set( pObjSh->GetModel()); return xModel; } struct TokenTable { SCROW mnRowCount; SCCOL mnColCount; vector> 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 pToken ) { maTokens.push_back( std::move(pToken) ); OSL_ENSURE( maTokens.size()<= static_cast( mnColCount*mnRowCount ), "too many tokens" ); } sal_uInt32 getIndex(SCCOL nCol, SCROW nRow) const { OSL_ENSURE( nCol(nCol*mnRowCount + nRow); OSL_ENSURE( maTokens.size()>= static_cast( mnColCount*mnRowCount ), "too few tokens" ); return nRet; } vector getColRanges(SCCOL nCol) const; vector getRowRanges(SCROW nRow) const; vector getAllRanges() const; }; vector TokenTable::getColRanges(SCCOL nCol) const { if (nCol >= mnColCount) return vector(); if( mnRowCount<=0 ) return vector(); vector 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(aTokens, pCopy, ScAddress()); } return aTokens; } vector TokenTable::getRowRanges(SCROW nRow) const { if (nRow >= mnRowCount) return vector(); if( mnColCount<=0 ) return vector(); vector 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(aTokens, p2, ScAddress()); } return aTokens; } vector TokenTable::getAllRanges() const { vector 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(aTokens, p2, ScAddress()); } return aTokens; } typedef std::map> FormulaTokenMap; typedef std::map 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 getLeftUpperCornerRanges() const; vector getAllColHeaderRanges() const; vector getAllRowHeaderRanges() const; vector getColHeaderRanges(SCCOL nChartCol) const; vector getRowHeaderRanges(SCROW nChartRow) const; vector getDataColRanges(SCCOL nCol) const; vector getDataRowRanges(SCROW nRow) const; private: 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) { // 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(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 lets 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 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 Chart2PositionMap::getLeftUpperCornerRanges() const { return maLeftUpperCorner.getAllRanges(); } vector Chart2PositionMap::getAllColHeaderRanges() const { return maColHeaders.getAllRanges(); } vector Chart2PositionMap::getAllRowHeaderRanges() const { return maRowHeaders.getAllRanges(); } vector Chart2PositionMap::getColHeaderRanges(SCCOL nCol) const { return maColHeaders.getColRanges( nCol); } vector Chart2PositionMap::getRowHeaderRanges(SCROW nRow) const { return maRowHeaders.getRowRanges( nRow); } vector Chart2PositionMap::getDataColRanges(SCCOL nCol) const { return maData.getColRanges( nCol); } vector Chart2PositionMap::getDataRowRanges(SCROW nRow) const { return maData.getRowRanges( 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& 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& mrRefTokens; std::unique_ptr mpPositionMap; GlueType meGlue; SCCOL mnStartCol; SCROW mnStartRow; ScDocument* const 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; ScRefTokenHelper::getDoubleRefDataFromToken(aData, mrRefTokens.front()); 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(n1); if (n2 > nEndCol) nEndCol = static_cast(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(n1); if (n2 > nEndRow) nEndRow = static_cast(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(nColSize*nRowSize); vector aCellStates(nCR, Hole); // Mark all referenced cells "occupied". for (const auto& rxToken : mrRefTokens) { ScComplexRefData aData; ScRefTokenHelper::getDoubleRefDataFromToken(aData, rxToken); SCCOL nCol1 = aData.Ref1.Col() - mnStartCol; SCCOL nCol2 = aData.Ref2.Col() - mnStartCol; SCROW nRow1 = aData.Ref1.Row() - mnStartRow; SCROW nRow2 = aData.Ref2.Row() - mnStartRow; for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) for (SCROW nRow = nRow1; nRow <= nRow2; ++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 (SCCOL 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.get()) 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(); 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(nTab) << 16) | (bNoGlue ? 0 : static_cast(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); if (rCol.find(nInsRow) == rCol.end()) { if (bExternal) rCol[ nInsRow ].reset(new ScExternalSingleRefToken(nFileId, aTabName, aCellData)); else rCol[ nInsRow ].reset(new ScSingleRefToken(aCellData)); } } } } nNoGlueRow += nRow2 - nRow1 + 1; } bool bFillRowHeader = mbRowHeaders; bool bFillColumnHeader = mbColHeaders; SCSIZE nAllColCount = static_cast(aCols.size()); SCSIZE nAllRowCount = 0; if (!aCols.empty()) { FormulaTokenMap& rCol = aCols.begin()->second; if (mbDummyUpperLeft) if (rCol.find(0) == rCol.end()) rCol[ 0 ] = nullptr; // dummy for labeling nAllRowCount = static_cast(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; if (rCol.find(nKey) == rCol.end()) rCol[ nKey ] = nullptr; } } } } mpPositionMap.reset( new Chart2PositionMap( static_cast(nAllColCount), static_cast(nAllRowCount), bFillRowHeader, bFillColumnHeader, aCols, mpDoc)); } /** * Function object to create a range string from a token list. */ class Tokens2RangeString { public: Tokens2RangeString(ScDocument* pDoc, FormulaGrammar::Grammar eGram, sal_Unicode cRangeSep) : mpRangeStr(new OUStringBuffer), mpDoc(pDoc), 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 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* pDoc) : mpRangeStr(new OUStringBuffer), mpDoc(pDoc), mbFirst(true) { } void operator() (const ScTokenRef& rToken) { if (mbFirst) mbFirst = false; else mpRangeStr->append(mcRangeSep); ScTokenRef aStart, aEnd; bool bValidToken = splitRangeToken(rToken, 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 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; svl::SharedString aTabName = svl::SharedString::getEmptyString(); if (bExternal) aTabName = pToken->GetString(); // 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(aData.Ref1)); if (bExternal) rEnd.reset(new ScExternalSingleRefToken(nFileId, aTabName, aData.Ref2)); else rEnd.reset(new ScSingleRefToken(aData.Ref2)); return true; } static void setRelative(ScSingleRefData& rData) { rData.SetColRel(true); rData.SetRowRel(true); rData.SetTabRel(true); } private: shared_ptr mpRangeStr; ScDocument* mpDoc; static const sal_Unicode mcRangeSep = ' '; static const sal_Unicode mcAddrSep = ':'; bool mbFirst; }; void lcl_convertTokensToString(OUString& rStr, const vector& rTokens, ScDocument* pDoc) { const sal_Unicode cRangeSep = ScCompiler::GetNativeSymbolChar(ocSep); FormulaGrammar::Grammar eGrammar = pDoc->GetGrammar(); Tokens2RangeString func(pDoc, 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 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, const uno::Reference< chart2::data::XDataProvider >& xDP, bool bIncludeHiddenCells ) { uno::Reference< chart2::data::XLabeledDataSequence > xResult; bool bHasValues = !aValueTokens.empty(); bool bHasLabel = !aLabelTokens.empty(); if( bHasValues || bHasLabel ) { try { 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, xDP, std::move(aValueTokens), bIncludeHiddenCells ) ); xResult->setValues( xSeq ); } if ( bHasLabel ) { uno::Reference< chart2::data::XDataSequence > xLabelSeq( new ScChart2DataSequence( pDoc, xDP, std::move(aLabelTokens), bIncludeHiddenCells ) ); 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(vector& rRefTokens, SCROW nCornerRowCount, SCCOL nCornerColumnCount) { using ::std::max; using ::std::min; if (rRefTokens.empty()) return false; SCCOL nMinCol = MAXCOLCOUNT; SCROW nMinRow = MAXROWCOUNT; SCCOL nMaxCol = 0; SCROW nMaxRow = 0; SCTAB nTab = 0; sal_uInt16 nFileId = 0; svl::SharedString aExtTabName; bool bExternal = false; vector::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: ; } } if (nMinRow >= nMaxRow || nMinCol >= nMaxCol || nMinRow >= MAXROWCOUNT || nMinCol >= MAXCOLCOUNT || nMaxRow >= MAXROWCOUNT || nMaxCol >= MAXCOLCOUNT) { // 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, aExtTabName, aData)); ScRefTokenHelper::join(rRefTokens, pCorner, ScAddress()); } else { ScTokenRef pCorner(new ScSingleRefToken(aData)); ScRefTokenHelper::join(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, aExtTabName, r)); ScRefTokenHelper::join(rRefTokens, pCorner, ScAddress()); } else { ScTokenRef pCorner(new ScDoubleRefToken(r)); ScRefTokenHelper::join(rRefTokens, pCorner, ScAddress()); } } return true; } #define SHRINK_RANGE_THRESHOLD 10000 class ShrinkRefTokenToDataRange { ScDocument* const 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& 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 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(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; ScChart2DataSource* pDS = nullptr; ::std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > aSeqs; // Fill Categories if( bCategories ) { vector aValueTokens; if (bOrientCol) aValueTokens = pChartMap->getAllRowHeaderRanges(); else aValueTokens = pChartMap->getAllColHeaderRanges(); vector aLabelTokens( pChartMap->getLeftUpperCornerRanges()); uno::Reference< chart2::data::XLabeledDataSequence > xCategories = lcl_createLabeledDataSequenceFromTokens( std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, this, 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 aValueTokens; vector aLabelTokens; if (bOrientCol) { aValueTokens = pChartMap->getDataColRanges(static_cast(i)); aLabelTokens = pChartMap->getColHeaderRanges(static_cast(i)); } else { aValueTokens = pChartMap->getDataRowRanges(static_cast(i)); aLabelTokens = pChartMap->getRowHeaderRanges(static_cast(i)); } uno::Reference< chart2::data::XLabeledDataSequence > xChartSeries = lcl_createLabeledDataSequenceFromTokens( std::move(aValueTokens), std::move(aLabelTokens), m_pDocument, this, 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(new vector) { } void operator() (const ScTokenRef& pToken) const { if (!ScRefTokenHelper::isRef(pToken)) return; const ScSingleRefData& r = *pToken->GetSingleRef(); mpTabNumVector->push_back(r.Tab()); } void getVector(vector& rVector) { mpTabNumVector->swap(rVector); } private: shared_ptr< vector > mpTabNumVector; }; class RangeAnalyzer { public: RangeAnalyzer(); void initRangeAnalyzer( const vector& 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 vector& 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(mnColumnCount, static_cast(abs(r.Ref2.Col() - r.Ref1.Col())+1)); mnRowCount = std::max(mnRowCount, static_cast(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( mnColumnCount, 1); mnRowCount = std::max( mnRowCount, 1); if( mnStartColumn == -1 ) { mnStartColumn = r.Col(); mnStartRow = r.Row(); } else { if (mnStartColumn != r.Col() && mnStartRow != r.Row()) 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 constructKey(const uno::Reference< chart2::data::XLabeledDataSequence>& xNew) { std::pair 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 aAllCategoriesValuesTokens; vector aAllSeriesLabelTokens; chart::ChartDataRowSource eRowSource = chart::ChartDataRowSource_COLUMNS; vector 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("Role") >>= aRole) && aRole == "categories" ) bThisIsCategories = bHasCategories = true; } RangeAnalyzer aLabel,aValues; // label uno::Reference< chart2::data::XDataSequence > xLabel( xLS->getLabel()); if( xLabel.is()) { bFirstCellAsLabel = true; vector aTokens; const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep); ScRefTokenHelper::compileRangeRepresentation( aTokens, xLabel->getSourceRangeRepresentation(), m_pDocument, cSep, m_pDocument->GetGrammar(), true); aLabel.initRangeAnalyzer(aTokens); for (const auto& rxToken : aTokens) { ScRefTokenHelper::join(aAllTokens, rxToken, ScAddress()); if(!bThisIsCategories) ScRefTokenHelper::join(aAllSeriesLabelTokens, rxToken, ScAddress()); } if(bThisIsCategories) bHasCategoriesLabels=true; } // values uno::Reference< chart2::data::XDataSequence > xValues( xLS->getValues()); if( xValues.is()) { vector aTokens; const sal_Unicode cSep = ScCompiler::GetNativeSymbolChar(ocSep); ScRefTokenHelper::compileRangeRepresentation( aTokens, xValues->getSourceRangeRepresentation(), m_pDocument, cSep, m_pDocument->GetGrammar(), true); aValues.initRangeAnalyzer(aTokens); for (const auto& rxToken : aTokens) { ScRefTokenHelper::join(aAllTokens, rxToken, ScAddress()); if(bThisIsCategories) ScRefTokenHelper::join(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 aTableNumVector; InsertTabNumber func; func = ::std::for_each(aAllTokens.begin(), aAllTokens.end(), func); func.getVector(aTableNumVector); aResult.emplace_back( "TableNumberList", -1, uno::makeAny( lcl_createTableNumberList( aTableNumVector ) ), beans::PropertyState_DIRECT_VALUE ); } if( bRowSourceDetected ) { // DataRowSource (calculated before) aResult.emplace_back( "DataRowSource", -1, uno::makeAny( eRowSource ), beans::PropertyState_DIRECT_VALUE ); // HasCategories aResult.emplace_back( "HasCategories", -1, uno::makeAny( bHasCategories ), beans::PropertyState_DIRECT_VALUE ); // FirstCellAsLabel aResult.emplace_back( "FirstCellAsLabel", -1, uno::makeAny( 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(aAllSeriesLabelTokens); aLeft.initRangeAnalyzer(aAllCategoriesValuesTokens); } else { aTop.initRangeAnalyzer(aAllCategoriesValuesTokens); aLeft.initRangeAnalyzer(aAllSeriesLabelTokens); } lcl_addUpperLeftCornerIfMissing(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::makeAny( 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,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 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 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::makeAny( 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 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 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, this, std::move(aRefTokens), m_bIncludeHiddenCells)); return xResult; } uno::Reference SAL_CALL ScChart2DataProvider::createDataSequenceByValueArray( const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/ ) { return uno::Reference(); } 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& aTokens ) { if (!aTokens.hasElements()) return false; ScTokenArray aCode; 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 SAL_CALL ScChart2DataProvider::createDataSequenceByFormulaTokens( const Sequence& aTokens ) { uno::Reference xResult; if (!aTokens.hasElements()) return xResult; ScTokenArray aCode; if (!ScTokenConversion::ConvertToTokenArray(*m_pDocument, aCode, aTokens)) return xResult; sal_uInt16 n = aCode.GetLen(); if (!n) return xResult; vector 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 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, this, 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 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, "" ); 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 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(0.0), mbIsValue(false) { ::rtl::math::setNan(&mfValue); } ScChart2DataSequence::HiddenRangeListener::HiddenRangeListener(ScChart2DataSequence& rParent) : mrParent(rParent) { } ScChart2DataSequence::HiddenRangeListener::~HiddenRangeListener() { } void ScChart2DataSequence::HiddenRangeListener::notify() { mrParent.setDataChangedHint(true); } ScChart2DataSequence::ScChart2DataSequence( ScDocument* pDoc, const uno::Reference < chart2::data::XDataProvider >& xDP, vector&& rTokens, bool bIncludeHiddenCells ) : m_bIncludeHiddenCells( bIncludeHiddenCells) , m_nObjectId( 0 ) , m_pDocument( pDoc) , m_aTokens(std::move(rTokens)) , m_xDataProvider( xDP) , 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); } 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() ) { m_pValueListener->EndListeningAll(); if( m_pDocument ) { 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(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_aDataArray.empty()) return; StopListeningToAllExternalRefs(); ::std::vector aHiddenValues; sal_Int32 nDataCount = 0; for (const auto& rxToken : m_aTokens) { if (ScRefTokenHelper::isExternalRef(rxToken)) { nDataCount += FillCacheFromExternalRef(rxToken); } else { ScRange aRange; if (!ScRefTokenHelper::getRangeFromToken(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) { 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.meType) { case CELLTYPE_VALUE: aItem.mfValue = aCell.getValue(); aItem.mbIsValue = true; break; case CELLTYPE_FORMULA: { ScFormulaCell* pFCell = aCell.mpFormula; 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_aDataArray.push_back(aItem); ++nDataCount; } } } } } // convert the hidden cell list to sequence. m_aHiddenValues.realloc(aHiddenValues.size()); std::copy( aHiddenValues.begin(), aHiddenValues.end(), m_aHiddenValues.begin()); // Clear the data series cache when the array is re-built. m_aMixedDataCache.realloc(0); } void ScChart2DataSequence::RebuildDataCache() { if (!m_bExtDataRebuildQueued) { m_aDataArray.clear(); 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(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); 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; Color* pColor = nullptr; sal_uInt32 nFmt = 0; if (pTable) { // Get the correct format index from the cache. SCCOL nCol = aRange.aStart.Col() + static_cast(nC); SCROW nRow = aRange.aStart.Row() + static_cast(nR); pTable->getCell(nCol, nRow, &nFmt); } pFormatter->GetOutputString(fVal, nFmt, aItem.maString, &pColor); } m_aDataArray.push_back(aItem); ++nDataCount; } else if (pMat->IsStringOrEmpty(nC, nR)) { Item aItem; aItem.mbIsValue = false; aItem.maString = pMat->GetString(nC, nR).getString(); m_aDataArray.emplace_back(aItem); ++nDataCount; } } } } return nDataCount; } void ScChart2DataSequence::UpdateTokensFromRanges(const ScRangeList& rRanges) { if (!m_pRangeIndices.get()) return; for ( size_t i = 0, nCount = rRanges.size(); i < nCount; ++i ) { ScTokenRef pToken; const ScRange & rRange = rRanges[i]; ScRefTokenHelper::getTokenFromRange(pToken, rRange); sal_uInt32 nOrigPos = (*m_pRangeIndices)[i]; m_aTokens[nOrigPos] = 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.get()) m_pExtRefListener.reset(new ExternalRefListener(*this, m_pDocument)); return m_pExtRefListener.get(); } void ScChart2DataSequence::StopListeningToAllExternalRefs() { if (!m_pExtRefListener.get()) return; const std::unordered_set& 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::CopyData(const ScChart2DataSequence& r) { if (!m_pDocument) { OSL_FAIL("document instance is nullptr!?"); return; } std::vector aDataArray(r.m_aDataArray); m_aDataArray.swap(aDataArray); m_aHiddenValues = r.m_aHiddenValues; m_aRole = r.m_aRole; if (r.m_pRangeIndices.get()) m_pRangeIndices.reset(new vector(*r.m_pRangeIndices)); if (r.m_pExtRefListener.get()) { // 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& rFileIds = r.m_pExtRefListener->getAllFileIds(); for (const auto& rFileId : rFileIds) { pRefMgr->addLinkListener(rFileId, m_pExtRefListener.get()); m_pExtRefListener->addFileId(rFileId); } } } void ScChart2DataSequence::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint) { if ( dynamic_cast(&rHint) ) { // 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_pRangeIndices.reset(new vector); vector::const_iterator itrBeg = m_aTokens.begin(), itrEnd = m_aTokens.end(); for (vector::const_iterator itr = itrBeg ;itr != itrEnd; ++itr) { if (!ScRefTokenHelper::isExternalRef(*itr)) { ScRange aRange; ScRefTokenHelper::getRangeFromToken(aRange, *itr, ScAddress()); aRanges.push_back(aRange); sal_uInt32 nPos = distance(itrBeg, itr); m_pRangeIndices->push_back(nPos); } } OSL_ENSURE(m_pRangeIndices->size() == aRanges.size(), "range list and range index list have different sizes."); unique_ptr pUndoRanges; if ( m_pDocument->HasUnoRefUndo() ) pUndoRanges.reset(new ScRangeList(aRanges)); const ScUpdateRefHint& rRef = static_cast(rHint); bool bChanged = aRanges.UpdateReference( rRef.GetMode(), m_pDocument, rRef.GetRange(), rRef.GetDx(), rRef.GetDy(), rRef.GetDz()); if (bChanged) { OSL_ENSURE(m_pRangeIndices->size() == aRanges.size(), "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 ( dynamic_cast(&rHint) ) { const ScUnoRefUndoHint& rUndoHint = static_cast(rHint); do { if (rUndoHint.GetObjectId() != m_nObjectId) break; // The hint object provides the old ranges. Restore the old state // from these ranges. if (!m_pRangeIndices.get() || m_pRangeIndices->empty()) { OSL_FAIL(" faulty range indices"); break; } const ScRangeList& rRanges = rUndoHint.GetRanges(); size_t nCount = rRanges.size(); if (nCount != m_pRangeIndices->size()) { OSL_FAIL("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_aDataArray.clear(); lang::EventObject aEvent; aEvent.Source.set(static_cast(this)); if( m_pDocument ) { for (const uno::Reference & 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_aDataArray.clear(); } } } 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) : ScExternalRefManager::LinkListener(), 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; } } 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_aDataArray.size(); m_aMixedDataCache.realloc(nCount); uno::Any* pArr = m_aMixedDataCache.getArray(); for (const Item &rItem : m_aDataArray) { 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(); double fNAN; ::rtl::math::setNan(&fNAN); sal_Int32 nCount = m_aDataArray.size(); uno::Sequence aSeq(nCount); double* pArr = aSeq.getArray(); for (const Item& rItem : m_aDataArray) { *pArr = rItem.mbIsValue ? rItem.mfValue : fNAN; ++pArr; } return aSeq; } // XTextualDataSequence -------------------------------------------------- uno::Sequence< OUString > SAL_CALL ScChart2DataSequence::getTextualData() { SolarMutexGuard aGuard; uno::Sequence aSeq; if ( !m_pDocument ) throw uno::RuntimeException(); BuildDataCache(); sal_Int32 nCount = m_aDataArray.size(); if ( nCount > 0 ) { aSeq = uno::Sequence(nCount); OUString* pArr = aSeq.getArray(); for (const Item& rItem : m_aDataArray) { *pArr = rItem.maString; ++pArr; } } else if ( m_aTokens.front() ) { if( m_aTokens.front()->GetType() == svString ) { aSeq = uno::Sequence(1); aSeq[0] = 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() : mnCols(0), mnRows(0) {} void operator() (const ScTokenRef& pToken) { ScRange r; bool bExternal = ScRefTokenHelper::isExternalRef(pToken); ScRefTokenHelper::getRangeFromToken(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: SCCOL mnCols; SCROW mnRows; }; /** * This function object is used to generate label strings from a list of * reference tokens. */ class GenerateLabelStrings { public: GenerateLabelStrings(sal_Int32 nSize, chart2::data::LabelOrigin eOrigin, bool bColumn) : mpLabels(new Sequence(nSize)), meOrigin(eOrigin), mnCount(0), mbColumn(bColumn) {} void operator() (const ScTokenRef& pToken) { bool bExternal = ScRefTokenHelper::isExternalRef(pToken); ScRange aRange; ScRefTokenHelper::getRangeFromToken(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) { OUString aString = ScResId(STR_ROW) + " " + OUString::number( nRow+1 ); pArr[mnCount] = aString; } else //only indices for categories pArr[mnCount] = OUString::number( mnCount+1 ); ++mnCount; } } } const Sequence& getLabels() const { return *mpLabels; } private: shared_ptr< Sequence > 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; 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(); } // Generate label strings based on the info so far. sal_Int32 nCount = bColumn ? nCols : nRows; GenerateLabelStrings genLabels(nCount, eOrigin, bColumn); genLabels = ::std::for_each(m_aTokens.begin(), m_aTokens.end(), genLabels); Sequence aSeq = genLabels.getLabels(); return aSeq; } namespace { sal_uInt32 getDisplayNumberFormat(const ScDocument* pDoc, const ScAddress& rPos) { sal_uInt32 nFormat = pDoc->GetNumberFormat(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_aDataArray) { ScRefCellValue aCell(*m_pDocument, rItem.mAddress); if (!aCell.isEmpty() && aCell.hasNumeric()) { return static_cast(getDisplayNumberFormat(m_pDocument, rItem.mAddress)); } } // we could not find a non-empty cell return 0; } if (nIndex < 0 || nIndex >= static_cast(m_aDataArray.size())) { SAL_WARN("sc.ui", "Passed invalid index to getNumberFormatKeyByIndex(). Will return default value '0'."); return 0; } return static_cast(getDisplayNumberFormat(m_pDocument, m_aDataArray.at(nIndex).mAddress)); } // XCloneable ================================================================ uno::Reference< util::XCloneable > SAL_CALL ScChart2DataSequence::createClone() { SolarMutexGuard aGuard; // Clone tokens. vector aTokensNew; aTokensNew.reserve(m_aTokens.size()); for (const auto& rxToken : m_aTokens) { ScTokenRef p(rxToken->Clone()); aTokensNew.push_back(p); } rtl::Reference p(new ScChart2DataSequence(m_pDocument, m_xDataProvider, std::move(aTokensNew), m_bIncludeHiddenCells)); p->CopyData(*this); uno::Reference< util::XCloneable > xClone(p.get()); return xClone; } // 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(aRanges, m_aTokens, ScAddress()); m_aValueListeners.emplace_back( aListener ); if ( m_aValueListeners.size() == 1 ) { 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(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 aSelfHold(this); // in case the listeners have the last ref sal_uInt16 nCount = m_aValueListeners.size(); for ( sal_uInt16 n=nCount; n--; ) { uno::Reference& 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.get() && 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 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_aDataArray.clear();//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: */