diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/unocore/unochart.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/unocore/unochart.cxx')
-rw-r--r-- | sw/source/core/unocore/unochart.cxx | 2695 |
1 files changed, 2695 insertions, 0 deletions
diff --git a/sw/source/core/unocore/unochart.cxx b/sw/source/core/unocore/unochart.cxx new file mode 100644 index 0000000000..db97093a31 --- /dev/null +++ b/sw/source/core/unocore/unochart.cxx @@ -0,0 +1,2695 @@ +/* -*- 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 <algorithm> +#include <string_view> + +#include <com/sun/star/chart/ChartDataRowSource.hpp> +#include <com/sun/star/chart2/data/LabelOrigin.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/deleter.hxx> +#include <o3tl/string_view.hxx> +#include <mutex> +#include <vcl/svapp.hxx> + +#include "XMLRangeHelper.hxx" +#include <unochart.hxx> +#include <swtable.hxx> +#include <unoprnms.hxx> +#include <unomap.hxx> +#include <unocrsr.hxx> +#include <unotbl.hxx> +#include <doc.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <frmfmt.hxx> +#include <ndole.hxx> +#include <swtypes.hxx> +#include <strings.hrc> +#include <comphelper/servicehelper.hxx> +#include <comphelper/string.hxx> +#include <svl/itemprop.hxx> + +using namespace ::com::sun::star; + +void SwChartHelper::DoUpdateAllCharts( SwDoc* pDoc ) +{ + if (!pDoc) + return; + + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) && + pONd->GetOLEObj().GetObject().IsChart() ) + { + // Load the object and set modified + + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + try + { + uno::Reference< util::XModifiable > xModif( xIP->getComponent(), uno::UNO_QUERY_THROW ); + xModif->setModified( true ); + } + catch ( uno::Exception& ) + { + } + + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } +} + +SwChartLockController_Helper::SwChartLockController_Helper( SwDoc *pDocument ) : + m_pDoc( pDocument ) + , m_aUnlockTimer( "sw::SwChartLockController_Helper aUnlockTimer" ) + , m_bIsLocked( false ) +{ + m_aUnlockTimer.SetTimeout( 1500 ); + m_aUnlockTimer.SetInvokeHandler( LINK( this, SwChartLockController_Helper, DoUnlockAllCharts )); +} + +SwChartLockController_Helper::~SwChartLockController_Helper() +{ + if (m_pDoc) // still connected? + suppress_fun_call_w_exception(Disconnect()); +} + +void SwChartLockController_Helper::StartOrContinueLocking() +{ + if (!m_bIsLocked) + LockAllCharts(); + m_aUnlockTimer.Start(); // start or continue time of locking +} + +void SwChartLockController_Helper::Disconnect() +{ + m_aUnlockTimer.Stop(); + UnlockAllCharts(); + m_pDoc = nullptr; +} + +void SwChartLockController_Helper::LockUnlockAllCharts( bool bLock ) +{ + if (!m_pDoc) + return; + + uno::Reference< frame::XModel > xRes; + SwOLENode *pONd; + SwStartNode *pStNd; + SwNodeIndex aIdx( *m_pDoc->GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + if (nullptr != ( pONd = aIdx.GetNode().GetOLENode() ) && + !pONd->GetChartTableName().isEmpty() /* is chart object? */) + { + uno::Reference < embed::XEmbeddedObject > xIP = pONd->GetOLEObj().GetOleRef(); + if ( svt::EmbeddedObjectRef::TryRunningState( xIP ) ) + { + xRes.set( xIP->getComponent(), uno::UNO_QUERY ); + if (xRes.is()) + { + if (bLock) + xRes->lockControllers(); + else + xRes->unlockControllers(); + } + } + } + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + + m_bIsLocked = bLock; +} + +IMPL_LINK_NOARG( SwChartLockController_Helper, DoUnlockAllCharts, Timer *, void ) +{ + UnlockAllCharts(); +} + +static std::mutex & GetChartMutex() +{ + static std::mutex aMutex; + return aMutex; +} + +static void LaunchModifiedEvent( + ::comphelper::OInterfaceContainerHelper4<util::XModifyListener> &rICH, + const uno::Reference< uno::XInterface > &rxI ) +{ + lang::EventObject aEvtObj( rxI ); + std::unique_lock aGuard(GetChartMutex()); + rICH.notifyEach( aGuard, &util::XModifyListener::modified, aEvtObj ); +} + +/** + * rCellRangeName needs to be of one of the following formats: + * - e.g. "A2:E5" or + * - e.g. "Table1.A2:E5" + */ +bool FillRangeDescriptor( + SwRangeDescriptor &rDesc, + std::u16string_view rCellRangeName ) +{ + sal_Int32 nToken = std::u16string_view::npos == rCellRangeName.find('.') ? 0 : 1; + std::u16string_view aCellRangeNoTableName( o3tl::getToken(rCellRangeName, nToken, '.' ) ); + OUString aTLName( o3tl::getToken(aCellRangeNoTableName, 0, ':') ); // name of top left cell + OUString aBRName( o3tl::getToken(aCellRangeNoTableName, 1, ':') ); // name of bottom right cell + if(aTLName.isEmpty() || aBRName.isEmpty()) + return false; + + rDesc.nTop = rDesc.nLeft = rDesc.nBottom = rDesc.nRight = -1; + SwXTextTable::GetCellPosition( aTLName, rDesc.nLeft, rDesc.nTop ); + SwXTextTable::GetCellPosition( aBRName, rDesc.nRight, rDesc.nBottom ); + rDesc.Normalize(); + OSL_ENSURE( rDesc.nTop != -1 && + rDesc.nLeft != -1 && + rDesc.nBottom != -1 && + rDesc.nRight != -1, + "failed to get range descriptor" ); + OSL_ENSURE( rDesc.nTop <= rDesc.nBottom && rDesc.nLeft <= rDesc.nRight, + "invalid range descriptor"); + return true; +} + +static OUString GetCellRangeName( const SwFrameFormat &rTableFormat, SwUnoCursor &rTableCursor ) +{ + OUString aRes; + + //!! see also SwXTextTableCursor::getRangeName + + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&rTableCursor); + if (!pUnoTableCursor) + return OUString(); + + // tdf#132714 empty outdated selection cache to avoid crashing in ActualizeSelection() + size_t nCount = pUnoTableCursor->GetSelectedBoxesCount(); + while (nCount--) + pUnoTableCursor->DeleteBox(nCount); + + pUnoTableCursor->MakeBoxSels(); + + const SwStartNode* pStart; + const SwTableBox* pStartBox = nullptr; + const SwTableBox* pEndBox = nullptr; + + pStart = pUnoTableCursor->GetPoint()->GetNode().FindTableBoxStartNode(); + if (pStart) + { + const SwTable* pTable = SwTable::FindTable( &rTableFormat ); + pEndBox = pTable->GetTableBox( pStart->GetIndex()); + aRes = pEndBox->GetName(); + + if(pUnoTableCursor->HasMark()) + { + pStart = pUnoTableCursor->GetMark()->GetNode().FindTableBoxStartNode(); + pStartBox = pTable->GetTableBox( pStart->GetIndex()); + } + OSL_ENSURE( pStartBox, "start box not found" ); + OSL_ENSURE( pEndBox, "end box not found" ); + + // need to switch start and end? + if (*pUnoTableCursor->GetPoint() < *pUnoTableCursor->GetMark()) + { + const SwTableBox* pTmpBox = pStartBox; + pStartBox = pEndBox; + pEndBox = pTmpBox; + } + + if (!pStartBox) + return aRes; + + aRes = pStartBox->GetName() + ":"; + if (pEndBox) + aRes += pEndBox->GetName(); + else + aRes += pStartBox->GetName(); + } + + return aRes; +} + +static OUString GetRangeRepFromTableAndCells( std::u16string_view rTableName, + std::u16string_view rStartCell, std::u16string_view rEndCell, + bool bForceEndCellName ) +{ + OSL_ENSURE( !rTableName.empty(), "table name missing" ); + OSL_ENSURE( !rStartCell.empty(), "cell name missing" ); + OUString aRes = OUString::Concat(rTableName) + "." + rStartCell; + + if (!rEndCell.empty()) + { + aRes += OUString::Concat(":") + rEndCell; + } + else if (bForceEndCellName) + { + aRes += OUString::Concat(":") + rStartCell; + } + + return aRes; +} + +static bool GetTableAndCellsFromRangeRep( + std::u16string_view rRangeRepresentation, + OUString &rTableName, + OUString &rStartCell, + OUString &rEndCell, + bool bSortStartEndCells = true ) +{ + // parse range representation for table name and cell/range names + // accepted format sth like: "Table1.A2:C5" , "Table2.A2.1:B3.2" + OUString aTableName; // table name + OUString aStartCell; // name of top left cell + OUString aEndCell; // name of bottom right cell + size_t nIdx = rRangeRepresentation.find( '.' ); + if (nIdx != std::u16string_view::npos) + { + aTableName = rRangeRepresentation.substr( 0, nIdx ); + std::u16string_view aRange = rRangeRepresentation.substr( nIdx + 1 ); // cell range + size_t nPos = aRange.find( ':' ); + if (nPos != std::u16string_view::npos) // a cell-range like "Table1.A2:D4" + { + aStartCell = aRange.substr( 0, nPos ); + aEndCell = aRange.substr( nPos + 1 ); + + // need to switch start and end cell ? + // (does not check for normalization here) + if (bSortStartEndCells && 1 == sw_CompareCellsByColFirst( aStartCell, aEndCell )) + { + OUString aTmp( aStartCell ); + aStartCell = aEndCell; + aEndCell = aTmp; + } + } + else // a single cell like in "Table1.B3" + { + aStartCell = aEndCell = aRange; + } + } + + bool bSuccess = !aTableName.isEmpty() && + !aStartCell.isEmpty() && !aEndCell.isEmpty(); + if (bSuccess) + { + rTableName = aTableName; + rStartCell = aStartCell; + rEndCell = aEndCell; + } + return bSuccess; +} + +static void GetTableByName( const SwDoc &rDoc, std::u16string_view rTableName, + SwFrameFormat **ppTableFormat, SwTable **ppTable) +{ + SwFrameFormat *pTableFormat = nullptr; + + // find frame format of table + //! see SwXTextTables::getByName + const size_t nCount = rDoc.GetTableFrameFormatCount(true); + for (size_t i = 0; i < nCount && !pTableFormat; ++i) + { + SwFrameFormat& rTableFormat = rDoc.GetTableFrameFormat(i, true); + if(rTableName == rTableFormat.GetName()) + pTableFormat = &rTableFormat; + } + + if (ppTableFormat) + *ppTableFormat = pTableFormat; + + if (ppTable) + *ppTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr; +} + +static void GetFormatAndCreateCursorFromRangeRep( + const SwDoc *pDoc, + std::u16string_view rRangeRepresentation, // must be a single range (i.e. so called sub-range) + SwFrameFormat **ppTableFormat, // will be set to the table format of the table used in the range representation + std::shared_ptr<SwUnoCursor>& rpUnoCursor ) // will be set to cursor spanning the cell range (cursor will be created!) +{ + OUString aTableName; // table name + OUString aStartCell; // name of top left cell + OUString aEndCell; // name of bottom right cell + bool bNamesFound = GetTableAndCellsFromRangeRep( rRangeRepresentation, + aTableName, aStartCell, aEndCell ); + + if (!bNamesFound) + { + if (ppTableFormat) + *ppTableFormat = nullptr; + rpUnoCursor.reset(); + } + else + { + SwFrameFormat *pTableFormat = nullptr; + + // is the correct table format already provided? + if (*ppTableFormat != nullptr && (*ppTableFormat)->GetName() == aTableName) + pTableFormat = *ppTableFormat; + else + GetTableByName( *pDoc, aTableName, &pTableFormat, nullptr ); + + *ppTableFormat = pTableFormat; + + rpUnoCursor.reset(); // default result in case of failure + + SwTable *pTable = pTableFormat ? SwTable::FindTable( pTableFormat ) : nullptr; + // create new SwUnoCursor spanning the specified range + //! see also SwXTextTable::GetRangeByName + // #i80314# + // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)> + const SwTableBox* pTLBox = + pTable ? pTable->GetTableBox( aStartCell, true ) : nullptr; + if(pTLBox) + { + const SwStartNode* pSttNd = pTLBox->GetSttNd(); + SwPosition aPos(*pSttNd); + + // set cursor to top left box of range + auto pUnoCursor = pTableFormat->GetDoc()->CreateUnoCursor(aPos, true); + pUnoCursor->Move( fnMoveForward, GoInNode ); + pUnoCursor->SetRemainInSection( false ); + + // #i80314# + // perform validation check. Thus, pass <true> as 2nd parameter to <SwTable::GetTableBox(..)> + const SwTableBox* pBRBox = pTable->GetTableBox( aEndCell, true ); + if(pBRBox) + { + pUnoCursor->SetMark(); + pUnoCursor->GetPoint()->Assign( *pBRBox->GetSttNd() ); + pUnoCursor->Move( fnMoveForward, GoInNode ); + SwUnoTableCursor& rCursor = + dynamic_cast<SwUnoTableCursor&>(*pUnoCursor); + // HACK: remove pending actions for old style tables + UnoActionRemoveContext aRemoveContext(rCursor); + rCursor.MakeBoxSels(); + rpUnoCursor = pUnoCursor; + } + } + } +} + +static bool GetSubranges( std::u16string_view rRangeRepresentation, + uno::Sequence< OUString > &rSubRanges, bool bNormalize ) +{ + bool bRes = true; + const sal_Int32 nLen = comphelper::string::getTokenCount(rRangeRepresentation, ';'); + uno::Sequence< OUString > aRanges( nLen ); + + sal_Int32 nCnt = 0; + if (nLen != 0) + { + OUString *pRanges = aRanges.getArray(); + OUString aFirstTable; + sal_Int32 nPos = 0; + for( sal_Int32 i = 0; i < nLen && bRes; ++i ) + { + const OUString aRange( o3tl::getToken(rRangeRepresentation, 0, ';', nPos ) ); + if (!aRange.isEmpty()) + { + pRanges[nCnt] = aRange; + + OUString aTableName, aStartCell, aEndCell; + if (!GetTableAndCellsFromRangeRep( aRange, + aTableName, aStartCell, aEndCell )) + bRes = false; + + if (bNormalize) + { + sw_NormalizeRange( aStartCell, aEndCell ); + pRanges[nCnt] = GetRangeRepFromTableAndCells( aTableName, + aStartCell, aEndCell, true ); + } + + // make sure to use only a single table + if (nCnt == 0) + aFirstTable = aTableName; + else + if (aFirstTable != aTableName) bRes = false; + + ++nCnt; + } + } + } + aRanges.realloc( nCnt ); + + rSubRanges = aRanges; + return bRes; +} + +static void SortSubranges( uno::Sequence< OUString > &rSubRanges, bool bCmpByColumn ) +{ + sal_Int32 nLen = rSubRanges.getLength(); + OUString *pSubRanges = rSubRanges.getArray(); + + OUString aSmallestTableName; + OUString aSmallestStartCell; + OUString aSmallestEndCell; + + for (sal_Int32 i = 0; i < nLen; ++i) + { + sal_Int32 nIdxOfSmallest = i; + GetTableAndCellsFromRangeRep( pSubRanges[nIdxOfSmallest], + aSmallestTableName, aSmallestStartCell, aSmallestEndCell ); + if (aSmallestEndCell.isEmpty()) + aSmallestEndCell = aSmallestStartCell; + + for (sal_Int32 k = i+1; k < nLen; ++k) + { + // get cell names for sub range + OUString aTableName; + OUString aStartCell; + OUString aEndCell; + GetTableAndCellsFromRangeRep( pSubRanges[k], + aTableName, aStartCell, aEndCell ); + if (aEndCell.isEmpty()) + aEndCell = aStartCell; + + // compare cell ranges ( is the new one smaller? ) + if (-1 == sw_CompareCellRanges( aStartCell, aEndCell, + aSmallestStartCell, aSmallestEndCell, bCmpByColumn )) + { + nIdxOfSmallest = k; + aSmallestTableName = aTableName; + aSmallestStartCell = aStartCell; + aSmallestEndCell = aEndCell; + } + } + + // move smallest element to the start of the not sorted area + const OUString aTmp( pSubRanges[ nIdxOfSmallest ] ); + pSubRanges[ nIdxOfSmallest ] = pSubRanges[ i ]; + pSubRanges[ i ] = aTmp; + } +} + +SwChartDataProvider::SwChartDataProvider( const SwDoc& rSwDoc ) : + m_pDoc( &rSwDoc ) +{ + m_bDisposed = false; +} + +SwChartDataProvider::~SwChartDataProvider() +{ +} + +uno::Reference< chart2::data::XDataSource > SwChartDataProvider::Impl_createDataSource( + const uno::Sequence< beans::PropertyValue >& rArguments, bool bTestOnly ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Reference< chart2::data::XDataSource > xRes; + + if (!m_pDoc) + throw uno::RuntimeException("Not connected to a document."); + + // get arguments + OUString aRangeRepresentation; + uno::Sequence< sal_Int32 > aSequenceMapping; + bool bFirstIsLabel = false; + bool bDtaSrcIsColumns = true; // true : DataSource will be sequence of columns + // false: DataSource will be sequence of rows + + OUString aChartOleObjectName; //work around wrong writer ranges ( see Issue 58464 ) + sal_Int32 nArgs = rArguments.getLength(); + OSL_ENSURE( nArgs != 0, "no properties provided" ); + if (nArgs == 0) + return xRes; + for (const beans::PropertyValue& rArg : rArguments) + { + if ( rArg.Name == "DataRowSource" ) + { + chart::ChartDataRowSource eSource; + if (!(rArg.Value >>= eSource)) + { + sal_Int32 nTmp = 0; + if (!(rArg.Value >>= nTmp)) + throw lang::IllegalArgumentException(); + eSource = static_cast< chart::ChartDataRowSource >( nTmp ); + } + bDtaSrcIsColumns = eSource == chart::ChartDataRowSource_COLUMNS; + } + else if ( rArg.Name == "FirstCellAsLabel" ) + { + if (!(rArg.Value >>= bFirstIsLabel)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "CellRangeRepresentation" ) + { + if (!(rArg.Value >>= aRangeRepresentation)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "SequenceMapping" ) + { + if (!(rArg.Value >>= aSequenceMapping)) + throw lang::IllegalArgumentException(); + } + else if ( rArg.Name == "ChartOleObjectName" ) + { + if (!(rArg.Value >>= aChartOleObjectName)) + throw lang::IllegalArgumentException(); + } + } + + uno::Sequence< OUString > aSubRanges; + // get sub-ranges and check that they all are from the very same table + bool bOk = GetSubranges( aRangeRepresentation, aSubRanges, true ); + + if (!bOk && m_pDoc && !aChartOleObjectName.isEmpty() ) + { + //try to correct the range here + //work around wrong writer ranges ( see Issue 58464 ) + OUString aChartTableName; + + const SwNodes& rNodes = m_pDoc->GetNodes(); + for( SwNodeOffset nN = rNodes.Count(); nN--; ) + { + SwNode* pNode = rNodes[nN]; + if( !pNode ) + continue; + const SwOLENode* pOleNode = pNode->GetOLENode(); + if( !pOleNode ) + continue; + const SwOLEObj& rOObj = pOleNode->GetOLEObj(); + if( aChartOleObjectName == rOObj.GetCurrentPersistName() ) + { + aChartTableName = pOleNode->GetChartTableName(); + break; + } + } + + if( !aChartTableName.isEmpty() ) + { + //the wrong range is still shifted one row down + //thus the first row is missing and an invalid row at the end is added. + //Therefore we need to shift the range one row up + SwRangeDescriptor aDesc; + if (aRangeRepresentation.isEmpty()) + return xRes; // we can't handle this thus returning an empty references + + aRangeRepresentation = aRangeRepresentation.copy( 1 ); // get rid of '.' to have only the cell range left + FillRangeDescriptor( aDesc, aRangeRepresentation ); + aDesc.Normalize(); + + if (aDesc.nTop <= 0) // no chance to shift the range one row up? + return xRes; // we can't handle this thus returning an empty references + + aDesc.nTop -= 1; + aDesc.nBottom -= 1; + + OUString aNewStartCell( sw_GetCellName( aDesc.nLeft, aDesc.nTop ) ); + OUString aNewEndCell( sw_GetCellName( aDesc.nRight, aDesc.nBottom ) ); + aRangeRepresentation = GetRangeRepFromTableAndCells( + aChartTableName, aNewStartCell, aNewEndCell, true ); + bOk = GetSubranges( aRangeRepresentation, aSubRanges, true ); + } + } + if (!bOk) // different tables used, or incorrect range specifiers + throw lang::IllegalArgumentException(); + + SortSubranges( aSubRanges, bDtaSrcIsColumns ); + + // get table format for that single table from above + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pUnoCursor; // here required to check if the cells in the range do actually exist + if (aSubRanges.hasElements()) + GetFormatAndCreateCursorFromRangeRep( m_pDoc, aSubRanges[0], &pTableFormat, pUnoCursor ); + + if (!pTableFormat || !pUnoCursor) + throw lang::IllegalArgumentException(); + + SwTable* pTable = SwTable::FindTable(pTableFormat); + if (pTable->IsTableComplex()) + return xRes; // we can't handle this thus returning an empty references + + // get a character map in the size of the table to mark + // all the ranges to use in + sal_Int32 nRows = pTable->GetTabLines().size(); + sal_Int32 nCols = 0; + // As per tdf#149718 one should know that some cells can be merged together. + // Therefore, the number of columns (boxes in each row) are not necessarily + // equal. Here, we calculate the maximum number of columns in all rows. + for (sal_Int32 i = 0; i < nRows; ++i) + nCols = std::max(nCols, static_cast<sal_Int32>(pTable->GetTabLines()[i]->GetTabBoxes().size())); + + std::vector<std::vector<char>> aMap(nRows); + for (sal_Int32 i = 0; i < nRows; ++i) + aMap[i].resize(nCols); + + // iterate over subranges and mark used cells in above map + //!! by proceeding this way we automatically get rid of + //!! multiple listed or overlapping cell ranges which should + //!! just be ignored silently + for (const OUString& rSubRange : std::as_const(aSubRanges)) + { + OUString aTableName, aStartCell, aEndCell; + bool bOk2 = GetTableAndCellsFromRangeRep( + rSubRange, aTableName, aStartCell, aEndCell ); + OSL_ENSURE(bOk2, "failed to get table and start/end cells"); + + sal_Int32 nStartRow, nStartCol, nEndRow, nEndCol; + SwXTextTable::GetCellPosition(aStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition(aEndCell, nEndCol, nEndRow); + OSL_ENSURE( nStartRow <= nEndRow && nStartCol <= nEndCol, + "cell range not normalized"); + + // test if the ranges span more than the available cells + if( nStartRow < 0 || nEndRow >= nRows || + nStartCol < 0 || nEndCol >= nCols ) + { + throw lang::IllegalArgumentException(); + } + for (sal_Int32 k1 = nStartRow; k1 <= nEndRow; ++k1) + { + for (sal_Int32 k2 = nStartCol; k2 <= nEndCol; ++k2) + aMap[k1][k2] = 'x'; + } + } + + // find label and data sequences to use + + sal_Int32 oi; // outer index (slower changing index) + sal_Int32 ii; // inner index (faster changing index) + sal_Int32 oiEnd = bDtaSrcIsColumns ? nCols : nRows; + sal_Int32 iiEnd = bDtaSrcIsColumns ? nRows : nCols; + std::vector<sal_Int32> aLabelIdx(oiEnd); + std::vector<sal_Int32> aDataStartIdx(oiEnd); + std::vector<sal_Int32> aDataLen(oiEnd); + for (oi = 0; oi < oiEnd; ++oi) + { + aLabelIdx[oi] = -1; + aDataStartIdx[oi] = -1; + aDataLen[oi] = 0; + } + + for (oi = 0; oi < oiEnd; ++oi) + { + ii = 0; + while (ii < iiEnd) + { + char &rChar = bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii]; + + // label should be used but is not yet found? + if (rChar == 'x' && bFirstIsLabel && aLabelIdx[oi] == -1) + { + aLabelIdx[oi] = ii; + rChar = 'L'; // setting a different char for labels here + // makes the test for the data sequence below + // easier + } + + // find data sequence + if (rChar == 'x' && aDataStartIdx[oi] == -1) + { + aDataStartIdx[oi] = ii; + + // get length of data sequence + sal_Int32 nL = 0; + while (ii< iiEnd && 'x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii])) + { + ++nL; ++ii; + } + aDataLen[oi] = nL; + + // check that there is no other separate sequence of data + // to be found because that is not supported + while (ii < iiEnd) + { + if ('x' == (bDtaSrcIsColumns ? aMap[ii][oi] : aMap[oi][ii])) + throw lang::IllegalArgumentException(); + ++ii; + } + } + else + ++ii; + } + } + + // make some other consistency checks while calculating + // the number of XLabeledDataSequence to build: + // - labels should always be used or not at all + // - the data sequences should have equal non-zero length + sal_Int32 nNumLDS = 0; + if (oiEnd > 0) + { + for (oi = 0; oi < oiEnd; ++oi) + { + // row/col used at all? + if (aDataStartIdx[oi] != -1 && + (!bFirstIsLabel || aLabelIdx[oi] != -1)) + { + ++nNumLDS; + } + } + } + if (nNumLDS == 0) + throw lang::IllegalArgumentException(); + + // now we should have all necessary data to build a proper DataSource + // thus if we came this far there should be no further problem + if (bTestOnly) + return xRes; // have createDataSourcePossible return true + + // create data source from found label and data sequences + uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aLabelSeqs(nNumLDS); + uno::Reference<chart2::data::XDataSequence>* pLabelSeqs = aLabelSeqs.getArray(); + uno::Sequence<uno::Reference<chart2::data::XDataSequence>> aDataSeqs(nNumLDS); + uno::Reference<chart2::data::XDataSequence>* pDataSeqs = aDataSeqs.getArray(); + sal_Int32 nSeqsIdx = 0; + for (oi = 0; oi < oiEnd; ++oi) + { + // row/col not used? (see if-statement above where nNumLDS was counted) + if (!(aDataStartIdx[oi] != -1 && + (!bFirstIsLabel || aLabelIdx[oi] != -1))) + continue; + + // get cell ranges for label and data + + SwRangeDescriptor aLabelDesc; + SwRangeDescriptor aDataDesc; + if (bDtaSrcIsColumns) // use columns + { + aLabelDesc.nTop = aLabelIdx[oi]; + aLabelDesc.nLeft = oi; + aLabelDesc.nBottom = aLabelDesc.nTop; + aLabelDesc.nRight = oi; + + aDataDesc.nTop = aDataStartIdx[oi]; + aDataDesc.nLeft = oi; + aDataDesc.nBottom = aDataDesc.nTop + aDataLen[oi] - 1; + aDataDesc.nRight = oi; + } + else // use rows + { + aLabelDesc.nTop = oi; + aLabelDesc.nLeft = aLabelIdx[oi]; + aLabelDesc.nBottom = oi; + aLabelDesc.nRight = aLabelDesc.nLeft; + + aDataDesc.nTop = oi; + aDataDesc.nLeft = aDataStartIdx[oi]; + aDataDesc.nBottom = oi; + aDataDesc.nRight = aDataDesc.nLeft + aDataLen[oi] - 1; + } + const OUString aBaseName = pTableFormat->GetName() + "."; + + OUString aLabelRange; + if (aLabelIdx[oi] != -1) + { + aLabelRange = aBaseName + + sw_GetCellName( aLabelDesc.nLeft, aLabelDesc.nTop ) + + ":" + sw_GetCellName( aLabelDesc.nRight, aLabelDesc.nBottom ); + } + + OUString aDataRange = aBaseName + + sw_GetCellName( aDataDesc.nLeft, aDataDesc.nTop ) + + ":" + sw_GetCellName( aDataDesc.nRight, aDataDesc.nBottom ); + + // get cursors spanning the cell ranges for label and data + std::shared_ptr<SwUnoCursor> pLabelUnoCursor; + std::shared_ptr<SwUnoCursor> pDataUnoCursor; + GetFormatAndCreateCursorFromRangeRep(m_pDoc, aLabelRange, &pTableFormat, pLabelUnoCursor); + GetFormatAndCreateCursorFromRangeRep(m_pDoc, aDataRange, &pTableFormat, pDataUnoCursor); + + // create XDataSequence's from cursors + if (pLabelUnoCursor) + pLabelSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pLabelUnoCursor); + OSL_ENSURE(pDataUnoCursor, "pointer to data sequence missing"); + if (pDataUnoCursor) + pDataSeqs[nSeqsIdx] = new SwChartDataSequence(*this, *pTableFormat, pDataUnoCursor); + if (pLabelUnoCursor || pDataUnoCursor) + ++nSeqsIdx; + } + OSL_ENSURE(nSeqsIdx == nNumLDS, "mismatch between sequence size and num,ber of entries"); + + // build data source from data and label sequences + uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aLDS(nNumLDS); + uno::Reference<chart2::data::XLabeledDataSequence>* pLDS = aLDS.getArray(); + for (sal_Int32 i = 0; i < nNumLDS; ++i) + { + rtl::Reference<SwChartLabeledDataSequence> pLabeledDtaSeq = new SwChartLabeledDataSequence; + pLabeledDtaSeq->setLabel(pLabelSeqs[i]); + pLabeledDtaSeq->setValues(pDataSeqs[i]); + pLDS[i] = pLabeledDtaSeq; + } + + // apply 'SequenceMapping' if it was provided + if (aSequenceMapping.hasElements()) + { + uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence>> aOld_LDS(aLDS); + uno::Reference<chart2::data::XLabeledDataSequence>* pOld_LDS = aOld_LDS.getArray(); + + sal_Int32 nNewCnt = 0; + for (sal_Int32 nIdx : std::as_const(aSequenceMapping)) + { + // check that index to be used is valid + // and has not yet been used + if (0 <= nIdx && nIdx < nNumLDS && pOld_LDS[nIdx].is()) + { + pLDS[nNewCnt++] = pOld_LDS[nIdx]; + + // mark index as being used already (avoids duplicate entries) + pOld_LDS[nIdx].clear(); + } + } + // add not yet used 'old' sequences to new one + for (sal_Int32 i = 0; i < nNumLDS; ++i) + { + if (pOld_LDS[i].is()) + pLDS[nNewCnt++] = pOld_LDS[i]; + } + OSL_ENSURE(nNewCnt == nNumLDS, "unexpected size of resulting sequence"); + } + + xRes = new SwChartDataSource(aLDS); + return xRes; +} + +sal_Bool SAL_CALL SwChartDataProvider::createDataSourcePossible( + const uno::Sequence< beans::PropertyValue >& rArguments ) +{ + SolarMutexGuard aGuard; + + bool bPossible = true; + try + { + Impl_createDataSource( rArguments, true ); + } + catch (lang::IllegalArgumentException &) + { + bPossible = false; + } + + return bPossible; +} + +uno::Reference< chart2::data::XDataSource > SAL_CALL SwChartDataProvider::createDataSource( + const uno::Sequence< beans::PropertyValue >& rArguments ) +{ + SolarMutexGuard aGuard; + return Impl_createDataSource( rArguments ); +} + +/** + * Fix for #i79009 + * we need to return a property that has the same value as the property + * 'CellRangeRepresentation' but for all rows which are increased by one. + * E.g. Table1.A1:D5 -> Table1,A2:D6 + * Since the problem is only for old charts which did not support multiple + * we do not need to provide that property/string if the 'CellRangeRepresentation' + * contains multiple ranges. + */ +OUString SwChartDataProvider::GetBrokenCellRangeForExport( + std::u16string_view rCellRangeRepresentation ) +{ + // check that we do not have multiple ranges + if (std::u16string_view::npos == rCellRangeRepresentation.find( ';' )) + { + // get current cell and table names + OUString aTableName, aStartCell, aEndCell; + GetTableAndCellsFromRangeRep( rCellRangeRepresentation, + aTableName, aStartCell, aEndCell, false ); + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aEndCell, nEndCol, nEndRow ); + + // get new cell names + ++nStartRow; + ++nEndRow; + aStartCell = sw_GetCellName( nStartCol, nStartRow ); + aEndCell = sw_GetCellName( nEndCol, nEndRow ); + + return GetRangeRepFromTableAndCells( aTableName, + aStartCell, aEndCell, false ); + } + + return OUString(); +} + +uno::Sequence< beans::PropertyValue > SAL_CALL SwChartDataProvider::detectArguments( + const uno::Reference< chart2::data::XDataSource >& xDataSource ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Sequence< beans::PropertyValue > aResult; + if (!xDataSource.is()) + return aResult; + + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aDS_LDS( xDataSource->getDataSequences() ); + const uno::Reference< chart2::data::XLabeledDataSequence > *pDS_LDS = aDS_LDS.getConstArray(); + sal_Int32 nNumDS_LDS = aDS_LDS.getLength(); + + if (nNumDS_LDS == 0) + { + OSL_FAIL( "XLabeledDataSequence in data source contains 0 entries" ); + return aResult; + } + + SwFrameFormat *pTableFormat = nullptr; + SwTable *pTable = nullptr; + OUString aTableName; + sal_Int32 nTableRows = 0; + sal_Int32 nTableCols = 0; + + // data used to build 'CellRangeRepresentation' from later on + std::vector< std::vector< char > > aMap; + + uno::Sequence< sal_Int32 > aSequenceMapping( nNumDS_LDS ); + sal_Int32 *pSequenceMapping = aSequenceMapping.getArray(); + + OUString aCellRanges; + sal_Int16 nDtaSrcIsColumns = -1;// -1: don't know yet, 0: false, 1: true -2: neither + sal_Int32 nLabelSeqLen = -1; // used to see if labels are always used or not and have + // the expected size of 1 (i.e. if FirstCellAsLabel can + // be determined) + // -1: don't know yet, 0: not used, 1: always a single labe cell, ... + // -2: neither/failed + for (sal_Int32 nDS1 = 0; nDS1 < nNumDS_LDS; ++nDS1) + { + uno::Reference< chart2::data::XLabeledDataSequence > xLabeledDataSequence( pDS_LDS[nDS1] ); + if( !xLabeledDataSequence.is() ) + { + OSL_FAIL("got NULL for XLabeledDataSequence from Data source"); + continue; + } + const uno::Reference< chart2::data::XDataSequence > xCurLabel = xLabeledDataSequence->getLabel(); + const uno::Reference< chart2::data::XDataSequence > xCurValues = xLabeledDataSequence->getValues(); + + // get sequence lengths for label and values. + // (0 length is Ok) + sal_Int32 nCurLabelSeqLen = -1; + sal_Int32 nCurValuesSeqLen = -1; + if (xCurLabel.is()) + nCurLabelSeqLen = xCurLabel->getData().getLength(); + if (xCurValues.is()) + nCurValuesSeqLen = xCurValues->getData().getLength(); + + // check for consistent use of 'first cell as label' + if (nLabelSeqLen == -1) // set initial value to compare with below further on + nLabelSeqLen = nCurLabelSeqLen; + if (nLabelSeqLen != nCurLabelSeqLen) + nLabelSeqLen = -2; // failed / no consistent use of label cells + + // get table and cell names for label and values data sequences + // (start and end cell will be sorted, i.e. start cell <= end cell) + OUString aLabelTableName, aLabelStartCell, aLabelEndCell; + OUString aValuesTableName, aValuesStartCell, aValuesEndCell; + OUString aLabelRange, aValuesRange; + if (xCurLabel.is()) + aLabelRange = xCurLabel->getSourceRangeRepresentation(); + if (xCurValues.is()) + aValuesRange = xCurValues->getSourceRangeRepresentation(); + if ((!aLabelRange.isEmpty() && !GetTableAndCellsFromRangeRep( aLabelRange, + aLabelTableName, aLabelStartCell, aLabelEndCell )) || + !GetTableAndCellsFromRangeRep( aValuesRange, + aValuesTableName, aValuesStartCell, aValuesEndCell )) + { + return aResult; // failed -> return empty property sequence + } + + // make sure all sequences use the same table + if (aTableName.isEmpty()) + aTableName = aValuesTableName; // get initial value to compare with + if (aTableName.isEmpty() || + aTableName != aValuesTableName || + (!aLabelTableName.isEmpty() && aTableName != aLabelTableName)) + { + return aResult; // failed -> return empty property sequence + } + + // try to get 'DataRowSource' value (ROWS or COLUMNS) from inspecting + // first and last cell used in both sequences + + sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1; + const OUString aCell( !aLabelStartCell.isEmpty() ? aLabelStartCell : aValuesStartCell ); + OSL_ENSURE( !aCell.isEmpty() , "start cell missing?" ); + SwXTextTable::GetCellPosition( aCell, nFirstCol, nFirstRow); + SwXTextTable::GetCellPosition( aValuesEndCell, nLastCol, nLastRow); + + sal_Int16 nDirection = -1; // -1: not yet set, 0: columns, 1: rows, -2: failed + if (nFirstCol == nLastCol && nFirstRow == nLastRow) // a single cell... + { + OSL_ENSURE( nCurLabelSeqLen == 0 && nCurValuesSeqLen == 1, + "trying to determine 'DataRowSource': something's fishy... should have been a single cell"); + nDirection = 0; // default direction for a single cell should be 'columns' + } + else // more than one cell is available (in values and label together!) + { + if (nFirstCol == nLastCol && nFirstRow != nLastRow) + nDirection = 1; + else if (nFirstCol != nLastCol && nFirstRow == nLastRow) + nDirection = 0; + else + { + OSL_FAIL( "trying to determine 'DataRowSource': unexpected case found" ); + nDirection = -2; + } + } + // check for consistent direction of data source + if (nDtaSrcIsColumns == -1) // set initial value to compare with below + nDtaSrcIsColumns = nDirection; + if (nDtaSrcIsColumns != nDirection) + { + nDtaSrcIsColumns = -2; // failed + } + + if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1) + { + // build data to obtain 'SequenceMapping' later on + + OSL_ENSURE( nDtaSrcIsColumns == 0 || /* rows */ + nDtaSrcIsColumns == 1, /* columns */ + "unexpected value for 'nDtaSrcIsColumns'" ); + pSequenceMapping[nDS1] = nDtaSrcIsColumns ? nFirstCol : nFirstRow; + + // build data used to determine 'CellRangeRepresentation' later on + + GetTableByName( *m_pDoc, aTableName, &pTableFormat, &pTable ); + if (!pTable || pTable->IsTableComplex()) + return aResult; // failed -> return empty property sequence + nTableRows = pTable->GetTabLines().size(); + nTableCols = pTable->GetTabLines().front()->GetTabBoxes().size(); + aMap.resize( nTableRows ); + for (sal_Int32 i = 0; i < nTableRows; ++i) + aMap[i].resize( nTableCols ); + + if (!aLabelStartCell.isEmpty() && !aLabelEndCell.isEmpty()) + { + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aLabelEndCell, nEndCol, nEndRow ); + if (nStartRow < 0 || nEndRow >= nTableRows || + nStartCol < 0 || nEndCol >= nTableCols) + { + return aResult; // failed -> return empty property sequence + } + for (sal_Int32 i = nStartRow; i <= nEndRow; ++i) + { + for (sal_Int32 k = nStartCol; k <= nEndCol; ++k) + { + char &rChar = aMap[i][k]; + if (rChar == '\0') // check for overlapping values and/or labels + rChar = 'L'; + else + return aResult; // failed -> return empty property sequence + } + } + } + if (!aValuesStartCell.isEmpty() && !aValuesEndCell.isEmpty()) + { + sal_Int32 nStartCol = -1, nStartRow = -1, nEndCol = -1, nEndRow = -1; + SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow ); + SwXTextTable::GetCellPosition( aValuesEndCell, nEndCol, nEndRow ); + if (nStartRow < 0 || nEndRow >= nTableRows || + nStartCol < 0 || nEndCol >= nTableCols) + { + return aResult; // failed -> return empty property sequence + } + for (sal_Int32 i = nStartRow; i <= nEndRow; ++i) + { + for (sal_Int32 k = nStartCol; k <= nEndCol; ++k) + { + char &rChar = aMap[i][k]; + if (rChar == '\0') // check for overlapping values and/or labels + rChar = 'x'; + else + return aResult; // failed -> return empty property sequence + } + } + } + } + +#if OSL_DEBUG_LEVEL > 0 + // do some extra sanity checking that the length of the sequences + // matches their range representation + { + sal_Int32 nStartRow = -1, nStartCol = -1, nEndRow = -1, nEndCol = -1; + if (xCurLabel.is()) + { + SwXTextTable::GetCellPosition( aLabelStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition( aLabelEndCell, nEndCol, nEndRow); + OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurLabel->getData().getLength()) || + (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurLabel->getData().getLength()), + "label sequence length does not match range representation!" ); + } + if (xCurValues.is()) + { + SwXTextTable::GetCellPosition( aValuesStartCell, nStartCol, nStartRow); + SwXTextTable::GetCellPosition( aValuesEndCell, nEndCol, nEndRow); + OSL_ENSURE( (nStartCol == nEndCol && (nEndRow - nStartRow + 1) == xCurValues->getData().getLength()) || + (nStartRow == nEndRow && (nEndCol - nStartCol + 1) == xCurValues->getData().getLength()), + "value sequence length does not match range representation!" ); + } + } +#endif + } // for + + // build value for 'CellRangeRepresentation' + + const OUString aCellRangeBase = aTableName + "."; + OUString aCurRange; + for (sal_Int32 i = 0; i < nTableRows; ++i) + { + for (sal_Int32 k = 0; k < nTableCols; ++k) + { + if (aMap[i][k] != '\0') // top-left cell of a sub-range found + { + // find rectangular sub-range to use + sal_Int32 nRowIndex1 = i; // row index + sal_Int32 nColIndex1 = k; // column index + sal_Int32 nRowSubLen = 0; + sal_Int32 nColSubLen = 0; + while (nRowIndex1 < nTableRows && aMap[nRowIndex1++][k] != '\0') + ++nRowSubLen; + // be aware of shifted sequences! + // (according to the checks done prior the length should be ok) + while (nColIndex1 < nTableCols && aMap[i][nColIndex1] != '\0' + && aMap[i + nRowSubLen-1][nColIndex1] != '\0') + { + ++nColIndex1; + ++nColSubLen; + } + OUString aStartCell( sw_GetCellName( k, i ) ); + OUString aEndCell( sw_GetCellName( k + nColSubLen - 1, i + nRowSubLen - 1) ); + aCurRange = aCellRangeBase + aStartCell + ":" + aEndCell; + if (!aCellRanges.isEmpty()) + aCellRanges += ";"; + aCellRanges += aCurRange; + + // clear already found sub-range from map + for (sal_Int32 nRowIndex2 = 0; nRowIndex2 < nRowSubLen; ++nRowIndex2) + for (sal_Int32 nColumnIndex2 = 0; nColumnIndex2 < nColSubLen; ++nColumnIndex2) + aMap[i + nRowIndex2][k + nColumnIndex2] = '\0'; + } + } + } + // to be nice to the user we now sort the cell ranges according to + // rows or columns depending on the direction used in the data source + uno::Sequence< OUString > aSortedRanges; + GetSubranges( aCellRanges, aSortedRanges, false /*sub ranges should already be normalized*/ ); + SortSubranges( aSortedRanges, (nDtaSrcIsColumns == 1) ); + OUString aSortedCellRanges; + for (const OUString& rSortedRange : std::as_const(aSortedRanges)) + { + if (!aSortedCellRanges.isEmpty()) + aSortedCellRanges += ";"; + aSortedCellRanges += rSortedRange; + } + + // build value for 'SequenceMapping' + + uno::Sequence< sal_Int32 > aSortedMapping( aSequenceMapping ); + auto [begin, end] = asNonConstRange(aSortedMapping); + std::sort(begin, end); + bool bNeedSequenceMapping = false; + for (sal_Int32 i = 0; i < aSequenceMapping.getLength(); ++i) + { + auto it = std::find( std::cbegin(aSortedMapping), std::cend(aSortedMapping), + aSequenceMapping[i] ); + pSequenceMapping[i] = std::distance(std::cbegin(aSortedMapping), it); + + if (i != std::as_const(aSequenceMapping)[i]) + bNeedSequenceMapping = true; + } + + // check if 'SequenceMapping' is actually not required... + // (don't write unnecessary properties to the XML file) + if (!bNeedSequenceMapping) + aSequenceMapping.realloc(0); + + // build resulting properties + + OSL_ENSURE(nLabelSeqLen >= 0 || nLabelSeqLen == -2 /*not used*/, + "unexpected value for 'nLabelSeqLen'" ); + bool bFirstCellIsLabel = false; // default value if 'nLabelSeqLen' could not properly determined + if (nLabelSeqLen > 0) // == 0 means no label sequence in use + bFirstCellIsLabel = true; + + OSL_ENSURE( !aSortedCellRanges.isEmpty(), "CellRangeRepresentation missing" ); + const OUString aBrokenCellRangeForExport( GetBrokenCellRangeForExport( aSortedCellRanges ) ); + + aResult.realloc(5); + auto pResult = aResult.getArray(); + sal_Int32 nProps = 0; + pResult[nProps ].Name = "FirstCellAsLabel"; + pResult[nProps++].Value <<= bFirstCellIsLabel; + pResult[nProps ].Name = "CellRangeRepresentation"; + pResult[nProps++].Value <<= aSortedCellRanges; + if (!aBrokenCellRangeForExport.isEmpty()) + { + pResult[nProps ].Name = "BrokenCellRangeForExport"; + pResult[nProps++].Value <<= aBrokenCellRangeForExport; + } + if (nDtaSrcIsColumns == 0 || nDtaSrcIsColumns == 1) + { + chart::ChartDataRowSource eDataRowSource = (nDtaSrcIsColumns == 1) ? + chart::ChartDataRowSource_COLUMNS : chart::ChartDataRowSource_ROWS; + pResult[nProps ].Name = "DataRowSource"; + pResult[nProps++].Value <<= eDataRowSource; + + if (aSequenceMapping.hasElements()) + { + pResult[nProps ].Name = "SequenceMapping"; + pResult[nProps++].Value <<= aSequenceMapping; + } + } + aResult.realloc( nProps ); + + return aResult; +} + +uno::Reference< chart2::data::XDataSequence > SwChartDataProvider::Impl_createDataSequenceByRangeRepresentation( + std::u16string_view rRangeRepresentation, bool bTestOnly ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pUnoCursor; // pointer to new created cursor spanning the cell range + GetFormatAndCreateCursorFromRangeRep( m_pDoc, rRangeRepresentation, + &pTableFormat, pUnoCursor ); + if (!pTableFormat || !pUnoCursor) + throw lang::IllegalArgumentException(); + + // check that cursors point and mark are in a single row or column. + OUString aCellRange( GetCellRangeName( *pTableFormat, *pUnoCursor ) ); + SwRangeDescriptor aDesc; + FillRangeDescriptor( aDesc, aCellRange ); + if (aDesc.nTop != aDesc.nBottom && aDesc.nLeft != aDesc.nRight) + throw lang::IllegalArgumentException(); + + OSL_ENSURE( pTableFormat && pUnoCursor, "table format or cursor missing" ); + uno::Reference< chart2::data::XDataSequence > xDataSeq; + if (!bTestOnly) + xDataSeq = new SwChartDataSequence( *this, *pTableFormat, pUnoCursor ); + + return xDataSeq; +} + +sal_Bool SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentationPossible( + const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + + bool bPossible = true; + try + { + Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation, true ); + } + catch (lang::IllegalArgumentException &) + { + bPossible = false; + } + + return bPossible; +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartDataProvider::createDataSequenceByRangeRepresentation( + const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + return Impl_createDataSequenceByRangeRepresentation( rRangeRepresentation ); +} + +uno::Reference< sheet::XRangeSelection > SAL_CALL SwChartDataProvider::getRangeSelection( ) +{ + // note: it is no error to return nothing here + return uno::Reference< sheet::XRangeSelection >(); +} + +uno::Reference<css::chart2::data::XDataSequence> SAL_CALL + SwChartDataProvider::createDataSequenceByValueArray( + const OUString& /*aRole*/, const OUString& /*aRangeRepresentation*/, + const OUString& /*aRoleQualifier*/ ) +{ + return uno::Reference<css::chart2::data::XDataSequence>(); +} + +void SAL_CALL SwChartDataProvider::dispose( ) +{ + bool bMustDispose( false ); + { + std::unique_lock aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (!bMustDispose) + return; + + // dispose all data-sequences + for (const auto& rEntry : m_aDataSequences) + { + DisposeAllDataSequences( rEntry.first ); + } + // release all references to data-sequences + m_aDataSequences.clear(); + + // require listeners to release references to this object + lang::EventObject aEvtObj( static_cast< chart2::data::XDataProvider * >(this) ); + std::unique_lock aGuard( GetChartMutex() ); + m_aEventListeners.disposeAndClear( aGuard, aEvtObj ); +} + +void SAL_CALL SwChartDataProvider::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.addInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartDataProvider::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.removeInterface( aGuard, rxListener ); +} + +OUString SAL_CALL SwChartDataProvider::getImplementationName( ) +{ + return "SwChartDataProvider"; +} + +sal_Bool SAL_CALL SwChartDataProvider::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataProvider::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataProvider"}; +} + +void SwChartDataProvider::AddDataSequence( const SwTable &rTable, rtl::Reference< SwChartDataSequence > const &rxDataSequence ) +{ + Vec_DataSequenceRef_t& rVec = m_aDataSequences[ &rTable ]; + assert(std::find_if(rVec.begin(), rVec.end(), + [&rxDataSequence](const unotools::WeakReference < SwChartDataSequence >& i) + { + return i.get() == rxDataSequence; + }) == rVec.end() && "duplicate insert"); + rVec.push_back( rxDataSequence ); +} + +void SwChartDataProvider::RemoveDataSequence( const SwTable &rTable, rtl::Reference< SwChartDataSequence > const &rxDataSequence ) +{ + Vec_DataSequenceRef_t& rVec = m_aDataSequences[ &rTable ]; + std::erase_if(rVec, + [&rxDataSequence](const unotools::WeakReference < SwChartDataSequence >& i) + { + return i.get() == rxDataSequence; + }); +} + +void SwChartDataProvider::InvalidateTable( const SwTable *pTable, bool bImmediate ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (!pTable) + return; + + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + const Vec_DataSequenceRef_t &rVec = m_aDataSequences[ pTable ]; + for (const unotools::WeakReference<SwChartDataSequence>& rItem : rVec) + { + rtl::Reference< SwChartDataSequence > xRef(rItem); + if (xRef.is()) + { + // mark the sequence as 'dirty' and notify listeners + xRef->setModified( true ); + } + } + + // tdf#122995 added Immediate-mode to allow non-timer-delayed Chart invalidation + if (bImmediate && !m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().Disconnect(); +} + +void SwChartDataProvider::DeleteBox( const SwTable *pTable, const SwTableBox &rBox ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (!pTable) + return; + + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + Vec_DataSequenceRef_t &rVec = m_aDataSequences[ pTable ]; + + // iterate over all data-sequences for that table... + auto aIt( rVec.begin() ); + while (aIt != rVec.end()) + { + bool bNowEmpty = false; + bool bSeqDisposed = false; + + // check if weak reference is still valid... + rtl::Reference< SwChartDataSequence > pDataSeq(*aIt); + if (pDataSeq.is()) + { + // then delete that table box (check if implementation cursor needs to be adjusted) + try + { + bNowEmpty = pDataSeq->DeleteBox( rBox ); + } + catch (const lang::DisposedException&) + { + bNowEmpty = true; + bSeqDisposed = true; + } + } + + if (bNowEmpty) + { + aIt = rVec.erase( aIt ); + if (pDataSeq && !bSeqDisposed) + pDataSeq->dispose(); // the current way to tell chart that sth. got removed + } + else + ++aIt; + } +} + +void SwChartDataProvider::DisposeAllDataSequences( const SwTable *pTable ) +{ + OSL_ENSURE( pTable, "table pointer is NULL" ); + if (!pTable) + return; + + if (!m_bDisposed) + pTable->GetFrameFormat()->GetDoc()->getIDocumentChartDataProviderAccess().GetChartControllerHelper().StartOrContinueLocking(); + + //! make a copy of the STL container! + //! This is necessary since calling 'dispose' will implicitly remove an element + //! of the original container, and thus any iterator in the original container + //! would become invalid. + const Vec_DataSequenceRef_t aVec( m_aDataSequences[ pTable ] ); + + for (const unotools::WeakReference<SwChartDataSequence>& rItem : aVec) + { + rtl::Reference< SwChartDataSequence > xRef(rItem); + if (xRef.is()) + { + xRef->dispose(); + } + } +} + +/** + * SwChartDataProvider::AddRowCols tries to notify charts of added columns + * or rows and extends the value sequence respectively (if possible). + * If those can be added to the end of existing value data-sequences those + * sequences get modified accordingly and will send a modification + * notification (calling 'setModified + * + * Since this function is a work-around for non existent Writer core functionality + * (no arbitrary multi-selection in tables that can be used to define a + * data-sequence) this function will be somewhat unreliable. + * For example we will only try to adapt value sequences. For this we assume + * that a sequence of length 1 is a label sequence and those with length >= 2 + * we presume to be value sequences. Also new cells can only be added in the + * direction the value sequence is already pointing (rows / cols) and at the + * start or end of the values data-sequence. + * Nothing needs to be done if the new cells are in between the table cursors + * point and mark since data-sequence are considered to consist of all cells + * between those. + * New rows/cols need to be added already to the table before calling + * this function. + */ +void SwChartDataProvider::AddRowCols( + const SwTable &rTable, + const SwSelBoxes& rBoxes, + sal_uInt16 nLines, bool bBehind ) +{ + if (rTable.IsTableComplex()) + return; + + const size_t nBoxes = rBoxes.size(); + if (nBoxes < 1 || nLines < 1) + return; + + SwTableBox* pFirstBox = rBoxes[0]; + SwTableBox* pLastBox = rBoxes.back(); + + if (!(pFirstBox && pLastBox)) + return; + + sal_Int32 nFirstCol = -1, nFirstRow = -1, nLastCol = -1, nLastRow = -1; + SwXTextTable::GetCellPosition( pFirstBox->GetName(), nFirstCol, nFirstRow ); + SwXTextTable::GetCellPosition( pLastBox->GetName(), nLastCol, nLastRow ); + + bool bAddCols = false; // default; also to be used if nBoxes == 1 :-/ + if (nFirstCol == nLastCol && nFirstRow != nLastRow) + bAddCols = true; + if (nFirstCol != nLastCol && nFirstRow != nLastRow) + return; + + //get range of indices in col/rows for new cells + sal_Int32 nFirstNewCol = nFirstCol; + sal_Int32 nFirstNewRow = bBehind ? nFirstRow + 1 : nFirstRow - nLines; + if (bAddCols) + { + OSL_ENSURE( nFirstCol == nLastCol, "column indices seem broken" ); + nFirstNewCol = bBehind ? nFirstCol + 1 : nFirstCol - nLines; + nFirstNewRow = nFirstRow; + } + + // iterate over all data-sequences for the table + const Vec_DataSequenceRef_t &rVec = m_aDataSequences[ &rTable ]; + for (const unotools::WeakReference<SwChartDataSequence>& rItem : rVec) + { + rtl::Reference< SwChartDataSequence > pDataSeq(rItem); + if (pDataSeq.is()) + { + const sal_Int32 nLen = pDataSeq->getTextualData().getLength(); + if (nLen > 1) // value data-sequence ? + { + SwRangeDescriptor aDesc; + pDataSeq->FillRangeDesc( aDesc ); + + chart::ChartDataRowSource eDRSource = chart::ChartDataRowSource_COLUMNS; + if (aDesc.nTop == aDesc.nBottom && aDesc.nLeft != aDesc.nRight) + eDRSource = chart::ChartDataRowSource_ROWS; + + if (!bAddCols && eDRSource == chart::ChartDataRowSource_COLUMNS) + { + // add rows: extend affected columns by newly added row cells + pDataSeq->ExtendTo( true, nFirstNewRow, nLines ); + } + else if (bAddCols && eDRSource == chart::ChartDataRowSource_ROWS) + { + // add cols: extend affected rows by newly added column cells + pDataSeq->ExtendTo( false, nFirstNewCol, nLines ); + } + } + } + } +} + +// XRangeXMLConversion +OUString SAL_CALL SwChartDataProvider::convertRangeToXML( const OUString& rRangeRepresentation ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rRangeRepresentation.isEmpty()) + return OUString(); + + OUStringBuffer aRes; + + // multiple ranges are delimited by a ';' like in + // "Table1.A1:A4;Table1.C2:C5" the same table must be used in all ranges! + SwTable* pFirstFoundTable = nullptr; // to check that only one table will be used + sal_Int32 nPos = 0; + do { + const OUString aRange( rRangeRepresentation.getToken(0, ';', nPos) ); + SwFrameFormat *pTableFormat = nullptr; // pointer to table format + std::shared_ptr<SwUnoCursor> pCursor; + GetFormatAndCreateCursorFromRangeRep( m_pDoc, aRange, &pTableFormat, pCursor ); + if (!pTableFormat) + throw lang::IllegalArgumentException(); + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if (pTable->IsTableComplex()) + throw uno::RuntimeException("Table too complex."); + + // check that there is only one table used in all ranges + if (!pFirstFoundTable) + pFirstFoundTable = pTable; + if (pTable != pFirstFoundTable) + throw lang::IllegalArgumentException(); + + OUString aTableName; + OUString aStartCell; + OUString aEndCell; + if (!GetTableAndCellsFromRangeRep( aRange, aTableName, aStartCell, aEndCell )) + throw lang::IllegalArgumentException(); + + sal_Int32 nCol, nRow; + SwXTextTable::GetCellPosition( aStartCell, nCol, nRow ); + if (nCol < 0 || nRow < 0) + throw uno::RuntimeException("Cell not found."); + + //!! following objects/functions are implemented in XMLRangeHelper.?xx + //!! which is a copy of the respective file from chart2 !! + XMLRangeHelper::CellRange aCellRange; + aCellRange.aTableName = aTableName; + aCellRange.aUpperLeft.nColumn = nCol; + aCellRange.aUpperLeft.nRow = nRow; + aCellRange.aUpperLeft.bIsEmpty = false; + if (aStartCell != aEndCell && !aEndCell.isEmpty()) + { + SwXTextTable::GetCellPosition( aEndCell, nCol, nRow ); + if (nCol < 0 || nRow < 0) + throw uno::RuntimeException("Cell not found."); + + aCellRange.aLowerRight.nColumn = nCol; + aCellRange.aLowerRight.nRow = nRow; + aCellRange.aLowerRight.bIsEmpty = false; + } + OUString aTmp( XMLRangeHelper::getXMLStringFromCellRange( aCellRange ) ); + if (!aRes.isEmpty()) // in case of multiple ranges add delimiter + aRes.append(" "); + aRes.append(aTmp); + } + while (nPos>0); + + return aRes.makeStringAndClear(); +} + +OUString SAL_CALL SwChartDataProvider::convertRangeFromXML( const OUString& rXMLRange ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rXMLRange.isEmpty()) + return OUString(); + + OUStringBuffer aRes; + + // multiple ranges are delimited by a ' ' like in + // "Table1.$A$1:.$A$4 Table1.$C$2:.$C$5" the same table must be used in all ranges! + OUString aFirstFoundTable; // to check that only one table will be used + sal_Int32 nPos = 0; + do + { + OUString aRange( rXMLRange.getToken(0, ' ', nPos) ); + + //!! following objects and function are implemented in XMLRangeHelper.?xx + //!! which is a copy of the respective file from chart2 !! + XMLRangeHelper::CellRange aCellRange( XMLRangeHelper::getCellRangeFromXMLString( aRange )); + + // check that there is only one table used in all ranges + if (aFirstFoundTable.isEmpty()) + aFirstFoundTable = aCellRange.aTableName; + if (aCellRange.aTableName != aFirstFoundTable) + throw lang::IllegalArgumentException(); + + OUString aTmp = aCellRange.aTableName + "." + + sw_GetCellName( aCellRange.aUpperLeft.nColumn, + aCellRange.aUpperLeft.nRow ); + // does cell range consist of more than a single cell? + if (!aCellRange.aLowerRight.bIsEmpty) + { + aTmp += ":" + sw_GetCellName( aCellRange.aLowerRight.nColumn, + aCellRange.aLowerRight.nRow ); + } + + if (!aRes.isEmpty()) // in case of multiple ranges add delimiter + aRes.append(";"); + aRes.append(aTmp); + } + while (nPos>0); + + return aRes.makeStringAndClear(); +} + +SwChartDataSource::SwChartDataSource( + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > &rLDS ) : + m_aLDS( rLDS ) +{ +} + +SwChartDataSource::~SwChartDataSource() +{ +} + +uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > SAL_CALL SwChartDataSource::getDataSequences( ) +{ + SolarMutexGuard aGuard; + return m_aLDS; +} + +OUString SAL_CALL SwChartDataSource::getImplementationName( ) +{ + return "SwChartDataSource"; +} + +sal_Bool SAL_CALL SwChartDataSource::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSource::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataSource" }; +} + +SwChartDataSequence::SwChartDataSequence( + SwChartDataProvider& rProvider, + SwFrameFormat& rTableFormat, + const std::shared_ptr<SwUnoCursor>& pTableCursor ) : + m_pFormat(&rTableFormat), + m_aRowLabelText( SwResId( STR_CHART2_ROW_LABEL_TEXT ) ), + m_aColLabelText( SwResId( STR_CHART2_COL_LABEL_TEXT ) ), + m_xDataProvider( &rProvider ), + m_pTableCursor( pTableCursor ), + m_pPropSet( aSwMapProvider.GetPropertySet( PROPERTY_MAP_CHART2_DATA_SEQUENCE ) ) +{ + StartListening(rTableFormat.GetNotifier()); + m_bDisposed = false; + + acquire(); + try + { + const SwTable* pTable = SwTable::FindTable( &rTableFormat ); + if (pTable) + { + m_xDataProvider->AddDataSequence( *pTable, this ); + m_xDataProvider->addEventListener( static_cast< lang::XEventListener * >(this) ); + } + else { + OSL_FAIL( "table missing" ); + } + } + catch (uno::RuntimeException &) + { + // TODO: shouldn't there be a call to release() here? + throw; + } + catch (uno::Exception &) + { + } + release(); + +#if OSL_DEBUG_LEVEL > 0 + // check if it can properly convert into a SwUnoTableCursor + // which is required for some functions + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + OSL_ENSURE(pUnoTableCursor, "SwChartDataSequence: cursor not SwUnoTableCursor"); +#endif +} + +SwChartDataSequence::SwChartDataSequence( const SwChartDataSequence &rObj ) : + SwChartDataSequenceBaseClass(rObj), + SvtListener(), + m_pFormat( rObj.m_pFormat ), + m_aRole( rObj.m_aRole ), + m_aRowLabelText( SwResId(STR_CHART2_ROW_LABEL_TEXT) ), + m_aColLabelText( SwResId(STR_CHART2_COL_LABEL_TEXT) ), + m_xDataProvider( rObj.m_xDataProvider ), + m_pTableCursor( rObj.m_pTableCursor ), + m_pPropSet( rObj.m_pPropSet ) +{ + if(m_pFormat) + StartListening(m_pFormat->GetNotifier()); + m_bDisposed = false; + + acquire(); + try + { + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + if (pTable) + { + m_xDataProvider->AddDataSequence( *pTable, this ); + m_xDataProvider->addEventListener( static_cast< lang::XEventListener * >(this) ); + } + else { + OSL_FAIL( "table missing" ); + } + } + catch (uno::RuntimeException &) + { + // TODO: shouldn't there be a call to release() here? + throw; + } + catch (uno::Exception &) + { + } + release(); + +#if OSL_DEBUG_LEVEL > 0 + // check if it can properly convert into a SwUnoTableCursor + // which is required for some functions + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + OSL_ENSURE(pUnoTableCursor, "SwChartDataSequence: cursor not SwUnoTableCursor"); +#endif +} + +SwChartDataSequence::~SwChartDataSequence() +{ +} + +OUString SAL_CALL SwChartDataSequence::getSourceRangeRepresentation( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + OUString aRes; + SwFrameFormat* pTableFormat = GetFrameFormat(); + if (pTableFormat) + { + const OUString aCellRange( GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + OSL_ENSURE( !aCellRange.isEmpty(), "failed to get cell range" ); + aRes = pTableFormat->GetName() + "." + aCellRange; + } + return aRes; +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::generateLabel( + chart2::data::LabelOrigin eLabelOrigin ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Sequence< OUString > aLabels; + + { + SwRangeDescriptor aDesc; + bool bOk = false; + SwFrameFormat* pTableFormat = GetFrameFormat(); + if (!pTableFormat) + throw uno::RuntimeException("No table format found."); + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if (!pTable) + throw uno::RuntimeException("No table found."); + if (pTable->IsTableComplex()) + throw uno::RuntimeException("Table too complex."); + + const OUString aCellRange( GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + OSL_ENSURE( !aCellRange.isEmpty(), "failed to get cell range" ); + bOk = FillRangeDescriptor( aDesc, aCellRange ); + OSL_ENSURE( bOk, "failed to get SwRangeDescriptor" ); + + if (bOk) + { + aDesc.Normalize(); + sal_Int32 nColSpan = aDesc.nRight - aDesc.nLeft + 1; + sal_Int32 nRowSpan = aDesc.nBottom - aDesc.nTop + 1; + OSL_ENSURE( nColSpan == 1 || nRowSpan == 1, + "unexpected range of selected cells" ); + + OUString aText; // label text to be returned + bool bReturnEmptyText = false; + bool bUseCol = true; + if (eLabelOrigin == chart2::data::LabelOrigin_COLUMN) + bUseCol = true; + else if (eLabelOrigin == chart2::data::LabelOrigin_ROW) + bUseCol = false; + else if (eLabelOrigin == chart2::data::LabelOrigin_SHORT_SIDE) + { + bUseCol = nColSpan < nRowSpan; + bReturnEmptyText = nColSpan == nRowSpan; + } + else if (eLabelOrigin == chart2::data::LabelOrigin_LONG_SIDE) + { + bUseCol = nColSpan > nRowSpan; + bReturnEmptyText = nColSpan == nRowSpan; + } + else { + OSL_FAIL( "unexpected case" ); + } + + // build label sequence + + sal_Int32 nSeqLen = bUseCol ? nColSpan : nRowSpan; + aLabels.realloc( nSeqLen ); + OUString *pLabels = aLabels.getArray(); + for (sal_Int32 i = 0; i < nSeqLen; ++i) + { + if (!bReturnEmptyText) + { + aText = bUseCol ? m_aColLabelText : m_aRowLabelText; + sal_Int32 nCol = aDesc.nLeft; + sal_Int32 nRow = aDesc.nTop; + if (bUseCol) + nCol = nCol + i; + else + nRow = nRow + i; + OUString aCellName( sw_GetCellName( nCol, nRow ) ); + + sal_Int32 nLen = aCellName.getLength(); + if (nLen) + { + const sal_Unicode *pBuf = aCellName.getStr(); + const sal_Unicode *pEnd = pBuf + nLen; + while (pBuf < pEnd && ('0' > *pBuf || *pBuf > '9')) + ++pBuf; + // start of number found? + if (pBuf < pEnd && ('0' <= *pBuf && *pBuf <= '9')) + { + OUString aRplc; + std::u16string_view aNew; + if (bUseCol) + { + aRplc = "%COLUMNLETTER"; + aNew = aCellName.subView(0, pBuf - aCellName.getStr()); + } + else + { + aRplc = "%ROWNUMBER"; + aNew = std::u16string_view(pBuf, (aCellName.getStr() + nLen) - pBuf); + } + aText = aText.replaceFirst( aRplc, aNew ); + } + } + } + pLabels[i] = aText; + } + } + } + + return aLabels; +} + +::sal_Int32 SAL_CALL SwChartDataSequence::getNumberFormatKeyByIndex( + ::sal_Int32 /*nIndex*/ ) +{ + return 0; +} + +std::vector< css::uno::Reference< css::table::XCell > > SwChartDataSequence::GetCells() +{ + if (m_bDisposed) + throw lang::DisposedException(); + auto pTableFormat(GetFrameFormat()); + if(!pTableFormat) + return std::vector< css::uno::Reference< css::table::XCell > >(); + auto pTable(SwTable::FindTable(pTableFormat)); + if(pTable->IsTableComplex()) + return std::vector< css::uno::Reference< css::table::XCell > >(); + SwRangeDescriptor aDesc; + if(!FillRangeDescriptor(aDesc, GetCellRangeName(*pTableFormat, *m_pTableCursor))) + return std::vector< css::uno::Reference< css::table::XCell > >(); + return SwXCellRange::CreateXCellRange(m_pTableCursor, *pTableFormat, aDesc)->GetCells(); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::getTextualData() +{ + SolarMutexGuard aGuard; + auto vCells(GetCells()); + uno::Sequence< OUString > vTextData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vTextData.getArray(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->getString(); }); + return vTextData; +} + +uno::Sequence< uno::Any > SAL_CALL SwChartDataSequence::getData() +{ + SolarMutexGuard aGuard; + try + { + auto vCells(GetCells()); + uno::Sequence< uno::Any > vAnyData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vAnyData.getArray(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->GetAny(); }); + return vAnyData; + } + catch (const lang::DisposedException&) + { + TOOLS_WARN_EXCEPTION( "sw", "unexpected exception caught" ); + } + return uno::Sequence< uno::Any >{}; +} + +uno::Sequence< double > SAL_CALL SwChartDataSequence::getNumericalData() +{ + SolarMutexGuard aGuard; + auto vCells(GetCells()); + uno::Sequence< double > vNumData(vCells.size()); + std::transform(vCells.begin(), + vCells.end(), + vNumData.getArray(), + [] (decltype(vCells)::value_type& xCell) + { return static_cast<SwXCell*>(xCell.get())->GetForcedNumericalValue(); }); + return vNumData; +} + +uno::Reference< util::XCloneable > SAL_CALL SwChartDataSequence::createClone( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return new SwChartDataSequence( *this ); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL SwChartDataSequence::getPropertySetInfo( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + static uno::Reference< beans::XPropertySetInfo > xRes = m_pPropSet->getPropertySetInfo(); + return xRes; +} + +void SAL_CALL SwChartDataSequence::setPropertyValue( + const OUString& rPropertyName, + const uno::Any& rValue ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (rPropertyName != UNO_NAME_ROLE) + throw beans::UnknownPropertyException(rPropertyName); + + if ( !(rValue >>= m_aRole) ) + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SwChartDataSequence::getPropertyValue( + const OUString& rPropertyName ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (!(rPropertyName == UNO_NAME_ROLE)) + throw beans::UnknownPropertyException(rPropertyName); + + return uno::Any(m_aRole); +} + +void SAL_CALL SwChartDataSequence::addPropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::removePropertyChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::addVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +void SAL_CALL SwChartDataSequence::removeVetoableChangeListener( + const OUString& /*rPropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ ) +{ + OSL_FAIL( "not implemented" ); +} + +OUString SAL_CALL SwChartDataSequence::getImplementationName( ) +{ + return "SwChartDataSequence"; +} + +sal_Bool SAL_CALL SwChartDataSequence::supportsService(const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartDataSequence::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.DataSequence" }; +} + +void SwChartDataSequence::Notify( const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + m_pFormat = nullptr; + if(!m_pFormat || !m_pTableCursor) + { + m_pFormat = nullptr; + m_pTableCursor.reset(nullptr); + dispose(); + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + setModified( true ); + } +} + +sal_Bool SAL_CALL SwChartDataSequence::isModified( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + return true; +} + +void SAL_CALL SwChartDataSequence::setModified( + sal_Bool bModified ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (bModified) + LaunchModifiedEvent( m_aModifyListeners, static_cast< XModifyBroadcaster * >(this) ); +} + +void SAL_CALL SwChartDataSequence::addModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.addInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartDataSequence::removeModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.removeInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartDataSequence::disposing( const lang::EventObject& rSource ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + if (rSource.Source == cppu::getXWeak(m_xDataProvider.get())) + { + m_xDataProvider.clear(); + } +} + +void SAL_CALL SwChartDataSequence::dispose( ) +{ + bool bMustDispose( false ); + { + std::unique_lock aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (!bMustDispose) + return; + + m_bDisposed = true; + if (m_xDataProvider.is()) + { + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + if (pTable) + { + m_xDataProvider->RemoveDataSequence( *pTable, this ); + } + else { + OSL_FAIL( "table missing" ); + } + + //#i119653# The bug is crashed for an exception thrown by + //SwCharDataSequence::setModified() because + //the SwCharDataSequence object has been disposed. + + //Actually, the former design of SwClient will disconnect itself + //from the notification list in its destructor. + + //But the SwCharDataSequence won't be destructed but disposed in code + //(the data member SwChartDataSequence::bDisposed will be set to + //TRUE), the relationship between client and modification is not + //released. + + //So any notification from modify object will lead to said + //exception threw out. Recorrect the logic of code in + //SwChartDataSequence::Dispose(), release the relationship + //here... + if (m_pFormat && m_pFormat->HasWriterListeners()) + { + EndListeningAll(); + m_pFormat = nullptr; + m_pTableCursor.reset(nullptr); + } + } + + // require listeners to release references to this object + lang::EventObject aEvtObj( static_cast< chart2::data::XDataSequence * >(this) ); + std::unique_lock aGuard( GetChartMutex() ); + m_aModifyListeners.disposeAndClear( aGuard, aEvtObj ); + m_aEvtListeners.disposeAndClear( aGuard, aEvtObj ); +} + +void SAL_CALL SwChartDataSequence::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEvtListeners.addInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartDataSequence::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEvtListeners.removeInterface( aGuard, rxListener ); +} + +bool SwChartDataSequence::DeleteBox( const SwTableBox &rBox ) +{ + if (m_bDisposed) + throw lang::DisposedException(); + + // to be set if the last box of the data-sequence was removed here + bool bNowEmpty = false; + + // if the implementation cursor gets affected (i.e. the box where it is located + // in gets removed) we need to move it before that... (otherwise it does not need to change) + + const SwStartNode* pPointStartNode = m_pTableCursor->GetPoint()->GetNode().FindTableBoxStartNode(); + const SwStartNode* pMarkStartNode = m_pTableCursor->GetMark()->GetNode().FindTableBoxStartNode(); + + if (!m_pTableCursor->HasMark() || (pPointStartNode == rBox.GetSttNd() && pMarkStartNode == rBox.GetSttNd())) + { + bNowEmpty = true; + } + else if (pPointStartNode == rBox.GetSttNd() || pMarkStartNode == rBox.GetSttNd()) + { + sal_Int32 nPointRow = -1, nPointCol = -1; + sal_Int32 nMarkRow = -1, nMarkCol = -1; + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + OUString aPointCellName( pTable->GetTableBox( pPointStartNode->GetIndex() )->GetName() ); + OUString aMarkCellName( pTable->GetTableBox( pMarkStartNode->GetIndex() )->GetName() ); + + SwXTextTable::GetCellPosition( aPointCellName, nPointCol, nPointRow ); + SwXTextTable::GetCellPosition( aMarkCellName, nMarkCol, nMarkRow ); + OSL_ENSURE( nPointRow >= 0 && nPointCol >= 0, "invalid row and col" ); + OSL_ENSURE( nMarkRow >= 0 && nMarkCol >= 0, "invalid row and col" ); + + // move vertical or horizontal? + OSL_ENSURE( nPointRow == nMarkRow || nPointCol == nMarkCol, + "row/col indices not matching" ); + OSL_ENSURE( nPointRow != nMarkRow || nPointCol != nMarkCol, + "point and mark are identical" ); + bool bMoveVertical = (nPointCol == nMarkCol); + bool bMoveHorizontal = (nPointRow == nMarkRow); + + // get movement direction + bool bMoveLeft = false; // move left or right? + bool bMoveUp = false; // move up or down? + if (bMoveVertical) + { + if (pPointStartNode == rBox.GetSttNd()) // move point? + bMoveUp = nPointRow > nMarkRow; + else // move mark + bMoveUp = nMarkRow > nPointRow; + } + else if (bMoveHorizontal) + { + if (pPointStartNode == rBox.GetSttNd()) // move point? + bMoveLeft = nPointCol > nMarkCol; + else // move mark + bMoveLeft = nMarkCol > nPointCol; + } + else { + OSL_FAIL( "neither vertical nor horizontal movement" ); + } + + // get new box (position) to use... + sal_Int32 nRow = (pPointStartNode == rBox.GetSttNd()) ? nPointRow : nMarkRow; + sal_Int32 nCol = (pPointStartNode == rBox.GetSttNd()) ? nPointCol : nMarkCol; + if (bMoveVertical) + nRow += bMoveUp ? -1 : +1; + if (bMoveHorizontal) + nCol += bMoveLeft ? -1 : +1; + const OUString aNewCellName = sw_GetCellName( nCol, nRow ); + SwTableBox* pNewBox = const_cast<SwTableBox*>(pTable->GetTableBox( aNewCellName )); + + if (pNewBox) // set new position (cell range) to use + { + // This is how you get the first content node of a row: + // First get a SwNodeIndex pointing to the node after SwStartNode of the box... + SwNodeIndex aIdx( *pNewBox->GetSttNd(), +1 ); + // This can be a SwContentNode, but might also be a table or section node, + // therefore call GoNext + SwContentNode *pCNd = aIdx.GetNode().GetContentNode(); + if (!pCNd) + pCNd = GetFrameFormat()->GetDoc()->GetNodes().GoNext( &aIdx ); + // and then one can e.g. create a SwPosition: + SwPosition aNewPos( *pCNd ); // new position to be used with cursor + + // if the mark is to be changed, make sure there is one + if (pMarkStartNode == rBox.GetSttNd() && !m_pTableCursor->HasMark()) + m_pTableCursor->SetMark(); + + // set cursor to new position + SwPosition *pPos = (pPointStartNode == rBox.GetSttNd()) ? + m_pTableCursor->GetPoint() : m_pTableCursor->GetMark(); + if (pPos) + { + *pPos = aNewPos; + } + else { + OSL_FAIL( "neither point nor mark available for change" ); + } + } + else { + OSL_FAIL( "failed to get position" ); + } + } + + return bNowEmpty; +} + +void SwChartDataSequence::FillRangeDesc( SwRangeDescriptor &rRangeDesc ) const +{ + SwFrameFormat* pTableFormat = GetFrameFormat(); + if(pTableFormat) + { + SwTable* pTable = SwTable::FindTable( pTableFormat ); + if(!pTable->IsTableComplex()) + { + FillRangeDescriptor( rRangeDesc, GetCellRangeName( *pTableFormat, *m_pTableCursor ) ); + } + } +} + +/** + * Extends the data-sequence by new cells added at the end of the direction + * the data-sequence points to. + * If the cells are already within the range of the sequence nothing needs + * to be done. + * If the cells are beyond the end of the sequence (are not adjacent to the + * current last cell) nothing can be done. Only if the cells are adjacent to + * the last cell they can be added. + * + * @returns true if the data-sequence was changed. + * @param bExtendCols - specifies if columns or rows are to be extended + * @param nFirstNew - index of first new row/col to be included in data-sequence + * @param nLastNew - index of last new row/col to be included in data-sequence + */ +void SwChartDataSequence::ExtendTo( bool bExtendCol, + sal_Int32 nFirstNew, sal_Int32 nCount ) +{ + SwUnoTableCursor* pUnoTableCursor = dynamic_cast<SwUnoTableCursor*>(&(*m_pTableCursor)); + if (!pUnoTableCursor) + return; + + const SwStartNode *pStartNd = nullptr; + const SwTableBox *pStartBox = nullptr; + const SwTableBox *pEndBox = nullptr; + + const SwTable* pTable = SwTable::FindTable( GetFrameFormat() ); + OSL_ENSURE( !pTable->IsTableComplex(), "table too complex" ); + if (nCount < 1 || nFirstNew < 0 || pTable->IsTableComplex()) + return; + + // get range descriptor (cell range) for current data-sequence + + pStartNd = pUnoTableCursor->GetPoint()->GetNode().FindTableBoxStartNode(); + pEndBox = pTable->GetTableBox( pStartNd->GetIndex() ); + const OUString aEndBox( pEndBox->GetName() ); + + pStartNd = pUnoTableCursor->GetMark()->GetNode().FindTableBoxStartNode(); + pStartBox = pTable->GetTableBox( pStartNd->GetIndex() ); + const OUString aStartBox( pStartBox->GetName() ); + + SwRangeDescriptor aDesc; + // note that cell range here takes the newly added rows/cols already into account + OUString sDescrip = aStartBox + ":" + aEndBox; + FillRangeDescriptor( aDesc, sDescrip ); + + bool bChanged = false; + OUString aNewStartCell; + OUString aNewEndCell; + if (bExtendCol && aDesc.nBottom + 1 == nFirstNew) + { + // new column cells adjacent to the bottom of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nLeft == aDesc.nRight, "data-sequence is not a column" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom + nCount); + bChanged = true; + } + else if (bExtendCol && aDesc.nTop - nCount == nFirstNew) + { + // new column cells adjacent to the top of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nLeft == aDesc.nRight, "data-sequence is not a column" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop - nCount); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom); + bChanged = true; + } + else if (!bExtendCol && aDesc.nRight + 1 == nFirstNew) + { + // new row cells adjacent to the right of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nTop == aDesc.nBottom, "data-sequence is not a row" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight + nCount, aDesc.nBottom); + bChanged = true; + } + else if (!bExtendCol && aDesc.nLeft - nCount == nFirstNew) + { + // new row cells adjacent to the left of the + // current data-sequence to be added... + OSL_ENSURE( aDesc.nTop == aDesc.nBottom, "data-sequence is not a row" ); + aNewStartCell = sw_GetCellName(aDesc.nLeft - nCount, aDesc.nTop); + aNewEndCell = sw_GetCellName(aDesc.nRight, aDesc.nBottom); + bChanged = true; + } + + if (bChanged) + { + // move table cursor to new start and end of data-sequence + const SwTableBox *pNewStartBox = pTable->GetTableBox( aNewStartCell ); + const SwTableBox *pNewEndBox = pTable->GetTableBox( aNewEndCell ); + pUnoTableCursor->SetMark(); + pUnoTableCursor->GetPoint()->Assign( *pNewEndBox->GetSttNd() ); + pUnoTableCursor->GetMark()->Assign( *pNewStartBox->GetSttNd() ); + pUnoTableCursor->Move( fnMoveForward, GoInNode ); + pUnoTableCursor->MakeBoxSels(); + } +} + +SwChartLabeledDataSequence::SwChartLabeledDataSequence() +{ + m_bDisposed = false; +} + +SwChartLabeledDataSequence::~SwChartLabeledDataSequence() +{ +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartLabeledDataSequence::getValues( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return m_xData; +} + +void SwChartLabeledDataSequence::SetDataSequence( + uno::Reference< chart2::data::XDataSequence >& rxDest, + const uno::Reference< chart2::data::XDataSequence >& rxSource) +{ + uno::Reference< util::XModifyListener > xML(this); + uno::Reference< lang::XEventListener > xEL(this); + + // stop listening to old data-sequence + uno::Reference< util::XModifyBroadcaster > xMB( rxDest, uno::UNO_QUERY ); + if (xMB.is()) + xMB->removeModifyListener( xML ); + uno::Reference< lang::XComponent > xC( rxDest, uno::UNO_QUERY ); + if (xC.is()) + xC->removeEventListener( xEL ); + + rxDest = rxSource; + + // start listening to new data-sequence + xC.set( rxDest, uno::UNO_QUERY ); + if (xC.is()) + xC->addEventListener( xEL ); + xMB.set( rxDest, uno::UNO_QUERY ); + if (xMB.is()) + xMB->addModifyListener( xML ); +} + +void SAL_CALL SwChartLabeledDataSequence::setValues( + const uno::Reference< chart2::data::XDataSequence >& rxSequence ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (m_xData != rxSequence) + { + SetDataSequence( m_xData, rxSequence ); + // inform listeners of changes + LaunchModifiedEvent( m_aModifyListeners, static_cast< XModifyBroadcaster * >(this) ); + } +} + +uno::Reference< chart2::data::XDataSequence > SAL_CALL SwChartLabeledDataSequence::getLabel( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + return m_xLabels; +} + +void SAL_CALL SwChartLabeledDataSequence::setLabel( + const uno::Reference< chart2::data::XDataSequence >& rxSequence ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + if (m_xLabels != rxSequence) + { + SetDataSequence( m_xLabels, rxSequence ); + // inform listeners of changes + LaunchModifiedEvent( m_aModifyListeners, static_cast< XModifyBroadcaster * >(this) ); + } +} + +uno::Reference< util::XCloneable > SAL_CALL SwChartLabeledDataSequence::createClone( ) +{ + SolarMutexGuard aGuard; + if (m_bDisposed) + throw lang::DisposedException(); + + uno::Reference< util::XCloneable > xDataCloneable( m_xData, uno::UNO_QUERY ); + uno::Reference< util::XCloneable > xLabelsCloneable( m_xLabels, uno::UNO_QUERY ); + rtl::Reference<SwChartLabeledDataSequence > pRes = new SwChartLabeledDataSequence(); + if (xDataCloneable.is()) + { + uno::Reference< chart2::data::XDataSequence > xDataClone( xDataCloneable->createClone(), uno::UNO_QUERY ); + pRes->setValues( xDataClone ); + } + + if (xLabelsCloneable.is()) + { + uno::Reference< chart2::data::XDataSequence > xLabelsClone( xLabelsCloneable->createClone(), uno::UNO_QUERY ); + pRes->setLabel( xLabelsClone ); + } + return pRes; +} + +OUString SAL_CALL SwChartLabeledDataSequence::getImplementationName( ) +{ + return "SwChartLabeledDataSequence"; +} + +sal_Bool SAL_CALL SwChartLabeledDataSequence::supportsService( + const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence< OUString > SAL_CALL SwChartLabeledDataSequence::getSupportedServiceNames( ) +{ + return { "com.sun.star.chart2.data.LabeledDataSequence" }; +} + +void SAL_CALL SwChartLabeledDataSequence::disposing( + const lang::EventObject& rSource ) +{ + std::unique_lock aGuard( GetChartMutex() ); + uno::Reference< uno::XInterface > xRef( rSource.Source ); + if (xRef == m_xData) + m_xData.clear(); + if (xRef == m_xLabels) + m_xLabels.clear(); + if (!m_xData.is() && !m_xLabels.is()) + { + aGuard.unlock(); + dispose(); + } +} + +void SAL_CALL SwChartLabeledDataSequence::modified( + const lang::EventObject& rEvent ) +{ + if (rEvent.Source == m_xData || rEvent.Source == m_xLabels) + { + LaunchModifiedEvent( m_aModifyListeners, static_cast< XModifyBroadcaster * >(this) ); + } +} + +void SAL_CALL SwChartLabeledDataSequence::addModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.addInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::removeModifyListener( + const uno::Reference< util::XModifyListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aModifyListeners.removeInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::dispose( ) +{ + bool bMustDispose( false ); + { + std::unique_lock aGuard( GetChartMutex() ); + bMustDispose = !m_bDisposed; + if (!m_bDisposed) + m_bDisposed = true; + } + if (bMustDispose) + { + m_bDisposed = true; + + // require listeners to release references to this object + lang::EventObject aEvtObj( static_cast< chart2::data::XLabeledDataSequence * >(this) ); + std::unique_lock aGuard( GetChartMutex() ); + m_aModifyListeners.disposeAndClear( aGuard, aEvtObj ); + m_aEventListeners.disposeAndClear( aGuard, aEvtObj ); + } +} + +void SAL_CALL SwChartLabeledDataSequence::addEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.addInterface( aGuard, rxListener ); +} + +void SAL_CALL SwChartLabeledDataSequence::removeEventListener( + const uno::Reference< lang::XEventListener >& rxListener ) +{ + std::unique_lock aGuard( GetChartMutex() ); + if (!m_bDisposed && rxListener.is()) + m_aEventListeners.removeInterface( aGuard, rxListener ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |