/* -*- 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 .
*/
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
}
/** * 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()) returnfalse;
staticbool GetTableAndCellsFromRangeRep(
std::u16string_view rRangeRepresentation,
UIName &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 ))
std::swap(aStartCell, aEndCell);
} else// a single cell like in "Table1.B3"
{
aStartCell = aEndCell = aRange;
}
}
staticvoid 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!)
{
UIName 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 );
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
UIName 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 = std::move(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;
}
}
if (!m_pDoc) throw uno::RuntimeException(u"Not connected to a document."_ustr);
// 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
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 )
UIName aChartTableName;
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 nullptr; // 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 nullptr; // 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 nullptr; // 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 : aSubRanges)
{
UIName aTableName;
OUString aStartCell, aEndCell; bool bOk2 = GetTableAndCellsFromRangeRep(
rSubRange, aTableName, aStartCell, aEndCell );
OSL_ENSURE(bOk2, "failed to get table and start/end cells");
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 nullptr; // 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 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 : 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");
}
/** * 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
UIName aTableName;
OUString 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 );
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)
UIName aLabelTableName;
OUString aLabelStartCell, aLabelEndCell;
UIName aValuesTableName;
OUString 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_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; elseif (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
#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.toString() + ".";
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 : 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 != 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;
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();
uno::Reference< sheet::XRangeSelection > SAL_CALL SwChartDataProvider::getRangeSelection( )
{ // note: it is no error to return nothing here return uno::Reference< sheet::XRangeSelection >();
}
// 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;
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 );
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(u"Table too complex."_ustr);
// check that there is only one table used in all ranges if (!pFirstFoundTable)
pFirstFoundTable = pTable; if (pTable != pFirstFoundTable) throw lang::IllegalArgumentException();
sal_Int32 nCol, nRow;
SwXTextTable::GetCellPosition( aStartCell, nCol, nRow ); if (nCol < 0 || nRow < 0) throw uno::RuntimeException(u"Cell not found."_ustr);
//!! following objects/functions are implemented in XMLRangeHelper.?xx //!! which is a copy of the respective file from chart2 !!
XMLRangeHelper::CellRange aCellRange;
aCellRange.aTableName = aTableName.toString();
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(u"Cell not found."_ustr);
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);
// 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);
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
}
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
}
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 );
}
// 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)
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 = SwNodes::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 = std::move(aNewPos);
} else {
OSL_FAIL( "neither point nor mark available for change" );
}
} else {
OSL_FAIL( "failed to get position" );
}
}
/** * 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;
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;
} elseif (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;
} elseif (!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;
} elseif (!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();
}
}
// 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 );
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.