diff options
Diffstat (limited to 'sc/source/core/data/table1.cxx')
-rw-r--r-- | sc/source/core/data/table1.cxx | 2729 |
1 files changed, 2729 insertions, 0 deletions
diff --git a/sc/source/core/data/table1.cxx b/sc/source/core/data/table1.cxx new file mode 100644 index 000000000..b177bd55b --- /dev/null +++ b/sc/source/core/data/table1.cxx @@ -0,0 +1,2729 @@ +/* -*- 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 <scitems.hxx> +#include <editeng/justifyitem.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/unit_conversion.hxx> +#include <unotools/textsearch.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> + +#include <patattr.hxx> +#include <table.hxx> +#include <document.hxx> +#include <drwlayer.hxx> +#include <olinetab.hxx> +#include <global.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <refupdat.hxx> +#include <markdata.hxx> +#include <progress.hxx> +#include <prnsave.hxx> +#include <tabprotection.hxx> +#include <sheetevents.hxx> +#include <segmenttree.hxx> +#include <dbdata.hxx> +#include <conditio.hxx> +#include <globalnames.hxx> +#include <cellvalue.hxx> +#include <scmatrix.hxx> +#include <refupdatecontext.hxx> +#include <rowheightcontext.hxx> +#include <compressedarray.hxx> +#include <vcl/svapp.hxx> + +#include <formula/vectortoken.hxx> +#include <token.hxx> + +#include <vector> +#include <memory> + +using ::std::vector; + +namespace { + +ScProgress* GetProgressBar( + SCSIZE nCount, SCSIZE nTotalCount, ScProgress* pOuterProgress, const ScDocument* pDoc) +{ + if (nTotalCount < 1000) + { + // if the total number of rows is less than 1000, don't even bother + // with the progress bar because drawing progress bar can be very + // expensive especially in GTK. + return nullptr; + } + + if (pOuterProgress) + return pOuterProgress; + + if (nCount > 1) + return new ScProgress( + pDoc->GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nTotalCount, true); + + return nullptr; +} + +void GetOptimalHeightsInColumn( + sc::RowHeightContext& rCxt, ScColContainer& rCol, SCROW nStartRow, SCROW nEndRow, + ScProgress* pProgress, sal_uLong nProgressStart ) +{ + assert(nStartRow <= nEndRow); + + // first, one time over the whole range + // (with the last column in the hope that they most likely still are + // on standard format) + + + rCol.back().GetOptimalHeight(rCxt, nStartRow, nEndRow, 0, 0); + + // from there search for the standard height that is in use in the lower part + + RowHeightsArray& rHeights = rCxt.getHeightArray(); + sal_uInt16 nMinHeight = rHeights.GetValue(nEndRow); + SCSIZE nPos = nEndRow - 1; + while ( nPos ) + { + auto aRangeData = rHeights.GetRangeData(nPos-1); + if (aRangeData.maValue < nMinHeight) + break; + nPos = std::max<SCSIZE>(0, aRangeData.mnRow1); + } + + const SCROW nMinStart = nPos; + + sal_uInt64 nWeightedCount = nProgressStart + rCol.back().GetWeightedCount(nStartRow, nEndRow); + const SCCOL maxCol = rCol.size() - 1; // last col done already above + for (SCCOL nCol=0; nCol<maxCol; nCol++) + { + rCol[nCol].GetOptimalHeight(rCxt, nStartRow, nEndRow, nMinHeight, nMinStart); + + if (pProgress) + { + nWeightedCount += rCol[nCol].GetWeightedCount(nStartRow, nEndRow); + pProgress->SetState( nWeightedCount ); + } + } +} + +struct OptimalHeightsFuncObjBase +{ + virtual ~OptimalHeightsFuncObjBase() {} + virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) = 0; +}; + +struct SetRowHeightOnlyFunc : public OptimalHeightsFuncObjBase +{ + ScTable* mpTab; + explicit SetRowHeightOnlyFunc(ScTable* pTab) : + mpTab(pTab) + {} + + virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool /* bApi */) override + { + mpTab->SetRowHeightOnly(nStartRow, nEndRow, nHeight); + return false; + } +}; + +struct SetRowHeightRangeFunc : public OptimalHeightsFuncObjBase +{ + ScTable* mpTab; + double mnPPTY; + + SetRowHeightRangeFunc(ScTable* pTab, double nPPTY) : + mpTab(pTab), + mnPPTY(nPPTY) + {} + + virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) override + { + return mpTab->SetRowHeightRange(nStartRow, nEndRow, nHeight, mnPPTY, bApi); + } +}; + +bool SetOptimalHeightsToRows( + sc::RowHeightContext& rCxt, + OptimalHeightsFuncObjBase& rFuncObj, + ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, SCROW nStartRow, SCROW nEndRow, + bool bApi ) +{ + bool bChanged = false; + SCROW nRngStart = 0; + SCROW nRngEnd = 0; + sal_uInt16 nLast = 0; + sal_uInt16 nExtraHeight = rCxt.getExtraHeight(); + for (SCSIZE i = nStartRow; i <= o3tl::make_unsigned(nEndRow); i++) + { + size_t nIndex; + SCROW nRegionEndRow; + CRFlags nRowFlag = pRowFlags->GetValue( i, nIndex, nRegionEndRow ); + if ( nRegionEndRow > nEndRow ) + nRegionEndRow = nEndRow; + SCSIZE nMoreRows = nRegionEndRow - i; // additional equal rows after first + + bool bAutoSize = !(nRowFlag & CRFlags::ManualSize); + if (bAutoSize || rCxt.isForceAutoSize()) + { + if (nExtraHeight) + { + if (bAutoSize) + pRowFlags->SetValue( i, nRegionEndRow, nRowFlag | CRFlags::ManualSize); + } + else if (!bAutoSize) + pRowFlags->SetValue( i, nRegionEndRow, nRowFlag & ~CRFlags::ManualSize); + + for (SCSIZE nInner = i; nInner <= i + nMoreRows; ++nInner) + { + if (nLast) + { + SCROW nRangeRowEnd; + size_t nTmp; + sal_uInt16 nRangeValue = rCxt.getHeightArray().GetValue(nInner, nTmp, nRangeRowEnd); + if (nRangeValue + nExtraHeight == nLast) + { + nRngEnd = std::min<SCSIZE>(i + nMoreRows, nRangeRowEnd); + nInner = nRangeRowEnd; + } + else + { + bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi); + nLast = 0; + } + } + if (!nLast) + { + nLast = rCxt.getHeightArray().GetValue(nInner) + rCxt.getExtraHeight(); + nRngStart = nInner; + nRngEnd = nInner; + } + } + } + else + { + if (nLast) + bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi); + nLast = 0; + } + i += nMoreRows; // already handled - skip + } + if (nLast) + bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi); + + return bChanged; +} + +} + +ScTable::ScTable( ScDocument& rDoc, SCTAB nNewTab, const OUString& rNewName, + bool bColInfo, bool bRowInfo ) : + aCol( rDoc.GetSheetLimits(), INITIALCOLCOUNT ), + aName( rNewName ), + aCodeName( rNewName ), + nLinkRefreshDelay( 0 ), + nLinkMode( ScLinkMode::NONE ), + aPageStyle( ScResId(STR_STYLENAME_STANDARD) ), + nRepeatStartX( SCCOL_REPEAT_NONE ), + nRepeatEndX( SCCOL_REPEAT_NONE ), + nRepeatStartY( SCROW_REPEAT_NONE ), + nRepeatEndY( SCROW_REPEAT_NONE ), + mpRowHeights( static_cast<ScFlatUInt16RowSegments*>(nullptr) ), + mpHiddenCols(new ScFlatBoolColSegments(rDoc.MaxCol())), + mpHiddenRows(new ScFlatBoolRowSegments(rDoc.MaxRow())), + mpFilteredCols(new ScFlatBoolColSegments(rDoc.MaxCol())), + mpFilteredRows(new ScFlatBoolRowSegments(rDoc.MaxRow())), + nTableAreaX( 0 ), + nTableAreaY( 0 ), + nTableAreaVisibleX( 0 ), + nTableAreaVisibleY( 0 ), + nTab( nNewTab ), + rDocument( rDoc ), + pSortCollator( nullptr ), + nLockCount( 0 ), + aScenarioColor( COL_LIGHTGRAY ), + aTabBgColor( COL_AUTO ), + nScenarioFlags(ScScenarioFlags::NONE), + mpCondFormatList( new ScConditionalFormatList() ), + maLOKFreezeCell(-1, -1, nNewTab), + bScenario(false), + bLayoutRTL(false), + bLoadingRTL(false), + bPageSizeValid(false), + bTableAreaValid(false), + bTableAreaVisibleValid(false), + bVisible(true), + bPendingRowHeights(false), + bCalcNotification(false), + bGlobalKeepQuery(false), + bPrintEntireSheet(true), + bActiveScenario(false), + mbPageBreaksValid(false), + mbForceBreaks(false), + bStreamValid(false) +{ + aDefaultColData.InitAttrArray(new ScAttrArray(static_cast<SCCOL>(-1), nNewTab, rDoc, nullptr)); + if (bColInfo) + { + mpColWidth.reset( new ScCompressedArray<SCCOL, sal_uInt16>( rDocument.MaxCol()+1, STD_COL_WIDTH ) ); + mpColFlags.reset( new ScBitMaskCompressedArray<SCCOL, CRFlags>( rDocument.MaxCol()+1, CRFlags::NONE ) ); + } + + if (bRowInfo) + { + mpRowHeights.reset(new ScFlatUInt16RowSegments(rDocument.MaxRow(), ScGlobal::nStdRowHeight)); + pRowFlags.reset(new ScBitMaskCompressedArray<SCROW, CRFlags>( rDocument.MaxRow(), CRFlags::NONE)); + } + + if ( rDocument.IsDocVisible() ) + { + // when a sheet is added to a visible document, + // initialize its RTL flag from the system locale + bLayoutRTL = ScGlobal::IsSystemRTL(); + } + + ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); + if (pDrawLayer) + { + if ( pDrawLayer->ScAddPage( nTab ) ) // sal_False (not inserted) during Undo + { + pDrawLayer->ScRenamePage( nTab, aName ); + sal_uLong const nx = o3tl::convert((rDocument.MaxCol()+1) * STD_COL_WIDTH, o3tl::Length::twip, o3tl::Length::mm100); + sal_uLong ny = o3tl::convert((rDocument.MaxRow()+1) * ScGlobal::nStdRowHeight, o3tl::Length::twip, o3tl::Length::mm10); + pDrawLayer->SetPageSize( static_cast<sal_uInt16>(nTab), Size( nx, ny ), false ); + } + } + + for (SCCOL k=0; k < aCol.size(); k++) + aCol[k].Init( k, nTab, rDocument, true ); +} + +ScTable::~ScTable() COVERITY_NOEXCEPT_FALSE +{ + if (!rDocument.IsInDtorClear()) + { + for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol) + { + aCol[nCol].FreeNotes(); + } + // In the dtor, don't delete the pages in the wrong order. + // (or else nTab does not reflect the page number!) + // In ScDocument::Clear is afterwards used from Clear at the Draw Layer to delete everything. + + ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); + if (pDrawLayer) + pDrawLayer->ScRemovePage( nTab ); + } + + pRowFlags.reset(); + pSheetEvents.reset(); + pOutlineTable.reset(); + pSearchText.reset(); + moRepeatColRange.reset(); + moRepeatRowRange.reset(); + pScenarioRanges.reset(); + mpRangeName.reset(); + pDBDataNoName.reset(); + DestroySortCollator(); +} + +sal_Int64 ScTable::GetHashCode() const +{ + return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this)); +} + +void ScTable::SetName( const OUString& rNewName ) +{ + aName = rNewName; + aUpperName.clear(); // invalidated if the name is changed + + // SetStreamValid is handled in ScDocument::RenameTab +} + +const OUString& ScTable::GetUpperName() const +{ + if (aUpperName.isEmpty() && !aName.isEmpty()) + aUpperName = ScGlobal::getCharClass().uppercase(aName); + return aUpperName; +} + +void ScTable::SetVisible( bool bVis ) +{ + if (bVisible != bVis) + SetStreamValid(false); + + bVisible = bVis; +} + +void ScTable::SetStreamValid( bool bSet, bool bIgnoreLock ) +{ + if (!bStreamValid && !bSet) + return; // shortcut + if ( bIgnoreLock || !rDocument.IsStreamValidLocked() ) + bStreamValid = bSet; +} + +void ScTable::SetPendingRowHeights( bool bSet ) +{ + bPendingRowHeights = bSet; +} + +void ScTable::SetLayoutRTL( bool bSet ) +{ + bLayoutRTL = bSet; +} + +void ScTable::SetLoadingRTL( bool bSet ) +{ + bLoadingRTL = bSet; +} + +void ScTable::SetTabBgColor(const Color& rColor) +{ + if (aTabBgColor != rColor) + { + // The tab color has changed. Set this table 'modified'. + aTabBgColor = rColor; + SetStreamValid(false); + } +} + +void ScTable::SetScenario( bool bFlag ) +{ + bScenario = bFlag; +} + +void ScTable::SetLink( ScLinkMode nMode, + const OUString& rDoc, const OUString& rFlt, const OUString& rOpt, + const OUString& rTab, sal_uLong nRefreshDelay ) +{ + nLinkMode = nMode; + aLinkDoc = rDoc; // File + aLinkFlt = rFlt; // Filter + aLinkOpt = rOpt; // Filter options + aLinkTab = rTab; // Sheet name in source file + nLinkRefreshDelay = nRefreshDelay; // refresh delay in seconds, 0==off + + SetStreamValid(false); +} + +sal_uInt16 ScTable::GetOptimalColWidth( SCCOL nCol, OutputDevice* pDev, + double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY, + bool bFormula, const ScMarkData* pMarkData, + const ScColWidthParam* pParam ) +{ + if ( nCol >= aCol.size() ) + return ( STD_COL_WIDTH - STD_EXTRA_WIDTH ); + + return aCol[nCol].GetOptimalColWidth( pDev, nPPTX, nPPTY, rZoomX, rZoomY, + bFormula, STD_COL_WIDTH - STD_EXTRA_WIDTH, pMarkData, pParam ); +} + +tools::Long ScTable::GetNeededSize( SCCOL nCol, SCROW nRow, + OutputDevice* pDev, + double nPPTX, double nPPTY, + const Fraction& rZoomX, const Fraction& rZoomY, + bool bWidth, bool bTotalSize, bool bInPrintTwips ) +{ + if ( nCol >= aCol.size() ) + return 0; + + ScNeededSizeOptions aOptions; + aOptions.bSkipMerged = false; // count merged cells + aOptions.bTotalSize = bTotalSize; + + return aCol[nCol].GetNeededSize + ( nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, aOptions, nullptr, bInPrintTwips ); +} + +bool ScTable::SetOptimalHeight( + sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, bool bApi, + ScProgress* pOuterProgress, sal_uInt64 nProgressStart ) +{ + assert(nStartRow <= nEndRow); + + OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(), + "automatic OptimalHeight with Extra" ); + + if ( rDocument.IsAdjustHeightLocked() ) + { + return false; + } + + SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1); + + ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument); + + mpRowHeights->enableTreeSearch(false); + + GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart); + + SetRowHeightRangeFunc aFunc(this, rCxt.getPPTY()); + bool bChanged = SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, bApi); + + if ( pProgress != pOuterProgress ) + delete pProgress; + + mpRowHeights->enableTreeSearch(true); + + return bChanged; +} + +void ScTable::SetOptimalHeightOnly( + sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, + ScProgress* pOuterProgress, sal_uInt64 nProgressStart ) +{ + OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(), + "automatic OptimalHeight with Extra" ); + + if ( rDocument.IsAdjustHeightLocked() ) + return; + + SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1); + + ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument); + + GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart); + + SetRowHeightOnlyFunc aFunc(this); + + SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, true); + + if ( pProgress != pOuterProgress ) + delete pProgress; +} + +bool ScTable::GetCellArea( SCCOL& rEndCol, SCROW& rEndRow ) const +{ + bool bFound = false; + SCCOL nMaxX = 0; + SCROW nMaxY = 0; + for (SCCOL i=0; i<aCol.size(); i++) + { + if (!aCol[i].IsEmptyData()) + { + bFound = true; + nMaxX = i; + SCROW nRow = aCol[i].GetLastDataPos(); + if (nRow > nMaxY) + nMaxY = nRow; + } + if ( aCol[i].HasCellNotes() ) + { + SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow(); + if (maxNoteRow >= nMaxY) + { + bFound = true; + nMaxY = maxNoteRow; + } + if (i>nMaxX) + { + bFound = true; + nMaxX = i; + } + } + if (aCol[i].HasSparklines()) + { + SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow(); + if (maxSparklineRow >= nMaxY) + { + bFound = true; + nMaxY = maxSparklineRow; + } + if (i > nMaxX) + { + bFound = true; + nMaxX = i; + } + } + } + + rEndCol = nMaxX; + rEndRow = nMaxY; + return bFound; +} + +bool ScTable::GetTableArea( SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const +{ + bool bRet = true; //TODO: remember? + if (bCalcHiddens) + { + if (!bTableAreaValid) + { + bRet = GetPrintArea(nTableAreaX, nTableAreaY, true, bCalcHiddens); + bTableAreaValid = true; + } + rEndCol = nTableAreaX; + rEndRow = nTableAreaY; + } + else + { + if (!bTableAreaVisibleValid) + { + bRet = GetPrintArea(nTableAreaVisibleX, nTableAreaVisibleY, true, bCalcHiddens); + bTableAreaVisibleValid = true; + } + rEndCol = nTableAreaVisibleX; + rEndRow = nTableAreaVisibleY; + } + return bRet; +} + +const SCCOL SC_COLUMNS_STOP = 30; + +bool ScTable::GetPrintArea( SCCOL& rEndCol, SCROW& rEndRow, bool bNotes, bool bCalcHiddens ) const +{ + bool bFound = false; + SCCOL nMaxX = 0; + SCROW nMaxY = 0; + SCCOL i; + + for (i=0; i<aCol.size(); i++) // Test data + { + if (bCalcHiddens || !rDocument.ColHidden(i, nTab)) + { + if (!aCol[i].IsEmptyData()) + { + bFound = true; + if (i>nMaxX) + nMaxX = i; + SCROW nColY = aCol[i].GetLastDataPos(); + if (nColY > nMaxY) + nMaxY = nColY; + } + if (bNotes && aCol[i].HasCellNotes() ) + { + SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow(); + if (maxNoteRow >= nMaxY) + { + bFound = true; + nMaxY = maxNoteRow; + } + if (i>nMaxX) + { + bFound = true; + nMaxX = i; + } + } + if (aCol[i].HasSparklines()) + { + SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow(); + if (maxSparklineRow >= nMaxY) + { + bFound = true; + nMaxY = maxSparklineRow; + } + if (i > nMaxX) + { + bFound = true; + nMaxX = i; + } + } + } + } + + SCCOL nMaxDataX = nMaxX; + + for (i=0; i<aCol.size(); i++) // Test attribute + { + if (bCalcHiddens || !rDocument.ColHidden(i, nTab)) + { + SCROW nLastRow; + if (aCol[i].GetLastVisibleAttr( nLastRow )) + { + bFound = true; + nMaxX = i; + if (nLastRow > nMaxY) + nMaxY = nLastRow; + } + } + } + + if (nMaxX == rDocument.MaxCol()) // omit attribute at the right + { + --nMaxX; + while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], 0, rDocument.MaxRow()) ) + --nMaxX; + } + + if ( nMaxX < nMaxDataX ) + { + nMaxX = nMaxDataX; + } + else if ( nMaxX > nMaxDataX ) + { + SCCOL nAttrStartX = nMaxDataX + 1; + while ( nAttrStartX < (aCol.size()-1) ) + { + SCCOL nAttrEndX = nAttrStartX; + while ( nAttrEndX < (aCol.size()-1) && aCol[nAttrStartX].IsVisibleAttrEqual(aCol[nAttrEndX+1], 0, rDocument.MaxRow()) ) + ++nAttrEndX; + if ( nAttrEndX + 1 - nAttrStartX >= SC_COLUMNS_STOP ) + { + // found equally-formatted columns behind data -> stop before these columns + nMaxX = nAttrStartX - 1; + + // also don't include default-formatted columns before that + SCROW nDummyRow; + while ( nMaxX > nMaxDataX && !aCol[nMaxX].GetLastVisibleAttr( nDummyRow ) ) + --nMaxX; + break; + } + nAttrStartX = nAttrEndX + 1; + } + } + + rEndCol = nMaxX; + rEndRow = nMaxY; + return bFound; +} + +bool ScTable::GetPrintAreaHor( SCROW nStartRow, SCROW nEndRow, + SCCOL& rEndCol ) const +{ + bool bFound = false; + SCCOL nMaxX = 0; + SCCOL i; + + for (i=0; i<aCol.size(); i++) // Test attribute + { + if (aCol[i].HasVisibleAttrIn( nStartRow, nEndRow )) + { + bFound = true; + nMaxX = i; + } + } + + if (nMaxX == rDocument.MaxCol()) // omit attribute at the right + { + --nMaxX; + while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], nStartRow, nEndRow) ) + --nMaxX; + } + + for (i=0; i<aCol.size(); i++) // test the data + { + if (!aCol[i].IsEmptyData( nStartRow, nEndRow )) //TODO: bNotes ?????? + { + bFound = true; + if (i > nMaxX) + nMaxX = i; + } + else if (aCol[i].HasSparklines()) + { + if (i > nMaxX) + { + bFound = true; + nMaxX = i; + } + } + } + + rEndCol = nMaxX; + return bFound; +} + +bool ScTable::GetPrintAreaVer( SCCOL nStartCol, SCCOL nEndCol, + SCROW& rEndRow, bool bNotes ) const +{ + nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 ); + nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 ); + bool bFound = false; + SCROW nMaxY = 0; + SCCOL i; + + for (i=nStartCol; i<=nEndCol; i++) // Test attribute + { + SCROW nLastRow; + if (aCol[i].GetLastVisibleAttr( nLastRow )) + { + bFound = true; + if (nLastRow > nMaxY) + nMaxY = nLastRow; + } + } + + for (i=nStartCol; i<=nEndCol; i++) // Test data + { + if (!aCol[i].IsEmptyData()) + { + bFound = true; + SCROW nColY = aCol[i].GetLastDataPos(); + if (nColY > nMaxY) + nMaxY = nColY; + } + if (bNotes && aCol[i].HasCellNotes() ) + { + SCROW maxNoteRow =aCol[i].GetCellNotesMaxRow(); + if (maxNoteRow > nMaxY) + { + bFound = true; + nMaxY = maxNoteRow; + } + } + if (aCol[i].HasSparklines()) + { + SCROW maxNoteRow = aCol[i].GetSparklinesMaxRow(); + if (maxNoteRow > nMaxY) + { + bFound = true; + nMaxY = maxNoteRow; + } + } + } + + rEndRow = nMaxY; + return bFound; +} + +bool ScTable::GetDataStart( SCCOL& rStartCol, SCROW& rStartRow ) const +{ + bool bFound = false; + SCCOL nMinX = aCol.size()-1; + SCROW nMinY = rDocument.MaxRow(); + SCCOL i; + + for (i=0; i<aCol.size(); i++) // Test attribute + { + SCROW nFirstRow; + if (aCol[i].GetFirstVisibleAttr( nFirstRow )) + { + if (!bFound) + nMinX = i; + bFound = true; + if (nFirstRow < nMinY) + nMinY = nFirstRow; + } + } + + if (nMinX == 0) // omit attribute at the right + { + if ( aCol.size() > 1 && aCol[0].IsVisibleAttrEqual(aCol[1], 0, rDocument.MaxRow())) // no single ones + { + ++nMinX; + while ( nMinX<(aCol.size()-1) && aCol[nMinX].IsVisibleAttrEqual(aCol[nMinX-1], 0, rDocument.MaxRow())) + ++nMinX; + } + } + + bool bDatFound = false; + for (i=0; i<aCol.size(); i++) // Test data + { + if (!aCol[i].IsEmptyData()) + { + if (!bDatFound && i<nMinX) + nMinX = i; + bFound = bDatFound = true; + SCROW nRow = aCol[i].GetFirstDataPos(); + if (nRow < nMinY) + nMinY = nRow; + } + if ( aCol[i].HasCellNotes() ) + { + SCROW minNoteRow = aCol[i].GetCellNotesMinRow(); + if (minNoteRow <= nMinY) + { + bFound = true; + nMinY = minNoteRow; + } + if (i<nMinX) + { + bFound = true; + nMinX = i; + } + } + if (aCol[i].HasSparklines()) + { + SCROW minSparkline = aCol[i].GetSparklinesMinRow(); + if (minSparkline <= nMinY) + { + bFound = true; + nMinY = minSparkline; + } + if (i < nMinX) + { + bFound = true; + nMinX = i; + } + } + } + rStartCol = nMinX; + rStartRow = nMinY; + return bFound; +} + +void ScTable::GetDataArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, + bool bIncludeOld, bool bOnlyDown ) const +{ + // return the smallest area containing at least all contiguous cells having data. This area + // is a square containing also empty cells. It may shrink or extend the area given as input + // Flags as modifiers: + // + // bIncludeOld = true ensure that the returned area contains at least the initial area, + // independently of the emptiness of rows / columns (i.e. does not allow shrinking) + // bOnlyDown = true means extend / shrink the inputted area only down, i.e modify only rEndRow + + rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 ); + rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 ); + + bool bLeft = false; + bool bRight = false; + bool bTop = false; + bool bBottom = false; + bool bChanged = false; + + // We need to cache sc::ColumnBlockConstPosition per each column. + std::vector< sc::ColumnBlockConstPosition > blockPos( rEndCol + 1 ); + for( SCCOL i = 0; i <= rEndCol; ++i ) + aCol[ i ].InitBlockPosition( blockPos[ i ] ); + + do + { + bChanged = false; + + if (!bOnlyDown) + { + SCROW nStart = rStartRow; + SCROW nEnd = rEndRow; + if (nStart>0) --nStart; + if (nEnd<rDocument.MaxRow()) ++nEnd; + + if (rEndCol < (aCol.size()-1)) + if (!aCol[rEndCol+1].IsEmptyData(nStart,nEnd)) + { + assert( int( blockPos.size()) == rEndCol + 1 ); + ++rEndCol; + blockPos.resize( blockPos.size() + 1 ); + aCol[ rEndCol ].InitBlockPosition( blockPos[ rEndCol ] ); + bChanged = true; + bRight = true; + } + + if (rStartCol > 0) + if (!aCol[rStartCol-1].IsEmptyData(nStart,nEnd)) + { + --rStartCol; + bChanged = true; + bLeft = true; + } + + if (rStartRow > 0) + { + SCROW nTest = rStartRow-1; + bool needExtend = false; + for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++) + if (aCol[i].HasDataAt(blockPos[i], nTest)) + needExtend = true; + if (needExtend) + { + --rStartRow; + bChanged = true; + bTop = true; + } + } + } + + if (rEndRow < rDocument.MaxRow()) + { + SCROW nTest = rEndRow+1; + bool needExtend = false; + for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++) + if (aCol[i].HasDataAt(blockPos[ i ], nTest)) + needExtend = true; + if (needExtend) + { + ++rEndRow; + bChanged = true; + bBottom = true; + } + } + } + while( bChanged ); + + if ( !bIncludeOld && !bOnlyDown ) + { + if ( !bLeft ) + while ( rStartCol < rEndCol && rStartCol < (aCol.size()-1) && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) ) + ++rStartCol; + + if ( !bRight ) + while ( rEndCol > 0 && rStartCol < rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) ) + --rEndCol; + + if ( !bTop && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow ) + { + bool bShrink = true; + do + { + for ( SCCOL i = rStartCol; i<=rEndCol && bShrink; i++) + if (aCol[i].HasDataAt(rStartRow)) + bShrink = false; + if (bShrink) + ++rStartRow; + } while (bShrink && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow); + } + } + + if ( !bIncludeOld ) + { + if ( !bBottom && rEndRow > 0 && rStartRow < rEndRow ) + { + SCROW nLastDataRow = GetLastDataRow( rStartCol, rEndCol, rEndRow); + if (nLastDataRow < rEndRow) + rEndRow = std::max( rStartRow, nLastDataRow); + } + } +} + +bool ScTable::GetDataAreaSubrange( ScRange& rRange ) const +{ + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + + if ( nCol1 >= aCol.size() ) + return false; + + nCol2 = std::min<SCCOL>( nCol2, aCol.size()-1 ); + + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + + SCCOL nFirstNonEmptyCol = -1, nLastNonEmptyCol = -1; + SCROW nRowStart = nRow2, nRowEnd = nRow1; + + for ( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol ) + { + SCROW nRowStartThis = nRow1, nRowEndThis = nRow2; + bool bTrimmed = aCol[nCol].TrimEmptyBlocks(nRowStartThis, nRowEndThis); + if ( bTrimmed ) + { + if ( nFirstNonEmptyCol == -1 ) + nFirstNonEmptyCol = nCol; + nLastNonEmptyCol = nCol; + + nRowStart = std::min<SCROW>(nRowStart, nRowStartThis); + nRowEnd = std::max<SCROW>(nRowEnd, nRowEndThis); + } + } + + if ( nFirstNonEmptyCol == -1 ) + return false; + + assert(nFirstNonEmptyCol <= nLastNonEmptyCol); + assert(nRowStart <= nRowEnd); + + rRange.aStart.Set(nFirstNonEmptyCol, nRowStart, rRange.aStart.Tab()); + rRange.aEnd.Set(nLastNonEmptyCol, nRowEnd, rRange.aEnd.Tab()); + + return true; +} + +bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow, + SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol, + ScDataAreaExtras* pDataAreaExtras ) const +{ + rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 ); + // check for rEndCol is done below. + + o_bShrunk = false; + + PutInOrder( rStartCol, rEndCol); + PutInOrder( rStartRow, rEndRow); + if (rStartCol < 0) + { + rStartCol = 0; + o_bShrunk = true; + } + if (rStartRow < 0) + { + rStartRow = 0; + o_bShrunk = true; + } + if (rEndCol >= aCol.size()) + { + rEndCol = aCol.size()-1; + o_bShrunk = true; + } + if (rEndRow > rDocument.MaxRow()) + { + rEndRow = rDocument.MaxRow(); + o_bShrunk = true; + } + + while (rStartCol < rEndCol) + { + if (aCol[rEndCol].IsEmptyData( rStartRow, rEndRow)) + { + if (pDataAreaExtras && pDataAreaExtras->mnEndCol < rEndCol) + { + // Check in order of likeliness. + if ( (pDataAreaExtras->mbCellFormats + && aCol[rEndCol].GetPatternCount( rStartRow, rEndRow) > 1 + && aCol[rEndCol].HasVisibleAttrIn( rStartRow, rEndRow)) || + (pDataAreaExtras->mbCellNotes + && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow)) || + (pDataAreaExtras->mbCellDrawObjects + && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow))) + pDataAreaExtras->mnEndCol = rEndCol; + } + + --rEndCol; + o_bShrunk = true; + } + else + break; // while + } + + if (!bStickyLeftCol) + { + while (rStartCol < rEndCol) + { + if (aCol[rStartCol].IsEmptyData( rStartRow, rEndRow)) + { + if (pDataAreaExtras && pDataAreaExtras->mnStartCol > rStartCol) + { + // Check in order of likeliness. + if ( (pDataAreaExtras->mbCellFormats + && aCol[rStartCol].GetPatternCount( rStartRow, rEndRow) > 1 + && aCol[rStartCol].HasVisibleAttrIn( rStartRow, rEndRow)) || + (pDataAreaExtras->mbCellNotes + && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow)) || + (pDataAreaExtras->mbCellDrawObjects + && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow))) + pDataAreaExtras->mnStartCol = rStartCol; + } + + ++rStartCol; + o_bShrunk = true; + } + else + break; // while + } + } + + if (!bColumnsOnly) + { + while (rStartRow < rEndRow) + { + SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, pDataAreaExtras); + if (0 <= nLastDataRow && nLastDataRow < rEndRow) + { + rEndRow = std::max( rStartRow, nLastDataRow); + o_bShrunk = true; + } + else + break; // while + } + + if (!bStickyTopRow) + { + while (rStartRow < rEndRow) + { + bool bFound = false; + for (SCCOL i=rStartCol; i<=rEndCol && !bFound; i++) + { + if (aCol[i].HasDataAt(rStartRow, pDataAreaExtras)) + bFound = true; + } + if (!bFound) + { + ++rStartRow; + o_bShrunk = true; + } + else + break; // while + } + } + } + + return rStartCol != rEndCol || (bColumnsOnly ? + !aCol[rStartCol].IsEmptyData( rStartRow, rEndRow) : + (rStartRow != rEndRow || + aCol[rStartCol].HasDataAt( rStartRow, pDataAreaExtras))); +} + +SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const +{ + if ( !IsColValid( nCol1 ) || !ValidCol( nCol2 ) ) + return -1; + + nCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 ); + + SCROW nNewLastRow = 0; + for (SCCOL i = nCol1; i <= nCol2; ++i) + { + SCROW nThis = aCol[i].GetLastDataPos(nLastRow, pDataAreaExtras); + if (nNewLastRow < nThis) + nNewLastRow = nThis; + } + + return nNewLastRow; +} + +bool ScTable::IsEmptyData( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow ) const +{ + for( SCCOL col : GetAllocatedColumnsRange( nStartCol, nEndCol )) + if( !aCol[col].IsEmptyData( nStartRow, nEndRow )) + return false; + return true; +} + +SCSIZE ScTable::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, ScDirection eDir ) const +{ + SCCOL nStartColOrig = nStartCol; + SCCOL nEndColOrig = nEndCol; + nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 ); + nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 ); + + // The region is not allocated and does not contain any data. + if ( nStartColOrig != nStartCol ) + return ( ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP)) ? + static_cast<SCSIZE>(nEndRow - nStartRow + 1) : + static_cast<SCSIZE>(nEndColOrig - nStartColOrig + 1) ); + + SCSIZE nGapRight = static_cast<SCSIZE>(nEndColOrig - nEndCol); + SCSIZE nCount = 0; + SCCOL nCol; + if ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP)) + { + nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1); + for (nCol = nStartCol; nCol <= nEndCol; nCol++) + nCount = std::min(nCount, aCol[nCol].GetEmptyLinesInBlock(nStartRow, nEndRow, eDir)); + } + else if (eDir == DIR_RIGHT) + { + nCol = nEndCol; + while ((nCol >= nStartCol) && + aCol[nCol].IsEmptyData(nStartRow, nEndRow)) + { + nCount++; + nCol--; + } + nCount += nGapRight; + } + else + { + nCol = nStartCol; + while ((nCol <= nEndCol) && aCol[nCol].IsEmptyData(nStartRow, nEndRow)) + { + nCount++; + nCol++; + } + + // If the area between nStartCol and nEndCol are empty, + // add the count of unallocated columns on the right. + if ( nCol > nEndCol ) + nCount += nGapRight; + } + return nCount; +} + +bool ScTable::IsEmptyLine( SCROW nRow, SCCOL nStartCol, SCCOL nEndCol ) const +{ + // The range of columns are unallocated hence empty. + if ( nStartCol >= aCol.size() ) + return true; + + nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 ); + + for (SCCOL i=nStartCol; i<=nEndCol; i++) + if (aCol[i].HasDataAt(nRow)) + return false; + return true; +} + +void ScTable::LimitChartArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow ) const +{ + rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 ); + rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 ); + + while ( rStartCol<rEndCol && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) ) + ++rStartCol; + + while ( rStartCol<rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) ) + --rEndCol; + + while ( rStartRow<rEndRow && IsEmptyLine(rStartRow, rStartCol, rEndCol) ) + ++rStartRow; + + // Optimised loop for finding the bottom of the area, can be costly in large + // spreadsheets. + SCROW lastDataPos = 0; + for (SCCOL i=rStartCol; i<=rEndCol; i++) + lastDataPos = std::max(lastDataPos, aCol[i].GetLastDataPos()); + // reduce EndRow to the last row with data + rEndRow = std::min(rEndRow, lastDataPos); + // but make sure EndRow is >= StartRow + rEndRow = std::max(rStartRow, rEndRow); +} + +SCCOL ScTable::FindNextVisibleCol( SCCOL nCol, bool bRight ) const +{ + if(bRight) + { + nCol++; + SCCOL nEnd = 0; + bool bHidden = rDocument.ColHidden(nCol, nTab, nullptr, &nEnd); + if(bHidden) + nCol = nEnd +1; + + return std::min<SCCOL>(rDocument.MaxCol(), nCol); + } + else + { + nCol--; + SCCOL nStart = rDocument.MaxCol(); + bool bHidden = rDocument.ColHidden(nCol, nTab, &nStart); + if(bHidden) + nCol = nStart - 1; + + return std::max<SCCOL>(0, nCol); + } +} + +SCCOL ScTable::FindNextVisibleColWithContent( SCCOL nCol, bool bRight, SCROW nRow ) const +{ + const SCCOL nLastCol = aCol.size() - 1; + if(bRight) + { + // If nCol is the last allocated column index, there won't be any content to its right. + // To maintain the original return behaviour, return rDocument.MaxCol(). + if(nCol >= nLastCol) + return rDocument.MaxCol(); + + do + { + nCol++; + SCCOL nEndCol = 0; + bool bHidden = rDocument.ColHidden( nCol, nTab, nullptr, &nEndCol ); + if(bHidden) + { + nCol = nEndCol +1; + // Can end search early as there is no data after nLastCol. + // For nCol == nLastCol, it may still have data so don't want to return rDocument.MaxCol(). + if(nCol > nLastCol) + return rDocument.MaxCol(); + } + + if(aCol[nCol].HasVisibleDataAt(nRow)) + return nCol; + } + while(nCol < nLastCol); // Stop search as soon as the last allocated column is searched. + + return rDocument.MaxCol(); + } + else + { + // If nCol is in the unallocated range [nLastCol+1, rDocument.MaxCol()], then move it directly to nLastCol + // as there is no data in the unallocated range. This also makes the search faster and avoids + // the need for more range checks in the loop below. + if ( nCol > nLastCol ) + nCol = nLastCol; + + if(nCol == 0) + return 0; + + do + { + nCol--; + SCCOL nStartCol = rDocument.MaxCol(); + bool bHidden = rDocument.ColHidden( nCol, nTab, &nStartCol ); + if(bHidden) + { + nCol = nStartCol -1; + if(nCol <= 0) + return 0; + } + + if(aCol[nCol].HasVisibleDataAt(nRow)) + return nCol; + } + while(nCol > 0); + + return 0; + } +} + +void ScTable::FindAreaPos( SCCOL& rCol, SCROW& rRow, ScMoveDirection eDirection ) const +{ + const SCCOL nLastCol = aCol.size() - 1; + + if (eDirection == SC_MOVE_LEFT || eDirection == SC_MOVE_RIGHT) + { + SCCOL nNewCol = rCol; + bool bThere = ( nNewCol <= nLastCol ) && aCol[nNewCol].HasVisibleDataAt(rRow); + bool bRight = (eDirection == SC_MOVE_RIGHT); + if (bThere) + { + if(nNewCol >= rDocument.MaxCol() && eDirection == SC_MOVE_RIGHT) + return; + else if(nNewCol == 0 && eDirection == SC_MOVE_LEFT) + return; + + SCCOL nNextCol = FindNextVisibleCol( nNewCol, bRight ); + + if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) ) + { + bool bFound = false; + nNewCol = nNextCol; + do + { + nNextCol = FindNextVisibleCol( nNewCol, bRight ); + if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) ) + nNewCol = nNextCol; + else + bFound = true; + } + while(!bFound && nNextCol > 0 && nNextCol < rDocument.MaxCol()); + } + else + { + nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow); + } + } + else + { + nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow); + } + + if (nNewCol<0) + nNewCol=0; + if (nNewCol>rDocument.MaxCol()) + nNewCol=rDocument.MaxCol(); + rCol = nNewCol; + } + else + { + if ( rCol <= nLastCol ) + aCol[rCol].FindDataAreaPos(rRow,eDirection == SC_MOVE_DOWN); + else + { + // The cell (rCol, rRow) is equivalent to an empty cell (although not allocated). + // Set rRow to 0 or rDocument.MaxRow() depending on eDirection to maintain the behaviour of + // ScColumn::FindDataAreaPos() when the given column is empty. + rRow = ( eDirection == SC_MOVE_DOWN ) ? rDocument.MaxRow() : 0; + } + } +} + +bool ScTable::ValidNextPos( SCCOL nCol, SCROW nRow, const ScMarkData& rMark, + bool bMarked, bool bUnprotected ) const +{ + if (!ValidCol(nCol) || !ValidRow(nRow)) + return false; + + if (rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Overlapped)) + // Skip an overlapped cell. + return false; + + if (bMarked && !rMark.IsCellMarked(nCol,nRow)) + return false; + + /* TODO: for cursor movement *only* this should even take the protection + * options (select locked, select unlocked) into account, see + * ScTabView::SkipCursorHorizontal() and ScTabView::SkipCursorVertical(). */ + if (bUnprotected && rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected)) + return false; + + if (bMarked || bUnprotected) //TODO: also in other case ??? + { + // Hidden cells must be skipped, as the cursor would end up on the next cell + // even if it is protected or not marked. + //TODO: control per Extra-Parameter, only for Cursor movement ??? + + if (RowHidden(nRow)) + return false; + + if (ColHidden(nCol)) + return false; + } + + return true; +} + +// Skips the current cell if it is Hidden, Overlapped or Protected and Sheet is Protected +bool ScTable::SkipRow( const SCCOL nCol, SCROW& rRow, const SCROW nMovY, + const ScMarkData& rMark, const bool bUp, const SCROW nUsedY, + const bool bMarked, const bool bSheetProtected ) const +{ + if ( !ValidRow( rRow )) + return false; + + if (bSheetProtected && rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Protected)) + { + if ( rRow > nUsedY ) + rRow = (bUp ? nUsedY : rDocument.MaxRow() + nMovY); + else + rRow += nMovY; + + if (bMarked) + rRow = rMark.GetNextMarked( nCol, rRow, bUp ); + + return true; + } + else + { + bool bRowHidden = RowHidden( rRow ); + bool bOverlapped = rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Overlapped ); + + if ( bRowHidden || bOverlapped ) + { + rRow += nMovY; + if (bMarked) + rRow = rMark.GetNextMarked( nCol, rRow, bUp ); + + return true; + } + } + + return false; +} + +void ScTable::GetNextPos( SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, + bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const +{ + // Ensure bMarked is set only if there is a mark. + assert( !bMarked || rMark.IsMarked() || rMark.IsMultiMarked()); + + const bool bSheetProtected = IsProtected(); + + if ( bUnprotected && !bSheetProtected ) // Is sheet really protected? + bUnprotected = false; + + SCCOL nCol = rCol + nMovX; + SCROW nRow = rRow + nMovY; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + if (bMarked) + { + ScRange aRange( ScAddress::UNINITIALIZED); + if (rMark.IsMarked()) + aRange = rMark.GetMarkArea(); + else if (rMark.IsMultiMarked()) + aRange = rMark.GetMultiMarkArea(); + else + { + // Covered by assert() above, but for NDEBUG build. + if (ValidColRow(nCol,nRow)) + { + rCol = nCol; + rRow = nRow; + } + return; + } + nStartCol = aRange.aStart.Col(); + nStartRow = aRange.aStart.Row(); + nEndCol = aRange.aEnd.Col(); + nEndRow = aRange.aEnd.Row(); + } + else if (bUnprotected) + { + nStartCol = 0; + nStartRow = 0; + nEndCol = rCol; + nEndRow = rRow; + rDocument.GetPrintArea( nTab, nEndCol, nEndRow, true ); + // Add some cols/rows to the print area (which is "content or + // visually different from empty") to enable travelling through + // protected forms with empty cells and no visual indicator. + // 42 might be good enough and not too much... + nEndCol = std::min<SCCOL>( nEndCol+42, rDocument.MaxCol()); + nEndRow = std::min<SCROW>( nEndRow+42, rDocument.MaxRow()); + } + else + { + // Invalid values show up for instance for Tab, when nothing is + // selected and not protected (left / right edge), then leave values + // unchanged. + if (ValidColRow(nCol,nRow)) + { + rCol = nCol; + rRow = nRow; + } + + // Caller ensures actually moving nMovY to jump to prev/next row's + // start col. + if (nTabStartCol != SC_TABSTART_NONE) + rCol = nTabStartCol; + + return; + } + + if ( nMovY && (bMarked || bUnprotected)) + { + do + { + const bool bUp = (nMovY < 0); + const SCCOL nColAdd = (bUp ? -1 : 1); + + if (bMarked) + nRow = rMark.GetNextMarked( nCol, nRow, bUp ); + + if (nTabStartCol != SC_TABSTART_NONE) + { + /* NOTE: If current rCol < nTabStartCol when going down, there + * is no way to detect if the previous Tab wrapped around to + * the next row or if it was a Shift+Tab going backwards. The + * result after a wrap is an odd jump to the next row's + * nTabStartCol, which is logical though and always has been + * the case. Similar for rCol > nTabStartCol when going up. + * Related, it would be nice to limit advancing the position + * within bounds even if another wrap would occur, but again we + * can't tell if previously Tab or Shift+Tab was used, so we + * don't know if it would be nTabStartCol to nEndCol (for Tab) + * or nStartCol to nTabStartCol (for Shift+Tab). */ + + // Continue moving horizontally. + nMovX = nColAdd; + nCol = nTabStartCol; + break; // do + } + + while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected )) + ; + + sal_uInt16 nWrap = 0; + while ( nRow < nStartRow || nRow > nEndRow ) + { + nCol += nColAdd; + + while (nStartCol <= nCol && nCol <= nEndCol && ValidCol(nCol) && ColHidden(nCol)) + nCol += nColAdd; // skip hidden cols + + if (nCol < nStartCol) + { + nCol = nEndCol; + + if (++nWrap >= 2) + return; + } + else if (nCol > nEndCol) + { + nCol = nStartCol; + + if (++nWrap >= 2) + return; + } + if (nRow < nStartRow) + nRow = nEndRow; + else if (nRow > nEndRow) + nRow = nStartRow; + + if (bMarked) + nRow = rMark.GetNextMarked( nCol, nRow, bUp ); + + while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected )) + ; + } + } while (false); + } + + if ( nMovX && ( bMarked || bUnprotected ) ) + { + // wrap initial skip counting: + if (nCol < nStartCol) + { + nCol = nEndCol; + --nRow; + if (nRow < nStartRow) + nRow = nEndRow; + } + if (nCol > nEndCol) + { + nCol = nStartCol; + ++nRow; + if (nRow > nEndRow) + nRow = nStartRow; + } + + if ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) ) + { + const SCCOL nColCount = nEndCol - nStartCol + 1; + std::unique_ptr<SCROW[]> pNextRows( new SCROW[nColCount]); + const SCCOL nLastCol = aCol.size() - 1; + const bool bUp = (nMovX < 0); // Moving left also means moving up in rows. + const SCROW nRowAdd = (bUp ? -1 : 1); + sal_uInt16 nWrap = 0; + + if (bUp) + { + for (SCCOL i = 0; i < nColCount; ++i) + pNextRows[i] = (i + nStartCol > nCol) ? (nRow + nRowAdd) : nRow; + } + else + { + for (SCCOL i = 0; i < nColCount; ++i) + pNextRows[i] = (i + nStartCol < nCol) ? (nRow + nRowAdd) : nRow; + } + do + { + SCROW nNextRow = pNextRows[nCol - nStartCol] + nRowAdd; + if ( bMarked ) + nNextRow = rMark.GetNextMarked( nCol, nNextRow, bUp ); + if ( bUnprotected ) + nNextRow = ( nCol <= nLastCol ) ? aCol[nCol].GetNextUnprotected( nNextRow, bUp ) : + aDefaultColData.GetNextUnprotected( nNextRow, bUp ); + pNextRows[nCol - nStartCol] = nNextRow; + + if (bUp) + { + SCROW nMaxRow = nStartRow - 1; + for (SCCOL i = 0; i < nColCount; ++i) + { + if (pNextRows[i] >= nMaxRow) // when two equal the right one + { + nMaxRow = pNextRows[i]; + nCol = i + nStartCol; + } + } + nRow = nMaxRow; + + if ( nRow < nStartRow ) + { + if (++nWrap >= 2) + return; + nCol = nEndCol; + nRow = nEndRow; + for (SCCOL i = 0; i < nColCount; ++i) + pNextRows[i] = nEndRow; // do it all over again + } + } + else + { + SCROW nMinRow = nEndRow + 1; + for (SCCOL i = 0; i < nColCount; ++i) + { + if (pNextRows[i] < nMinRow) // when two equal the left one + { + nMinRow = pNextRows[i]; + nCol = i + nStartCol; + } + } + nRow = nMinRow; + + if ( nRow > nEndRow ) + { + if (++nWrap >= 2) + return; + nCol = nStartCol; + nRow = nStartRow; + for (SCCOL i = 0; i < nColCount; ++i) + pNextRows[i] = nStartRow; // do it all over again + } + } + } + while ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) ); + } + } + + if (ValidColRow(nCol,nRow)) + { + rCol = nCol; + rRow = nRow; + } +} + +bool ScTable::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark ) const +{ + ++rRow; // next row + + while ( rCol < aCol.size() ) + { + ScMarkArray aArray( rMark.GetMarkArray( rCol ) ); + while ( rRow <= rDocument.MaxRow() ) + { + SCROW nStart = aArray.GetNextMarked( rRow, false ); + if ( nStart <= rDocument.MaxRow() ) + { + SCROW nEnd = aArray.GetMarkEnd( nStart, false ); + + const sc::CellStoreType& rCells = aCol[rCol].maCells; + std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nStart); + sc::CellStoreType::const_iterator it = aPos.first; + SCROW nTestRow = nStart; + if (it->type == sc::element_type_empty) + { + // Skip the empty block. + nTestRow += it->size - aPos.second; + ++it; + if (it == rCells.end()) + { + // No more block. Move on to the next column. + rRow = rDocument.MaxRow() + 1; + continue; + } + } + + if (nTestRow <= nEnd) + { + // Cell found. + rRow = nTestRow; + return true; + } + + rRow = nEnd + 1; // Search for next selected range + } + else + rRow = rDocument.MaxRow() + 1; // End of column + } + rRow = 0; + ++rCol; // test next column + } + + // Though searched only the allocated columns, it is equivalent to a search till rDocument.MaxCol(). + rCol = rDocument.MaxCol() + 1; + return false; // Through all columns +} + +void ScTable::UpdateDrawRef( UpdateRefMode eUpdateRefMode, SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz, bool bUpdateNoteCaptionPos ) +{ + if ( !(nTab >= nTab1 && nTab <= nTab2 && nDz == 0) ) // only within the table + return; + + ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer(); + if ( eUpdateRefMode != URM_COPY && pDrawLayer ) + { + if ( eUpdateRefMode == URM_MOVE ) + { // source range + nCol1 = sal::static_int_cast<SCCOL>( nCol1 - nDx ); + nRow1 = sal::static_int_cast<SCROW>( nRow1 - nDy ); + nCol2 = sal::static_int_cast<SCCOL>( nCol2 - nDx ); + nRow2 = sal::static_int_cast<SCROW>( nRow2 - nDy ); + } + pDrawLayer->MoveArea( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy, + (eUpdateRefMode == URM_INSDEL), bUpdateNoteCaptionPos ); + } +} + +void ScTable::UpdateReference( + sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos ) +{ + bool bUpdated = false; + UpdateRefMode eUpdateRefMode = rCxt.meMode; + SCCOL nDx = rCxt.mnColDelta; + SCROW nDy = rCxt.mnRowDelta; + SCTAB nDz = rCxt.mnTabDelta; + SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col(); + SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row(); + SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab(); + + // Named expressions need to be updated before formulas accessing them. + if (mpRangeName) + mpRangeName->UpdateReference(rCxt, nTab); + + if (rCxt.meMode == URM_COPY ) + { + for( SCCOL col : GetAllocatedColumnsRange( rCxt.maRange.aStart.Col(), rCxt.maRange.aEnd.Col())) + bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc); + } + else + { + for( SCCOL col : GetAllocatedColumnsRange( 0, rDocument.MaxCol())) + bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc); + } + + if ( bIncludeDraw ) + UpdateDrawRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz, bUpdateNoteCaptionPos ); + + if ( nTab >= nTab1 && nTab <= nTab2 && nDz == 0 ) // print ranges: only within the table + { + SCTAB nSTab = nTab; + SCTAB nETab = nTab; + SCCOL nSCol = 0; + SCROW nSRow = 0; + SCCOL nECol = 0; + SCROW nERow = 0; + bool bRecalcPages = false; + + for ( auto& rPrintRange : aPrintRanges ) + { + nSCol = rPrintRange.aStart.Col(); + nSRow = rPrintRange.aStart.Row(); + nECol = rPrintRange.aEnd.Col(); + nERow = rPrintRange.aEnd.Row(); + + // do not try to modify sheet index of print range + if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode, + nCol1,nRow1,nTab, nCol2,nRow2,nTab, + nDx,nDy,0, + nSCol,nSRow,nSTab, nECol,nERow,nETab ) ) + { + rPrintRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 ); + bRecalcPages = true; + } + } + + if ( moRepeatColRange ) + { + nSCol = moRepeatColRange->aStart.Col(); + nSRow = moRepeatColRange->aStart.Row(); + nECol = moRepeatColRange->aEnd.Col(); + nERow = moRepeatColRange->aEnd.Row(); + + // do not try to modify sheet index of repeat range + if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode, + nCol1,nRow1,nTab, nCol2,nRow2,nTab, + nDx,nDy,0, + nSCol,nSRow,nSTab, nECol,nERow,nETab ) ) + { + *moRepeatColRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 ); + bRecalcPages = true; + nRepeatStartX = nSCol; // for UpdatePageBreaks + nRepeatEndX = nECol; + } + } + + if ( moRepeatRowRange ) + { + nSCol = moRepeatRowRange->aStart.Col(); + nSRow = moRepeatRowRange->aStart.Row(); + nECol = moRepeatRowRange->aEnd.Col(); + nERow = moRepeatRowRange->aEnd.Row(); + + // do not try to modify sheet index of repeat range + if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode, + nCol1,nRow1,nTab, nCol2,nRow2,nTab, + nDx,nDy,0, + nSCol,nSRow,nSTab, nECol,nERow,nETab ) ) + { + *moRepeatRowRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 ); + bRecalcPages = true; + nRepeatStartY = nSRow; // for UpdatePageBreaks + nRepeatEndY = nERow; + } + } + + // updating print ranges is not necessary with multiple print ranges + if ( bRecalcPages && GetPrintRangeCount() <= 1 ) + { + UpdatePageBreaks(nullptr); + + rDocument.RepaintRange( ScRange(0,0,nTab,rDocument.MaxCol(),rDocument.MaxRow(),nTab) ); + } + } + + if (bUpdated) + SetStreamValid(false); + + if(mpCondFormatList) + mpCondFormatList->UpdateReference(rCxt); + + if (pTabProtection) + pTabProtection->updateReference( eUpdateRefMode, rDocument, rCxt.maRange, nDx, nDy, nDz); +} + +void ScTable::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest, + ScDocument* pUndoDoc ) +{ + for (auto const & rpCol : aCol) + rpCol->UpdateTranspose( rSource, rDest, pUndoDoc ); +} + +void ScTable::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY ) +{ + for (auto const & rpCol : aCol) + rpCol->UpdateGrow( rArea, nGrowX, nGrowY ); +} + +void ScTable::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt ) +{ + // Store the old tab number in sc::UpdatedRangeNames for + // ScTokenArray::AdjustReferenceOnInsertedTab() to check with + // isNameModified() + if (mpRangeName) + mpRangeName->UpdateInsertTab(rCxt, nTab); + + if (nTab >= rCxt.mnInsertPos) + { + nTab += rCxt.mnSheets; + if (pDBDataNoName) + pDBDataNoName->UpdateMoveTab(nTab - 1 ,nTab); + } + + if (mpCondFormatList) + mpCondFormatList->UpdateInsertTab(rCxt); + + if (pTabProtection) + pTabProtection->updateReference( URM_INSDEL, rDocument, + ScRange( 0, 0, rCxt.mnInsertPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB), + 0, 0, rCxt.mnSheets); + + for (SCCOL i=0; i < aCol.size(); i++) + aCol[i].UpdateInsertTab(rCxt); + + SetStreamValid(false); +} + +void ScTable::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) +{ + // Store the old tab number in sc::UpdatedRangeNames for + // ScTokenArray::AdjustReferenceOnDeletedTab() to check with + // isNameModified() + if (mpRangeName) + mpRangeName->UpdateDeleteTab(rCxt, nTab); + + if (nTab > rCxt.mnDeletePos) + { + nTab -= rCxt.mnSheets; + if (pDBDataNoName) + pDBDataNoName->UpdateMoveTab(nTab + 1,nTab); + } + + if (mpCondFormatList) + mpCondFormatList->UpdateDeleteTab(rCxt); + + if (pTabProtection) + pTabProtection->updateReference( URM_INSDEL, rDocument, + ScRange( 0, 0, rCxt.mnDeletePos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB), + 0, 0, -rCxt.mnSheets); + + for (SCCOL i = 0; i < aCol.size(); ++i) + aCol[i].UpdateDeleteTab(rCxt); + + SetStreamValid(false); +} + +void ScTable::UpdateMoveTab( + sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo, ScProgress* pProgress ) +{ + nTab = nTabNo; + if (mpRangeName) + mpRangeName->UpdateMoveTab(rCxt, nTab); + + if (pDBDataNoName) + pDBDataNoName->UpdateMoveTab(rCxt.mnOldPos, rCxt.mnNewPos); + + if(mpCondFormatList) + mpCondFormatList->UpdateMoveTab(rCxt); + + if (pTabProtection) + pTabProtection->updateReference( URM_REORDER, rDocument, + ScRange( 0, 0, rCxt.mnOldPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB), + 0, 0, rCxt.mnNewPos - rCxt.mnOldPos); + + for ( SCCOL i=0; i < aCol.size(); i++ ) + { + aCol[i].UpdateMoveTab(rCxt, nTabNo); + if (pProgress) + pProgress->SetState(pProgress->GetState() + aCol[i].GetCodeCount()); + } + + SetStreamValid(false); +} + +void ScTable::UpdateCompile( bool bForceIfNameInUse ) +{ + for (SCCOL i=0; i < aCol.size(); i++) + { + aCol[i].UpdateCompile( bForceIfNameInUse ); + } +} + +void ScTable::SetTabNo(SCTAB nNewTab) +{ + nTab = nNewTab; + for (SCCOL i=0; i < aCol.size(); i++) + aCol[i].SetTabNo(nNewTab); +} + +void ScTable::FindRangeNamesInUse(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + sc::UpdatedRangeNames& rIndexes) const +{ + for (SCCOL i = nCol1; i <= nCol2 && IsColValid( i ); i++) + aCol[i].FindRangeNamesInUse(nRow1, nRow2, rIndexes); +} + +void ScTable::ExtendPrintArea( OutputDevice* pDev, + SCCOL /* nStartCol */, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow ) +{ + if ( !mpColFlags || !pRowFlags ) + { + OSL_FAIL("ExtendPrintArea: No ColInfo or RowInfo"); + return; + } + + Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip)); + double nPPTX = aPix1000.X() / 1000.0; + double nPPTY = aPix1000.Y() / 1000.0; + + // First, mark those columns that we need to skip i.e. hidden and empty columns. + + ScFlatBoolColSegments aSkipCols(rDocument.MaxCol()); + aSkipCols.setFalse(0, rDocument.MaxCol()); + for (SCCOL i = 0; i <= rDocument.MaxCol(); ++i) + { + SCCOL nLastCol = i; + if (ColHidden(i, nullptr, &nLastCol)) + { + // Columns are hidden in this range. + aSkipCols.setTrue(i, nLastCol); + } + else + { + // These columns are visible. Check for empty columns. + for (SCCOL j = i; j <= nLastCol; ++j) + { + if ( j >= aCol.size() ) + { + aSkipCols.setTrue( j, rDocument.MaxCol() ); + break; + } + if (aCol[j].GetCellCount() == 0) + // empty + aSkipCols.setTrue(j,j); + } + } + i = nLastCol; + } + + ScFlatBoolColSegments::RangeData aColData; + for (SCCOL nCol = rEndCol; nCol >= 0; --nCol) + { + if (!aSkipCols.getRangeData(nCol, aColData)) + // Failed to get the data. This should never happen! + return; + + if (aColData.mbValue) + { + // Skip these columns. + nCol = aColData.mnCol1; // move toward 0. + continue; + } + + // These are visible and non-empty columns. + for (SCCOL nDataCol = nCol; 0 <= nDataCol && nDataCol >= aColData.mnCol1; --nDataCol) + { + SCCOL nPrintCol = nDataCol; + VisibleDataCellIterator aIter(rDocument, *mpHiddenRows, aCol[nDataCol]); + ScRefCellValue aCell = aIter.reset(nStartRow); + if (aCell.isEmpty()) + // No visible cells found in this column. Skip it. + continue; + + while (!aCell.isEmpty()) + { + SCCOL nNewCol = nDataCol; + SCROW nRow = aIter.getRow(); + if (nRow > nEndRow) + // Went past the last row position. Bail out. + break; + + MaybeAddExtraColumn(nNewCol, nRow, pDev, nPPTX, nPPTY); + if (nNewCol > nPrintCol) + nPrintCol = nNewCol; + aCell = aIter.next(); + } + + if (nPrintCol > rEndCol) + // Make sure we don't shrink the print area. + rEndCol = nPrintCol; + } + nCol = aColData.mnCol1; // move toward 0. + } +} + +void ScTable::MaybeAddExtraColumn(SCCOL& rCol, SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY) +{ + // tdf#128873 we do not need to calculate text width (heavy operation) + // when we for sure know that an additional column will not be added + if (GetAllocatedColumnsCount() > rCol + 1) + { + ScRefCellValue aNextCell = aCol[rCol + 1].GetCellValue(nRow); + if (!aNextCell.isEmpty()) + { + // return rCol as is + return; + } + } + + ScColumn& rColumn = aCol[rCol]; + ScRefCellValue aCell = rColumn.GetCellValue(nRow); + if (!aCell.hasString()) + return; + + tools::Long nPixel = rColumn.GetTextWidth(nRow); + + // Width already calculated in Idle-Handler ? + if ( TEXTWIDTH_DIRTY == nPixel ) + { + ScNeededSizeOptions aOptions; + aOptions.bTotalSize = true; + aOptions.bFormula = false; //TODO: pass as parameter + aOptions.bSkipMerged = false; + + Fraction aZoom(1,1); + nPixel = rColumn.GetNeededSize( + nRow, pDev, nPPTX, nPPTY, aZoom, aZoom, true, aOptions, nullptr ); + + rColumn.SetTextWidth(nRow, static_cast<sal_uInt16>(nPixel)); + } + + tools::Long nTwips = static_cast<tools::Long>(nPixel / nPPTX); + tools::Long nDocW = GetColWidth( rCol ); + + tools::Long nMissing = nTwips - nDocW; + if ( nMissing > 0 ) + { + // look at alignment + + const ScPatternAttr* pPattern = GetPattern( rCol, nRow ); + const SfxItemSet* pCondSet = rDocument.GetCondResult( rCol, nRow, nTab ); + + SvxCellHorJustify eHorJust = + pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue(); + if ( eHorJust == SvxCellHorJustify::Center ) + nMissing /= 2; // distributed into both directions + else + { + // STANDARD is LEFT (only text is handled here) + bool bRight = ( eHorJust == SvxCellHorJustify::Right ); + if ( IsLayoutRTL() ) + bRight = !bRight; + if ( bRight ) + nMissing = 0; // extended only to the left (logical) + } + } + + SCCOL nNewCol = rCol; + while (nMissing > 0 && nNewCol < rDocument.MaxCol()) + { + auto nNextCol = nNewCol + 1; + bool bNextEmpty = true; + if (GetAllocatedColumnsCount() > nNextCol) + { + ScRefCellValue aNextCell = aCol[nNextCol].GetCellValue(nRow); + bNextEmpty = aNextCell.isEmpty(); + } + if (!bNextEmpty) + { + // Cell content in a next column ends display of this string. + nMissing = 0; + } + else + nMissing -= GetColWidth(++nNewCol); + } + rCol = nNewCol; +} + +namespace { + +class SetTableIndex +{ + SCTAB mnTab; +public: + explicit SetTableIndex(SCTAB nTab) : mnTab(nTab) {} + + void operator() (ScRange& rRange) const + { + rRange.aStart.SetTab(mnTab); + rRange.aEnd.SetTab(mnTab); + } +}; + +} + +void ScTable::CopyPrintRange(const ScTable& rTable) +{ + // The table index shouldn't be used when the print range is used, but + // just in case set the correct table index. + + aPrintRanges = rTable.aPrintRanges; + ::std::for_each(aPrintRanges.begin(), aPrintRanges.end(), SetTableIndex(nTab)); + + bPrintEntireSheet = rTable.bPrintEntireSheet; + + moRepeatColRange.reset(); + if (rTable.moRepeatColRange) + { + moRepeatColRange.emplace(*rTable.moRepeatColRange); + moRepeatColRange->aStart.SetTab(nTab); + moRepeatColRange->aEnd.SetTab(nTab); + } + + moRepeatRowRange.reset(); + if (rTable.moRepeatRowRange) + { + moRepeatRowRange.emplace(*rTable.moRepeatRowRange); + moRepeatRowRange->aStart.SetTab(nTab); + moRepeatRowRange->aEnd.SetTab(nTab); + } +} + +void ScTable::SetRepeatColRange( std::optional<ScRange> oNew ) +{ + moRepeatColRange = std::move(oNew); + + SetStreamValid(false); + + InvalidatePageBreaks(); +} + +void ScTable::SetRepeatRowRange( std::optional<ScRange> oNew ) +{ + moRepeatRowRange = std::move(oNew); + + SetStreamValid(false); + + InvalidatePageBreaks(); +} + +void ScTable::ClearPrintRanges() +{ + aPrintRanges.clear(); + bPrintEntireSheet = false; + + SetStreamValid(false); + + InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range +} + +void ScTable::AddPrintRange( const ScRange& rNew ) +{ + bPrintEntireSheet = false; + if( aPrintRanges.size() < 0xFFFF ) + aPrintRanges.push_back( rNew ); + + SetStreamValid(false); + + InvalidatePageBreaks(); +} + +void ScTable::SetPrintEntireSheet() +{ + if( !IsPrintEntireSheet() ) + { + ClearPrintRanges(); + bPrintEntireSheet = true; + } +} + +const ScRange* ScTable::GetPrintRange(sal_uInt16 nPos) const +{ + return (nPos < GetPrintRangeCount()) ? &aPrintRanges[ nPos ] : nullptr; +} + +void ScTable::FillPrintSaver( ScPrintSaverTab& rSaveTab ) const +{ + rSaveTab.SetAreas( std::vector(aPrintRanges), bPrintEntireSheet ); + rSaveTab.SetRepeat( moRepeatColRange, moRepeatRowRange ); +} + +void ScTable::RestorePrintRanges( const ScPrintSaverTab& rSaveTab ) +{ + aPrintRanges = rSaveTab.GetPrintRanges(); + bPrintEntireSheet = rSaveTab.IsEntireSheet(); + SetRepeatColRange( rSaveTab.GetRepeatCol() ); + SetRepeatRowRange( rSaveTab.GetRepeatRow() ); + + InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range + UpdatePageBreaks(nullptr); +} + +ScTable::VisibleDataCellIterator::VisibleDataCellIterator(const ScDocument& rDoc, ScFlatBoolRowSegments& rRowSegs, ScColumn& rColumn) : + mrDocument(rDoc), + mrRowSegs(rRowSegs), + mrColumn(rColumn), + mnCurRow(ROW_NOT_FOUND), + mnUBound(ROW_NOT_FOUND) +{ +} + +ScRefCellValue ScTable::VisibleDataCellIterator::reset(SCROW nRow) +{ + if (nRow > mrDocument.MaxRow()) + { + mnCurRow = ROW_NOT_FOUND; + return ScRefCellValue(); + } + + ScFlatBoolRowSegments::RangeData aData; + if (!mrRowSegs.getRangeData(nRow, aData)) + { + mnCurRow = ROW_NOT_FOUND; + return ScRefCellValue(); + } + + if (!aData.mbValue) + { + // specified row is visible. Take it. + mnCurRow = nRow; + mnUBound = aData.mnRow2; + } + else + { + // specified row is not-visible. The first visible row is the start of + // the next segment. + mnCurRow = aData.mnRow2 + 1; + mnUBound = mnCurRow; // get range data on the next iteration. + if (mnCurRow > mrDocument.MaxRow()) + { + // Make sure the row doesn't exceed our current limit. + mnCurRow = ROW_NOT_FOUND; + return ScRefCellValue(); + } + } + + maCell = mrColumn.GetCellValue(mnCurRow); + if (!maCell.isEmpty()) + // First visible cell found. + return maCell; + + // Find a first visible cell below this row (if any). + return next(); +} + +ScRefCellValue ScTable::VisibleDataCellIterator::next() +{ + if (mnCurRow == ROW_NOT_FOUND) + return ScRefCellValue(); + + while (mrColumn.GetNextDataPos(mnCurRow)) + { + if (mnCurRow > mnUBound) + { + // We don't know the visibility of this row range. Query it. + ScFlatBoolRowSegments::RangeData aData; + if (!mrRowSegs.getRangeData(mnCurRow, aData)) + { + mnCurRow = ROW_NOT_FOUND; + return ScRefCellValue(); + } + + if (aData.mbValue) + { + // This row is invisible. Skip to the last invisible row and + // try again. + mnCurRow = mnUBound = aData.mnRow2; + continue; + } + + // This row is visible. + mnUBound = aData.mnRow2; + } + + maCell = mrColumn.GetCellValue(mnCurRow); + if (!maCell.isEmpty()) + return maCell; + } + + mnCurRow = ROW_NOT_FOUND; + return ScRefCellValue(); +} + +void ScTable::SetAnonymousDBData(std::unique_ptr<ScDBData> pDBData) +{ + pDBDataNoName = std::move(pDBData); +} + +sal_uLong ScTable::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew ) +{ + if(!mpCondFormatList) + mpCondFormatList.reset(new ScConditionalFormatList()); + + sal_uInt32 nMax = mpCondFormatList->getMaxKey(); + + pNew->SetKey(nMax+1); + mpCondFormatList->InsertNew(std::move(pNew)); + + return nMax + 1; +} + +SvtScriptType ScTable::GetScriptType( SCCOL nCol, SCROW nRow ) const +{ + if ( !IsColValid( nCol ) ) + return SvtScriptType::NONE; + + return aCol[nCol].GetScriptType(nRow); +} + +void ScTable::SetScriptType( SCCOL nCol, SCROW nRow, SvtScriptType nType ) +{ + if (!ValidCol(nCol)) + return; + + aCol[nCol].SetScriptType(nRow, nType); +} + +SvtScriptType ScTable::GetRangeScriptType( + sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) +{ + if ( !IsColValid( nCol ) ) + return SvtScriptType::NONE; + + sc::CellStoreType::iterator itr = aCol[nCol].maCells.begin(); + return aCol[nCol].GetRangeScriptType(rBlockPos.miCellTextAttrPos, nRow1, nRow2, itr); +} + +formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol, SCROW nRow ) +{ + if ( !ValidCol( nCol ) || !ValidRow( nRow ) ) + return formula::FormulaTokenRef(); + if ( nCol >= aCol.size() ) + // Return a value of 0.0 if column not exists + return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0)); + return aCol[nCol].ResolveStaticReference(nRow); +} + +formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + if (nCol2 < nCol1 || nRow2 < nRow1) + return formula::FormulaTokenRef(); + + if ( !ValidCol( nCol1 ) || !ValidCol( nCol2 ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) ) + return formula::FormulaTokenRef(); + + SCCOL nMaxCol; + if ( nCol2 >= aCol.size() ) + nMaxCol = aCol.size() - 1; + else + nMaxCol = nCol2; + + ScMatrixRef pMat(new ScMatrix(nCol2-nCol1+1, nRow2-nRow1+1, 0.0)); + for (SCCOL nCol = nCol1; nCol <= nMaxCol; ++nCol) + { + if (!aCol[nCol].ResolveStaticReference(*pMat, nCol2-nCol1, nRow1, nRow2)) + // Column contains non-static cell. Failed. + return formula::FormulaTokenRef(); + } + + return formula::FormulaTokenRef(new ScMatrixToken(pMat)); +} + +formula::VectorRefArray ScTable::FetchVectorRefArray( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) +{ + if (nRow2 < nRow1) + return formula::VectorRefArray(); + + if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) ) + return formula::VectorRefArray(); + + return aCol[nCol].FetchVectorRefArray(nRow1, nRow2); +} + +#ifdef DBG_UTIL +void ScTable::AssertNoInterpretNeeded( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) +{ + assert( nRow2 >= nRow1 ); + assert( IsColValid( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) ); + return aCol[nCol].AssertNoInterpretNeeded(nRow1, nRow2); +} +#endif + +bool ScTable::HandleRefArrayForParallelism( SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup ) +{ + if (nRow2 < nRow1) + return false; + + if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) ) + return false; + + mpHiddenCols->makeReady(); + mpHiddenRows->makeReady(); + mpFilteredCols->makeReady(); + mpFilteredRows->makeReady(); + + return aCol[nCol].HandleRefArrayForParallelism(nRow1, nRow2, mxGroup); +} + +ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow ) +{ + if ( !IsColRowValid( nCol, nRow ) ) + return ScRefCellValue(); + + return aCol[nCol].GetCellValue(nRow); +} + +ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow, sc::ColumnBlockPosition& rBlockPos ) +{ + if ( !IsColRowValid( nCol, nRow ) ) + return ScRefCellValue(); + + return aCol[nCol].GetCellValue(rBlockPos, nRow); +} + +SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow ) +{ + if ( !IsColRowValid( nCol, nRow ) ) + return nullptr; + + return aCol[nCol].GetBroadcaster(nRow); +} + +void ScTable::DeleteBroadcasters( + sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) +{ + if ( !IsColValid( nCol ) ) + return; + + aCol[nCol].DeleteBroadcasters(rBlockPos, nRow1, nRow2); +} + +void ScTable::DeleteEmptyBroadcasters() +{ + for( auto& col : aCol ) + col->DeleteEmptyBroadcasters(); +} + +void ScTable::FillMatrix( ScMatrix& rMat, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const +{ + size_t nMatCol = 0; + nCol2 = ClampToAllocatedColumns(nCol2); + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nMatCol) + aCol[nCol].FillMatrix(rMat, nMatCol, nRow1, nRow2, pPool); +} + +void ScTable::InterpretDirtyCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + nCol2 = ClampToAllocatedColumns(nCol2); + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + aCol[nCol].InterpretDirtyCells(nRow1, nRow2); +} + +bool ScTable::InterpretCellsIfNeeded( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + nCol2 = ClampToAllocatedColumns(nCol2); + bool allInterpreted = true; + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + if(!aCol[nCol].InterpretCellsIfNeeded(nRow1, nRow2)) + allInterpreted = false; + return allInterpreted; +} + +void ScTable::SetFormulaResults( SCCOL nCol, SCROW nRow, const double* pResults, size_t nLen ) +{ + if (!ValidCol(nCol)) + return; + + aCol[nCol].SetFormulaResults(nRow, pResults, nLen); +} + +void ScTable::CalculateInColumnInThread( ScInterpreterContext& rContext, + SCCOL nColStart, SCCOL nColEnd, + SCROW nRowStart, SCROW nRowEnd, + unsigned nThisThread, unsigned nThreadsTotal) +{ + if (!ValidCol(nColStart) || !ValidCol(nColEnd)) + return; + + size_t nLen = nRowEnd - nRowStart + 1; + size_t nOffset = 0; + for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol) + { + aCol[nCurrCol].CalculateInThread( rContext, nRowStart, nLen, nOffset, nThisThread, nThreadsTotal ); + nOffset += nLen; + } +} + +void ScTable::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen, + ScInterpreter* pInterpreter) +{ + assert(ValidCol(nColStart) && ValidCol(nColEnd)); + + for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol) + aCol[nCurrCol].HandleStuffAfterParallelCalculation( nRow, nLen, pInterpreter ); +} + +#if DUMP_COLUMN_STORAGE +void ScTable::DumpColumnStorage( SCCOL nCol ) const +{ + if ( !IsColValid( nCol ) ) + return; + + aCol[nCol].DumpColumnStorage(); +} +#endif + +const SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow ) const +{ + if ( !IsColRowValid( nCol, nRow ) ) + return nullptr; + + return aCol[nCol].GetBroadcaster(nRow); +} + +void ScTable::DeleteConditionalFormat( sal_uLong nIndex ) +{ + mpCondFormatList->erase(nIndex); +} + +void ScTable::SetCondFormList( ScConditionalFormatList* pNew ) +{ + mpCondFormatList.reset( pNew ); +} + +ScConditionalFormatList* ScTable::GetCondFormList() +{ + if(!mpCondFormatList) + mpCondFormatList.reset( new ScConditionalFormatList() ); + + return mpCondFormatList.get(); +} + +const ScConditionalFormatList* ScTable::GetCondFormList() const +{ + return mpCondFormatList.get(); +} + +ScColumnsRange ScTable::GetWritableColumnsRange(SCCOL nColBegin, SCCOL nColEnd) +{ + // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range + if (nColEnd < nColBegin) + return ScColumnsRange(-1, -1); + assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol()); + CreateColumnIfNotExists(nColEnd); + return GetColumnsRange(nColBegin, nColEnd); +} + +ScColumnsRange ScTable::GetAllocatedColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const +{ + if (nColBegin >= aCol.size()) + return ScColumnsRange(-1, -1); + // clamp end of range to available columns + if (nColEnd >= aCol.size()) + nColEnd = aCol.size() - 1; + return GetColumnsRange(nColBegin, nColEnd); +} + +ScColumnsRange ScTable::GetColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const +{ + // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range + if (nColEnd < nColBegin) + return ScColumnsRange(-1, -1); + assert( nColBegin >= 0 && nColBegin <= GetDoc().MaxCol()); + assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol()); + return ScColumnsRange(nColBegin, nColEnd + 1); // change inclusive end to past-end +} + +// out-of-line the cold part of the CreateColumnIfNotExists function +void ScTable::CreateColumnIfNotExistsImpl( const SCCOL nScCol ) +{ + // When doing multi-threaded load of, e.g. XLS files, we can hit this, which calls + // into SfxItemPool::Put, in parallel with other code that calls into SfxItemPool::Put, + // which is bad since that code is not thread-safe. + SolarMutexGuard aGuard; + const SCCOL aOldColSize = aCol.size(); + aCol.resize( rDocument.GetSheetLimits(), static_cast< size_t >( nScCol + 1 ) ); + for (SCCOL i = aOldColSize; i <= nScCol; i++) + aCol[i].Init( i, nTab, rDocument, false ); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |