summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/column2.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/core/data/column2.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/core/data/column2.cxx')
-rw-r--r--sc/source/core/data/column2.cxx3793
1 files changed, 3793 insertions, 0 deletions
diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx
new file mode 100644
index 000000000..b7922d740
--- /dev/null
+++ b/sc/source/core/data/column2.cxx
@@ -0,0 +1,3793 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <column.hxx>
+#include <docsh.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <editutil.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <fillinfo.hxx>
+#include <segmenttree.hxx>
+#include <docparam.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <formulagroup.hxx>
+#include <listenercontext.hxx>
+#include <mtvcellfunc.hxx>
+#include <progress.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <sortparam.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineList.hxx>
+
+#include <editeng/eeitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/editstat.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/unolingu.hxx>
+#include <editeng/justifyitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/broadcast.hxx>
+#include <vcl/outdev.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/vectortoken.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <numeric>
+
+// factor from font size to optimal cell height (text width)
+#define SC_ROT_BREAK_FACTOR 6
+
+static bool IsAmbiguousScript( SvtScriptType nScript )
+{
+ //TODO: move to a header file
+ return ( nScript != SvtScriptType::LATIN &&
+ nScript != SvtScriptType::ASIAN &&
+ nScript != SvtScriptType::COMPLEX );
+}
+
+// Data operations
+
+tools::Long ScColumn::GetNeededSize(
+ SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, const ScNeededSizeOptions& rOptions,
+ const ScPatternAttr** ppPatternChange, bool bInPrintTwips ) const
+{
+ // If bInPrintTwips is set, the size calculated should be in print twips,
+ // else it should be in pixels.
+
+ // Switch unit to MapTwip instead ? (temporarily and then revert before exit).
+ if (bInPrintTwips)
+ assert(pDev->GetMapMode().GetMapUnit() == MapUnit::MapTwip);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end() || it->type == sc::element_type_empty)
+ // Empty cell, or invalid row.
+ return 0;
+
+ tools::Long nValue = 0;
+ ScRefCellValue aCell = GetCellValue(it, aPos.second);
+ double nPPT = bWidth ? nPPTX : nPPTY;
+
+ auto conditionalScaleFunc = [bInPrintTwips](tools::Long nMeasure, double fScale) {
+ return bInPrintTwips ? nMeasure : static_cast<tools::Long>(nMeasure * fScale);
+ };
+
+ const ScPatternAttr* pPattern = rOptions.pPattern;
+ if (!pPattern)
+ pPattern = pAttrArray->GetPattern( nRow );
+
+ // merged?
+ // Do not merge in conditional formatting
+
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+
+ if ( bWidth )
+ {
+ if ( pFlag->IsHorOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetColMerge() > 1 )
+ return 0;
+ }
+ else
+ {
+ if ( pFlag->IsVerOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetRowMerge() > 1 )
+ return 0;
+ }
+
+ // conditional formatting
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = rDocument.GetCondResult( nCol, nRow, nTab );
+
+ //The pPattern may change in GetCondResult
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern;
+ }
+ // line break?
+
+ const SvxHorJustifyItem* pCondItem;
+ SvxCellHorJustify eHorJust;
+ if (pCondSet && (pCondItem = pCondSet->GetItemIfSet(ATTR_HOR_JUSTIFY)) )
+ eHorJust = pCondItem->GetValue();
+ else
+ eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
+ bool bBreak;
+ const ScLineBreakCell* pLineBreakCell;
+ if ( eHorJust == SvxCellHorJustify::Block )
+ bBreak = true;
+ else if ( pCondSet && (pLineBreakCell = pCondSet->GetItemIfSet(ATTR_LINEBREAK)) )
+ bBreak = pLineBreakCell->GetValue();
+ else
+ bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue();
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+
+ // get "cell is value" flag
+ // Must be synchronized with ScOutputData::LayoutStrings()
+ bool bCellIsValue = (aCell.meType == CELLTYPE_VALUE);
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ bCellIsValue = pFCell->IsRunning() || pFCell->IsValue();
+ }
+
+ // #i111387#, tdf#121040: disable automatic line breaks for all number formats
+ if (bBreak && bCellIsValue && (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER))
+ {
+ // If a formula cell needs to be interpreted during aCell.hasNumeric()
+ // to determine the type, the pattern may get invalidated because the
+ // result may set a number format. In which case there's also the
+ // General format not set anymore...
+ bool bMayInvalidatePattern = (aCell.meType == CELLTYPE_FORMULA);
+ const ScPatternAttr* pOldPattern = pPattern;
+ bool bNumeric = aCell.hasNumeric();
+ if (bMayInvalidatePattern)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern; // XXX caller may have to check for change!
+ }
+ if (bNumeric)
+ {
+ if (!bMayInvalidatePattern || pPattern == pOldPattern)
+ bBreak = false;
+ else
+ {
+ nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+ if (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER)
+ bBreak = false;
+ }
+ }
+ }
+
+ // get other attributes from pattern and conditional formatting
+
+ SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet );
+ bool bAsianVertical = ( eOrient == SvxCellOrientation::Stacked &&
+ pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet ).GetValue() );
+ if ( bAsianVertical )
+ bBreak = false;
+
+ if ( bWidth && bBreak ) // after determining bAsianVertical (bBreak may be reset)
+ return 0;
+
+ Degree100 nRotate(0);
+ SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD;
+ if ( eOrient == SvxCellOrientation::Standard )
+ {
+ const ScRotateValueItem* pRotateValueItem;
+ if (pCondSet &&
+ (pRotateValueItem = pCondSet->GetItemIfSet(ATTR_ROTATE_VALUE)) )
+ nRotate = pRotateValueItem->GetValue();
+ else
+ nRotate = pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue();
+ if ( nRotate )
+ {
+ const SvxRotateModeItem* pRotateModeItem;
+ if (pCondSet &&
+ (pRotateModeItem = pCondSet->GetItemIfSet(ATTR_ROTATE_MODE)) )
+ eRotMode = pRotateModeItem->GetValue();
+ else
+ eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE).GetValue();
+
+ if ( nRotate == 18000_deg100 )
+ eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
+ }
+ }
+
+ if ( eHorJust == SvxCellHorJustify::Repeat )
+ {
+ // ignore orientation/rotation if "repeat" is active
+ eOrient = SvxCellOrientation::Standard;
+ nRotate = 0_deg100;
+ bAsianVertical = false;
+ }
+
+ const SvxMarginItem* pMargin;
+ if (pCondSet &&
+ (pMargin = pCondSet->GetItemIfSet(ATTR_MARGIN)) )
+ ;
+ else
+ pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ sal_uInt16 nIndent = 0;
+ if ( eHorJust == SvxCellHorJustify::Left )
+ {
+ const ScIndentItem* pIndentItem;
+ if (pCondSet &&
+ (pIndentItem = pCondSet->GetItemIfSet(ATTR_INDENT)) )
+ nIndent = pIndentItem->GetValue();
+ else
+ nIndent = pPattern->GetItem(ATTR_INDENT).GetValue();
+ }
+
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType();
+
+ // also call SetFont for edit cells, because bGetFont may be set only once
+ // bGetFont is set also if script type changes
+ if (rOptions.bGetFont)
+ {
+ Fraction aFontZoom = ( eOrient == SvxCellOrientation::Standard ) ? rZoomX : rZoomY;
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &aFontZoom, pCondSet, nScript );
+ pDev->SetFont(aFont);
+ }
+
+ bool bAddMargin = true;
+ CellType eCellType = aCell.meType;
+
+ bool bEditEngine = (eCellType == CELLTYPE_EDIT ||
+ eOrient == SvxCellOrientation::Stacked ||
+ IsAmbiguousScript(nScript) ||
+ ((eCellType == CELLTYPE_FORMULA) && aCell.mpFormula->IsMultilineResult()));
+
+ if (!bEditEngine) // direct output
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true, rOptions.bFormula);
+
+ if (!aValStr.isEmpty())
+ {
+ // SetFont is moved up
+
+ Size aSize( pDev->GetTextWidth( aValStr ), pDev->GetTextHeight() );
+ if ( eOrient != SvxCellOrientation::Standard )
+ {
+ tools::Long nTemp = aSize.Width();
+ aSize.setWidth( aSize.Height() );
+ aSize.setHeight( nTemp );
+ }
+ else if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ // only to the right:
+ //TODO: differ on direction up/down (only Text/whole height)
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = pDev->GetFont().GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nHeight > nCmp )
+ nHeight = nCmp;
+ }
+
+ aSize = Size( nWidth, nHeight );
+ }
+ nValue = bWidth ? aSize.Width() : aSize.Height();
+
+ if ( bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if ( nIndent )
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+ }
+
+ // linebreak done ?
+
+ if ( bBreak && !bWidth )
+ {
+ // test with EditEngine the safety at 90%
+ // (due to rounding errors and because EditEngine formats partially differently)
+
+ tools::Long nDocSize = conditionalScaleFunc((rDocument.GetColWidth( nCol,nTab ) -
+ pMargin->GetLeftMargin() - pMargin->GetRightMargin() -
+ nIndent), nPPTX);
+ nDocSize = (nDocSize * 9) / 10; // for safety
+ if ( aSize.Width() > nDocSize )
+ bEditEngine = true;
+ }
+ }
+ }
+
+ if (bEditEngine)
+ {
+ // the font is not reset each time with !bEditEngine
+ vcl::Font aOldFont = pDev->GetFont();
+
+ MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY );
+
+ // save in document ?
+ std::unique_ptr<ScFieldEditEngine> pEngine = rDocument.CreateFieldEditEngine();
+
+ const bool bPrevUpdateLayout = pEngine->SetUpdateLayout( false );
+ bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER );
+ EEControlBits nCtrl = pEngine->GetControlWord();
+ if ( bTextWysiwyg )
+ nCtrl |= EEControlBits::FORMAT100;
+ else
+ nCtrl &= ~EEControlBits::FORMAT100;
+ pEngine->SetControlWord( nCtrl );
+ MapMode aOld = pDev->GetMapMode();
+ pDev->SetMapMode( aHMMMode );
+ pEngine->SetRefDevice( pDev );
+ rDocument.ApplyAsianEditSettings( *pEngine );
+ SfxItemSet aSet( pEngine->GetEmptyItemSet() );
+ if ( ScStyleSheet* pPreviewStyle = rDocument.GetPreviewCellStyle( nCol, nRow, nTab ) )
+ {
+ ScPatternAttr aPreviewPattern( *pPattern );
+ aPreviewPattern.SetStyleSheet(pPreviewStyle);
+ aPreviewPattern.FillEditItemSet( &aSet, pCondSet );
+ }
+ else
+ {
+ SfxItemSet* pFontSet = rDocument.GetPreviewFont( nCol, nRow, nTab );
+ pPattern->FillEditItemSet( &aSet, pFontSet ? pFontSet : pCondSet );
+ }
+// no longer needed, are set with the text (is faster)
+// pEngine->SetDefaults( pSet );
+
+ if ( aSet.Get(EE_PARA_HYPHENATE).GetValue() ) {
+
+ css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
+ pEngine->SetHyphenator( xXHyphenator );
+ }
+
+ Size aPaper( 1000000, 1000000 );
+ if ( eOrient==SvxCellOrientation::Stacked && !bAsianVertical )
+ aPaper.setWidth( 1 );
+ else if (bBreak)
+ {
+ double fWidthFactor = bInPrintTwips ? 1.0 : nPPTX;
+ if ( bTextWysiwyg )
+ {
+ // if text is formatted for printer, don't use PixelToLogic,
+ // to ensure the exact same paper width (and same line breaks) as in
+ // ScEditUtil::GetEditArea, used for output.
+
+ fWidthFactor = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);
+ }
+
+ // use original width for hidden columns:
+ tools::Long nDocWidth = static_cast<tools::Long>( rDocument.GetOriginalWidth(nCol,nTab) * fWidthFactor );
+ SCCOL nColMerge = pMerge->GetColMerge();
+ if (nColMerge > 1)
+ for (SCCOL nColAdd=1; nColAdd<nColMerge; nColAdd++)
+ nDocWidth += static_cast<tools::Long>( rDocument.GetColWidth(nCol+nColAdd,nTab) * fWidthFactor );
+ nDocWidth -= static_cast<tools::Long>( pMargin->GetLeftMargin() * fWidthFactor )
+ + static_cast<tools::Long>( pMargin->GetRightMargin() * fWidthFactor )
+ + 1; // output size is width-1 pixel (due to gridline)
+ if ( nIndent )
+ nDocWidth -= static_cast<tools::Long>( nIndent * fWidthFactor );
+
+ // space for AutoFilter button: 20 * nZoom/100
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ if ( pFlag->HasAutoFilter() && !bTextWysiwyg )
+ nDocWidth -= bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+
+ aPaper.setWidth( nDocWidth );
+
+ if ( !bTextWysiwyg )
+ {
+ aPaper = bInPrintTwips ?
+ o3tl::convert(aPaper, o3tl::Length::twip, o3tl::Length::mm100) :
+ pDev->PixelToLogic(aPaper, aHMMMode);
+ }
+ }
+ pEngine->SetPaperSize(aPaper);
+
+ if (aCell.meType == CELLTYPE_EDIT)
+ {
+ pEngine->SetTextNewDefaults(*aCell.mpEditText, std::move(aSet));
+ }
+ else
+ {
+ const Color* pColor;
+ OUString aString = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true,
+ rOptions.bFormula);
+
+ if (!aString.isEmpty())
+ pEngine->SetTextNewDefaults(aString, std::move(aSet));
+ else
+ pEngine->SetDefaults(std::move(aSet));
+ }
+
+ bool bEngineVertical = pEngine->IsEffectivelyVertical();
+ pEngine->SetVertical( bAsianVertical );
+ pEngine->SetUpdateLayout( bPrevUpdateLayout );
+
+ bool bEdWidth = bWidth;
+ if ( eOrient != SvxCellOrientation::Standard && eOrient != SvxCellOrientation::Stacked )
+ bEdWidth = !bEdWidth;
+ if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() );
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+ aSize = Size( nWidth, nHeight );
+
+ Size aTextSize = bInPrintTwips ?
+ o3tl::toTwips(aSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(aSize, aHMMMode);
+
+ if ( bEdWidth )
+ nValue = aTextSize.Width();
+ else
+ {
+ nValue = aTextSize.Height();
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = aOldFont.GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nValue > nCmp )
+ nValue = nCmp;
+ }
+ }
+ }
+ else if ( bEdWidth )
+ {
+ if (bBreak)
+ nValue = 0;
+ else
+ {
+ sal_uInt32 aTextSize(pEngine->CalcTextWidth());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(aTextSize, 0), aHMMMode).Width();
+ }
+ }
+ else // height
+ {
+ sal_uInt32 aTextSize(pEngine->GetTextHeight());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+
+ // With non-100% zoom and several lines or paragraphs, don't shrink below the result with FORMAT100 set
+ if ( !bTextWysiwyg && ( rZoomY.GetNumerator() != 1 || rZoomY.GetDenominator() != 1 ) &&
+ ( pEngine->GetParagraphCount() > 1 || ( bBreak && pEngine->GetLineCount(0) > 1 ) ) )
+ {
+ pEngine->SetControlWord( nCtrl | EEControlBits::FORMAT100 );
+ pEngine->QuickFormatDoc( true );
+ aTextSize = pEngine->GetTextHeight();
+ tools::Long nSecondValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+ if ( nSecondValue > nValue )
+ nValue = nSecondValue;
+ }
+ }
+
+ if ( nValue && bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if (nIndent)
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ {
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+
+ if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ {
+ // add 1pt extra (default margin value) for line breaks with SetVertical
+ constexpr tools::Long nDefaultMarginInPoints = 1;
+ nValue += conditionalScaleFunc(
+ o3tl::convert(nDefaultMarginInPoints, o3tl::Length::pt, o3tl::Length::twip),
+ nPPT);
+ }
+ }
+ }
+
+ // EditEngine is cached and re-used, so the old vertical flag must be restored
+ pEngine->SetVertical( bEngineVertical );
+
+ rDocument.DisposeFieldEditEngine(pEngine);
+
+ pDev->SetMapMode( aOld );
+ pDev->SetFont( aOldFont );
+ }
+
+ if (bWidth)
+ {
+ // place for Autofilter Button
+ // 20 * nZoom/100
+ // Conditional formatting is not interesting here
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ ScMF nFlags = pPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
+ if (nFlags & ScMF::Auto)
+ nValue += bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+ }
+
+ return nValue;
+}
+
+namespace {
+
+class MaxStrLenFinder
+{
+ ScDocument& mrDoc;
+ sal_uInt32 mnFormat;
+ OUString maMaxLenStr;
+ sal_Int32 mnMaxLen;
+
+ // tdf#59820 - search for the longest substring in a multiline string
+ void checkLineBreak(const OUString& aStrVal)
+ {
+ sal_Int32 nFromIndex = 0;
+ sal_Int32 nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ // if there is no line break, just take the length of the entire string
+ if (nToIndex == -1)
+ {
+ mnMaxLen = aStrVal.getLength();
+ maMaxLenStr = aStrVal;
+ }
+ else
+ {
+ sal_Int32 nMaxLen = 0;
+ // search for the longest substring in the multiline string
+ while (nToIndex != -1)
+ {
+ if (nMaxLen < nToIndex - nFromIndex)
+ {
+ nMaxLen = nToIndex - nFromIndex;
+ }
+ nFromIndex = nToIndex + 1;
+ nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ }
+ // take into consideration the last part of multiline string
+ nToIndex = aStrVal.getLength() - nFromIndex;
+ if (nMaxLen < nToIndex)
+ {
+ nMaxLen = nToIndex;
+ }
+ // assign new maximum including its substring
+ if (mnMaxLen < nMaxLen)
+ {
+ mnMaxLen = nMaxLen;
+ maMaxLenStr = aStrVal.subView(nFromIndex);
+ }
+ }
+ }
+
+ void checkLength(const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ rCell, mnFormat, &pColor, *mrDoc.GetFormatTable(), mrDoc);
+
+ if (aValStr.getLength() <= mnMaxLen)
+ return;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_NONE:
+ case CELLTYPE_VALUE:
+ mnMaxLen = aValStr.getLength();
+ maMaxLenStr = aValStr;
+ break;
+ case CELLTYPE_EDIT:
+ case CELLTYPE_STRING:
+ case CELLTYPE_FORMULA:
+ default:
+ checkLineBreak(aValStr);
+ }
+ }
+
+public:
+ MaxStrLenFinder(ScDocument& rDoc, sal_uInt32 nFormat) :
+ mrDoc(rDoc), mnFormat(nFormat), mnMaxLen(0) {}
+
+ void operator() (size_t /*nRow*/, double f)
+ {
+ ScRefCellValue aCell(f);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString& rSS)
+ {
+ if (rSS.getLength() > mnMaxLen)
+ {
+ checkLineBreak(rSS.getString());
+ }
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ checkLength(aCell);
+ }
+
+ const OUString& getMaxLenStr() const { return maMaxLenStr; }
+};
+
+}
+
+sal_uInt16 ScColumn::GetOptimalColWidth(
+ OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, sal_uInt16 nOldWidth, const ScMarkData* pMarkData, const ScColWidthParam* pParam) const
+{
+ if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty)
+ // All cells are empty.
+ return nOldWidth;
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ sc::SingleColumnSpanSet::SpansType aMarkedSpans;
+ if (pMarkData && (pMarkData->IsMarked() || pMarkData->IsMultiMarked()))
+ {
+ aSpanSet.scan(*pMarkData, nTab, nCol);
+ aSpanSet.getSpans(aMarkedSpans);
+ }
+ else
+ // "Select" the entire column if no selection exists.
+ aMarkedSpans.emplace_back(0, GetDoc().MaxRow());
+
+ sal_uInt16 nWidth = static_cast<sal_uInt16>(nOldWidth*nPPTX);
+ bool bFound = false;
+ ScDocument& rDocument = GetDoc();
+
+ if ( pParam && pParam->mbSimpleText )
+ { // all the same except for number format
+ SCROW nRow = 0;
+ const ScPatternAttr* pPattern = GetPattern( nRow );
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &rZoomX );
+ pDev->SetFont( aFont );
+ const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ tools::Long nMargin = static_cast<tools::Long>( pMargin->GetLeftMargin() * nPPTX ) +
+ static_cast<tools::Long>( pMargin->GetRightMargin() * nPPTX );
+
+ // Try to find the row that has the longest string, and measure the width of that string.
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
+ while ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && nRow <= 2)
+ {
+ // This is often used with CSV import or other data having a header
+ // row; if there is no specific format set try next row for actual
+ // data format.
+ // Or again in case there was a leading sep=";" row or two header
+ // rows..
+ const ScPatternAttr* pNextPattern = GetPattern( ++nRow );
+ if (pNextPattern != pPattern)
+ nFormat = pNextPattern->GetNumberFormat( pFormatter );
+ }
+ OUString aLongStr;
+ const Color* pColor;
+ if (pParam->mnMaxTextRow >= 0)
+ {
+ ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow);
+ aLongStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument);
+ }
+ else
+ {
+ // Go though all non-empty cells within selection.
+ MaxStrLenFinder aFunc(rDocument, nFormat);
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ itPos = sc::ParseAllNonEmpty(itPos, maCells, rMarkedSpan.mnRow1, rMarkedSpan.mnRow2, aFunc);
+
+ aLongStr = aFunc.getMaxLenStr();
+ }
+
+ if (!aLongStr.isEmpty())
+ {
+ nWidth = pDev->GetTextWidth(aLongStr) + static_cast<sal_uInt16>(nMargin);
+ bFound = true;
+ }
+ }
+ else
+ {
+ ScNeededSizeOptions aOptions;
+ aOptions.bFormula = bFormula;
+ const ScPatternAttr* pOldPattern = nullptr;
+
+ // Go though all non-empty cells within selection.
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ {
+ SCROW nRow1 = rMarkedSpan.mnRow1, nRow2 = rMarkedSpan.mnRow2;
+ SCROW nRow = nRow1;
+ while (nRow <= nRow2)
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos->type == sc::element_type_empty)
+ {
+ // Skip empty cells.
+ nRow += itPos->size - aPos.second;
+ continue;
+ }
+
+ for (size_t nOffset = aPos.second; nOffset < itPos->size; ++nOffset, ++nRow)
+ {
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE)
+ nScript = ScGlobal::GetDefaultScriptType();
+
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ aOptions.pPattern = pPattern;
+ aOptions.bGetFont = (pPattern != pOldPattern || nScript != SvtScriptType::NONE);
+ pOldPattern = pPattern;
+ sal_uInt16 nThis = static_cast<sal_uInt16>(GetNeededSize(
+ nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern));
+ if (nThis && (nThis > nWidth || !bFound))
+ {
+ nWidth = nThis;
+ bFound = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ nWidth += 2;
+ sal_uInt16 nTwips = static_cast<sal_uInt16>(
+ std::min(nWidth / nPPTX, std::numeric_limits<sal_uInt16>::max() / 2.0));
+ return nTwips;
+ }
+ else
+ return nOldWidth;
+}
+
+static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId )
+{
+ const SvxFontHeightItem& rFontHeight =
+ static_cast<const SvxFontHeightItem&>(rPattern.GetItem(nFontHeightId));
+
+ sal_uInt16 nHeight = rFontHeight.GetHeight();
+ nHeight *= 1.18;
+
+ if ( rPattern.GetItem(ATTR_FONT_EMPHASISMARK).GetEmphasisMark() != FontEmphasisMark::NONE )
+ {
+ // add height for emphasis marks
+ //TODO: font metrics should be used instead
+ nHeight += nHeight / 4;
+ }
+
+ const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
+
+ nHeight += rMargin.GetTopMargin() + rMargin.GetBottomMargin();
+
+ if (nHeight > STD_ROWHEIGHT_DIFF)
+ nHeight -= STD_ROWHEIGHT_DIFF;
+
+ if (nHeight < ScGlobal::nStdRowHeight)
+ nHeight = ScGlobal::nStdRowHeight;
+
+ return nHeight;
+}
+
+// pHeight in Twips
+// optimize nMinHeight, nMinStart : with nRow >= nMinStart is at least nMinHeight
+// (is only evaluated with bStdAllowed)
+
+void ScColumn::GetOptimalHeight(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, sal_uInt16 nMinHeight, SCROW nMinStart )
+{
+ ScDocument& rDocument = GetDoc();
+ RowHeightsArray& rHeights = rCxt.getHeightArray();
+ ScAttrIterator aIter( pAttrArray.get(), nStartRow, nEndRow, rDocument.GetDefPattern() );
+
+ SCROW nStart = -1;
+ SCROW nEnd = -1;
+ SCROW nEditPos = 0;
+ SCROW nNextEnd = 0;
+
+ // with conditional formatting, always consider the individual cells
+
+ const ScPatternAttr* pPattern = aIter.Next(nStart,nEnd);
+ while ( pPattern )
+ {
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+ if ( pMerge->GetRowMerge() > 1 || pFlag->IsOverlapped() )
+ {
+ // do nothing - vertically with merged and overlapping,
+ // horizontally only with overlapped (invisible) -
+ // only one horizontal merged is always considered
+ }
+ else
+ {
+ bool bStdAllowed = (pPattern->GetCellOrientation() == SvxCellOrientation::Standard);
+ bool bStdOnly = false;
+ if (bStdAllowed)
+ {
+ bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() ||
+ (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() ==
+ SvxCellHorJustify::Block);
+ bStdOnly = !bBreak;
+
+ // conditional formatting: loop all cells
+ if (bStdOnly &&
+ !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ bStdOnly = false;
+ }
+
+ // rotated text: loop all cells
+ if ( bStdOnly && pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue() )
+ bStdOnly = false;
+ }
+
+ if (bStdOnly)
+ {
+ bool bHasEditCells = HasEditCells(nStart,nEnd,nEditPos);
+ // Call to HasEditCells() may change pattern due to
+ // calculation, => sync always.
+ // We don't know which row changed first, but as pPattern
+ // covered nStart to nEnd we can pick nStart. Worst case we
+ // have to repeat that for every row in range if every row
+ // changed.
+ pPattern = aIter.Resync( nStart, nStart, nEnd);
+ if (bHasEditCells && nEnd < nEditPos)
+ bHasEditCells = false; // run into that again
+ if (bHasEditCells) // includes mixed script types
+ {
+ if (nEditPos == nStart)
+ {
+ bStdOnly = false;
+ if (nEnd > nEditPos)
+ nNextEnd = nEnd;
+ nEnd = nEditPos; // calculate single
+ bStdAllowed = false; // will be computed in any case per cell
+ }
+ else
+ {
+ nNextEnd = nEnd;
+ nEnd = nEditPos - 1; // standard - part
+ }
+ }
+ }
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStart, nEnd);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ if (bStdAllowed)
+ {
+ sal_uInt16 nLatHeight = 0;
+ sal_uInt16 nCjkHeight = 0;
+ sal_uInt16 nCtlHeight = 0;
+ sal_uInt16 nDefHeight;
+ SvtScriptType nDefScript = ScGlobal::GetDefaultScriptType();
+ if ( nDefScript == SvtScriptType::ASIAN )
+ nDefHeight = nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ else if ( nDefScript == SvtScriptType::COMPLEX )
+ nDefHeight = nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ else
+ nDefHeight = nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+
+ // if everything below is already larger, the loop doesn't have to
+ // be run again
+ SCROW nStdEnd = nEnd;
+ if ( nDefHeight <= nMinHeight && nStdEnd >= nMinStart )
+ nStdEnd = (nMinStart>0) ? nMinStart-1 : 0;
+
+ if (nStart <= nStdEnd)
+ {
+ SCROW nRow = nStart;
+ for (;;)
+ {
+ size_t nIndex;
+ SCROW nRangeEnd;
+ sal_uInt16 nRangeHeight = rHeights.GetValue(nRow, nIndex, nRangeEnd);
+ if (nRangeHeight < nDefHeight)
+ rHeights.SetValue(nRow, std::min(nRangeEnd, nStdEnd), nDefHeight);
+ nRow = nRangeEnd + 1;
+ if (nRow > nStdEnd)
+ break;
+ }
+ }
+
+ if ( bStdOnly )
+ {
+ // if cells are not handled individually below,
+ // check for cells with different script type
+ sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin();
+ sc::CellStoreType::iterator itCells = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ SvtScriptType nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells);
+ if (nScript == nDefScript)
+ continue;
+
+ if ( nScript == SvtScriptType::ASIAN )
+ {
+ if ( nCjkHeight == 0 )
+ nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ if (nCjkHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCjkHeight);
+ }
+ else if ( nScript == SvtScriptType::COMPLEX )
+ {
+ if ( nCtlHeight == 0 )
+ nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ if (nCtlHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCtlHeight);
+ }
+ else
+ {
+ if ( nLatHeight == 0 )
+ nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+ if (nLatHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nLatHeight);
+ }
+ }
+ }
+ }
+ }
+
+ if (!bStdOnly) // search covered cells
+ {
+ ScNeededSizeOptions aOptions;
+
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ // only calculate the cell height when it's used later (#37928#)
+
+ if (rCxt.isForceAutoSize() || !(rDocument.GetRowFlags(nRow, nTab) & CRFlags::ManualSize) )
+ {
+ aOptions.pPattern = pPattern;
+ const ScPatternAttr* pOldPattern = pPattern;
+ sal_uInt16 nHeight = static_cast<sal_uInt16>(
+ std::min(
+ GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(),
+ rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions,
+ &pPattern) / rCxt.getPPTY(),
+ double(std::numeric_limits<sal_uInt16>::max())));
+ if (nHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nHeight);
+ // Pattern changed due to calculation? => sync.
+ if (pPattern != pOldPattern)
+ {
+ pPattern = aIter.Resync( nRow, nStart, nEnd);
+ nNextEnd = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (nNextEnd > 0)
+ {
+ nStart = nEnd + 1;
+ nEnd = nNextEnd;
+ nNextEnd = 0;
+ }
+ else
+ pPattern = aIter.Next(nStart,nEnd);
+ }
+}
+
+bool ScColumn::GetNextSpellingCell(SCROW& nRow, bool bInSel, const ScMarkData& rData) const
+{
+ ScDocument& rDocument = GetDoc();
+ sc::CellStoreType::const_iterator it = maCells.position(nRow).first;
+ mdds::mtv::element_t eType = it->type;
+ if (!bInSel && it != maCells.end() && eType != sc::element_type_empty)
+ {
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ }
+ if (bInSel)
+ {
+ SCROW lastDataPos = GetLastDataPos();
+ for (;;)
+ {
+ nRow = rData.GetNextMarked(nCol, nRow, false);
+ if (!rDocument.ValidRow(nRow) || nRow > lastDataPos )
+ {
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+ else
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ }
+ }
+ else
+ {
+ while (GetNextDataPos(nRow))
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+}
+
+namespace {
+
+class StrEntries
+{
+ sc::CellStoreType& mrCells;
+
+protected:
+ struct StrEntry
+ {
+ SCROW mnRow;
+ OUString maStr;
+
+ StrEntry(SCROW nRow, const OUString& rStr) : mnRow(nRow), maStr(rStr) {}
+ };
+
+ std::vector<StrEntry> maStrEntries;
+ ScDocument* mpDoc;
+
+ StrEntries(sc::CellStoreType& rCells, ScDocument* pDoc) : mrCells(rCells), mpDoc(pDoc) {}
+
+public:
+ void commitStrings()
+ {
+ svl::SharedStringPool& rPool = mpDoc->GetSharedStringPool();
+ sc::CellStoreType::iterator it = mrCells.begin();
+ for (const auto& rStrEntry : maStrEntries)
+ it = mrCells.set(it, rStrEntry.mnRow, rPool.intern(rStrEntry.maStr));
+ }
+};
+
+class RemoveEditAttribsHandler : public StrEntries
+{
+ std::unique_ptr<ScFieldEditEngine> mpEngine;
+
+public:
+ RemoveEditAttribsHandler(sc::CellStoreType& rCells, ScDocument* pDoc) : StrEntries(rCells, pDoc) {}
+
+ void operator() (size_t nRow, EditTextObject*& pObj)
+ {
+ // For the test on hard formatting (ScEditAttrTester), are the defaults in the
+ // EditEngine of no importance. When the tester would later recognise the same
+ // attributes in default and hard formatting and has to remove them, the correct
+ // defaults must be set in the EditEngine for each cell.
+
+ // test for attributes
+ if (!mpEngine)
+ {
+ mpEngine.reset(new ScFieldEditEngine(mpDoc, mpDoc->GetEditPool()));
+ // EEControlBits::ONLINESPELLING if there are errors already
+ mpEngine->SetControlWord(mpEngine->GetControlWord() | EEControlBits::ONLINESPELLING);
+ mpDoc->ApplyAsianEditSettings(*mpEngine);
+ }
+ mpEngine->SetTextCurrentDefaults(*pObj);
+ sal_Int32 nParCount = mpEngine->GetParagraphCount();
+ for (sal_Int32 nPar=0; nPar<nParCount; nPar++)
+ {
+ mpEngine->RemoveCharAttribs(nPar);
+ const SfxItemSet& rOld = mpEngine->GetParaAttribs(nPar);
+ if ( rOld.Count() )
+ {
+ SfxItemSet aNew( *rOld.GetPool(), rOld.GetRanges() ); // empty
+ mpEngine->SetParaAttribs( nPar, aNew );
+ }
+ }
+ // change URL field to text (not possible otherwise, thus pType=0)
+ mpEngine->RemoveFields();
+
+ bool bSpellErrors = mpEngine->HasOnlineSpellErrors();
+ bool bNeedObject = bSpellErrors || nParCount>1; // keep errors/paragraphs
+ // ScEditAttrTester is not needed anymore, arrays are gone
+
+ if (bNeedObject) // remains edit cell
+ {
+ EEControlBits nCtrl = mpEngine->GetControlWord();
+ EEControlBits nWantBig = bSpellErrors ? EEControlBits::ALLOWBIGOBJS : EEControlBits::NONE;
+ if ( ( nCtrl & EEControlBits::ALLOWBIGOBJS ) != nWantBig )
+ mpEngine->SetControlWord( (nCtrl & ~EEControlBits::ALLOWBIGOBJS) | nWantBig );
+
+ // Overwrite the existing object.
+ delete pObj;
+ pObj = mpEngine->CreateTextObject().release();
+ }
+ else // create String
+ {
+ // Store the string replacement for later commits.
+ OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine);
+ maStrEntries.emplace_back(nRow, aText);
+ }
+ }
+};
+
+class TestTabRefAbsHandler
+{
+ SCTAB mnTab;
+ bool mbTestResult;
+public:
+ explicit TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {}
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
+ {
+ if (const_cast<ScFormulaCell*>(pCell)->TestTabRefAbs(mnTab))
+ mbTestResult = true;
+ }
+
+ bool getTestResult() const { return mbTestResult; }
+};
+
+}
+
+void ScColumn::RemoveEditAttribs( sc::ColumnBlockPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow )
+{
+ RemoveEditAttribsHandler aFunc(maCells, &GetDoc());
+
+ rBlockPos.miCellPos = sc::ProcessEditText(
+ rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc);
+
+ aFunc.commitStrings();
+}
+
+bool ScColumn::TestTabRefAbs(SCTAB nTable) const
+{
+ TestTabRefAbsHandler aFunc(nTable);
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getTestResult();
+}
+
+bool ScColumn::IsEmptyData() const
+{
+ return maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty;
+}
+
+namespace {
+
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (
+ const sc::CellStoreType::value_type& node, size_t /*nOffset*/, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += nDataSize;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::VisibleCount( SCROW nStartRow, SCROW nEndRow ) const
+{
+ CellCounter aFunc;
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+ return aFunc.getCount();
+}
+
+bool ScColumn::HasVisibleDataAt(SCROW nRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Likely invalid row number.
+ return false;
+
+ return it->type != sc::element_type_empty;
+}
+
+bool ScColumn::IsEmptyData(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+bool ScColumn::IsNotesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
+ sc::CellNoteStoreType::const_iterator it = aPos.first;
+ if (it == maCellNotes.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+SCSIZE ScColumn::GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir ) const
+{
+ // Given a range of rows, find a top or bottom empty segment.
+ switch (eDir)
+ {
+ case DIR_TOP:
+ {
+ // Determine the length of empty head segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // First row is already not empty.
+ return 0;
+
+ // length of this empty block minus the offset.
+ size_t nThisLen = it->size - aPos.second;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ case DIR_BOTTOM:
+ {
+ // Determine the length of empty tail segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nEndRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // end row is already not empty.
+ return 0;
+
+ // length of this empty block from the tip to the end row position.
+ size_t nThisLen = aPos.second + 1;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return 0;
+}
+
+SCROW ScColumn::GetFirstDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_iterator it = maCells.begin();
+ if (it->type != sc::element_type_empty)
+ return 0;
+
+ return it->size;
+}
+
+SCROW ScColumn::GetLastDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
+ if (it->type != sc::element_type_empty)
+ return GetDoc().MaxRow();
+
+ return GetDoc().MaxRow() - static_cast<SCROW>(it->size);
+}
+
+SCROW ScColumn::GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ nLastRow = std::min( nLastRow, GetDoc().MaxRow());
+
+ if (pDataAreaExtras && pDataAreaExtras->mnEndRow < nLastRow)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats && HasVisibleAttrIn(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)))
+ pDataAreaExtras->mnEndRow = nLastRow;
+ }
+
+ sc::CellStoreType::const_position_type aPos = maCells.position(nLastRow);
+
+ if (aPos.first->type != sc::element_type_empty)
+ return nLastRow;
+
+ if (aPos.first == maCells.begin())
+ // This is the first block, and is empty.
+ return 0;
+
+ return static_cast<SCROW>(aPos.first->position - 1);
+}
+
+bool ScColumn::GetPrevDataPos(SCROW& rRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ if (it == maCells.begin())
+ // No more previous non-empty cell.
+ return false;
+
+ rRow -= aPos.second + 1; // Last row position of the previous block.
+ return true;
+ }
+
+ // This block is not empty.
+ if (aPos.second)
+ {
+ // There are preceding cells in this block. Simply move back one cell.
+ --rRow;
+ return true;
+ }
+
+ // This is the first cell in a non-empty block. Move back to the previous block.
+ if (it == maCells.begin())
+ // No more preceding block.
+ return false;
+
+ --rRow; // Move to the last cell of the previous block.
+ --it;
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty.
+ if (it == maCells.begin())
+ // No more preceding blocks.
+ return false;
+
+ // Skip the whole empty block segment.
+ rRow -= it->size;
+ }
+
+ return true;
+}
+
+bool ScColumn::GetNextDataPos(SCROW& rRow) const // greater than rRow
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ rRow += it->size - aPos.second;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ // Next block exists, and is non-empty.
+ return true;
+ }
+
+ if (aPos.second < it->size - 1)
+ {
+ // There are still cells following the current position.
+ ++rRow;
+ return true;
+ }
+
+ // This is the last cell in the block. Move ahead to the next block.
+ rRow += it->size - aPos.second; // First cell in the next block.
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Next block is empty. Move to the next block.
+ rRow += it->size;
+ ++it;
+ if (it == maCells.end())
+ return false;
+ }
+
+ return true;
+}
+
+bool ScColumn::TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const
+{
+ assert(rRowStart <= rRowEnd);
+ SCROW nRowStartNew = rRowStart, nRowEndNew = rRowEnd;
+
+ // Trim down rRowStart first
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRowStart);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ nRowStartNew += it->size - aPos.second;
+ if (nRowStartNew > rRowEnd)
+ return false;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+ }
+
+ // Trim up rRowEnd next
+ aPos = maCells.position(rRowEnd);
+ it = aPos.first;
+ if (it == maCells.end())
+ {
+ rRowStart = nRowStartNew;
+ return true; // Because trimming of rRowStart is ok
+ }
+
+ if (it->type == sc::element_type_empty)
+ {
+ // rRowEnd cannot be in the first block which is empty !
+ assert(it != maCells.begin());
+ // This block is empty. Skip to the previous block (it exists).
+ nRowEndNew -= aPos.second + 1; // Last row position of the previous block.
+ assert(nRowStartNew <= nRowEndNew);
+ }
+
+ rRowStart = nRowStartNew;
+ rRowEnd = nRowEndNew;
+ return true;
+}
+
+SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const
+{
+ if(bForward)
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if(bHidden)
+ return std::min<SCROW>(GetDoc().MaxRow(), nEndRow + 1);
+ else
+ return nRow;
+ }
+ else
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, &nStartRow);
+ if(bHidden)
+ return std::max<SCROW>(0, nStartRow - 1);
+ else
+ return nRow;
+ }
+}
+
+SCROW ScColumn::FindNextVisibleRowWithContent(
+ sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const
+{
+ ScDocument& rDocument = GetDoc();
+ if (bForward)
+ {
+ do
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = rDocument.RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if (bHidden)
+ {
+ nRow = nEndRow + 1;
+ if(nRow >= GetDoc().MaxRow())
+ return GetDoc().MaxRow();
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return GetDoc().MaxRow();
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the last cell of the current empty block.
+ nRow += itPos->size - aPos.second - 1;
+ }
+ while (nRow < GetDoc().MaxRow());
+
+ return GetDoc().MaxRow();
+ }
+
+ do
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = rDocument.RowHidden(nRow, nTab, &nStartRow);
+ if (bHidden)
+ {
+ nRow = nStartRow - 1;
+ if(nRow <= 0)
+ return 0;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return 0;
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the first cell of the current empty block.
+ nRow -= aPos.second;
+ }
+ while (nRow > 0);
+
+ return 0;
+}
+
+void ScColumn::CellStorageModified()
+{
+ // Remove cached values. Given how often this function is called and how (not that) often
+ // the cached values are used, it should be more efficient to just discard everything
+ // instead of trying to figure out each time exactly what to discard.
+ GetDoc().DiscardFormulaGroupContext();
+
+ // TODO: Update column's "last updated" timestamp here.
+
+ assert(sal::static_int_cast<SCROW>(maCells.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell array is incorrect." );
+
+ assert(sal::static_int_cast<SCROW>(maCellTextAttrs.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell text attribute array is incorrect.");
+
+ assert(sal::static_int_cast<SCROW>(maBroadcasters.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the broadcaster array is incorrect.");
+
+#if DEBUG_COLUMN_STORAGE
+ // Make sure that these two containers are synchronized wrt empty segments.
+ auto lIsEmptyType = [](const auto& rElement) { return rElement.type == sc::element_type_empty; };
+ // Move to the first empty blocks.
+ auto itCell = std::find_if(maCells.begin(), maCells.end(), lIsEmptyType);
+ auto itAttr = std::find_if(maCellTextAttrs.begin(), maCellTextAttrs.end(), lIsEmptyType);
+
+ while (itCell != maCells.end())
+ {
+ if (itCell->position != itAttr->position || itCell->size != itAttr->size)
+ {
+ cout << "ScColumn::CellStorageModified: Cell array and cell text attribute array are out of sync." << endl;
+ cout << "-- cell array" << endl;
+ maCells.dump_blocks(cout);
+ cout << "-- attribute array" << endl;
+ maCellTextAttrs.dump_blocks(cout);
+ cout.flush();
+ abort();
+ }
+
+ // Move to the next empty blocks.
+ ++itCell;
+ itCell = std::find_if(itCell, maCells.end(), lIsEmptyType);
+
+ ++itAttr;
+ itAttr = std::find_if(itAttr, maCellTextAttrs.end(), lIsEmptyType);
+ }
+#endif
+}
+
+#if DUMP_COLUMN_STORAGE
+
+namespace {
+
+#define DUMP_FORMULA_RESULTS 0
+
+struct ColumnStorageDumper
+{
+ const ScDocument& mrDoc;
+
+ ColumnStorageDumper( const ScDocument& rDoc ) : mrDoc(rDoc) {}
+
+ void operator() (const sc::CellStoreType::value_type& rNode) const
+ {
+ switch (rNode.type)
+ {
+ case sc::element_type_numeric:
+ cout << " * numeric block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_string:
+ cout << " * string block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_edittext:
+ cout << " * edit-text block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_formula:
+ dumpFormulaBlock(rNode);
+ break;
+ case sc::element_type_empty:
+ cout << " * empty block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ default:
+ cout << " * unknown block" << endl;
+ }
+ }
+
+ void dumpFormulaBlock(const sc::CellStoreType::value_type& rNode) const
+ {
+ cout << " * formula block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data);
+ sc::formula_block::const_iterator itEnd = sc::formula_block::end(*rNode.data);
+
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ if (!pCell->IsShared())
+ {
+ cout << " * row " << pCell->aPos.Row() << " not shared" << endl;
+ printFormula(pCell);
+ printResult(pCell);
+ continue;
+ }
+
+ if (pCell->GetSharedTopRow() != pCell->aPos.Row())
+ {
+ cout << " * row " << pCell->aPos.Row() << " shared with top row "
+ << pCell->GetSharedTopRow() << " with length " << pCell->GetSharedLength()
+ << endl;
+ continue;
+ }
+
+ SCROW nLen = pCell->GetSharedLength();
+ cout << " * group: start=" << pCell->aPos.Row() << ", length=" << nLen << endl;
+ printFormula(pCell);
+ printResult(pCell);
+
+ if (nLen > 1)
+ {
+ for (SCROW i = 0; i < nLen-1; ++i, ++it)
+ {
+ pCell = *it;
+ printResult(pCell);
+ }
+ }
+ }
+ }
+
+ void printFormula(const ScFormulaCell* pCell) const
+ {
+ sc::TokenStringContext aCxt(mrDoc, mrDoc.GetGrammar());
+ OUString aFormula = pCell->GetCode()->CreateString(aCxt, pCell->aPos);
+ cout << " * formula: " << aFormula << endl;
+ }
+
+#if DUMP_FORMULA_RESULTS
+ void printResult(const ScFormulaCell* pCell) const
+ {
+ sc::FormulaResultValue aRes = pCell->GetResult();
+ cout << " * result: ";
+ switch (aRes.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ cout << aRes.mfValue << " (type: value)";
+ break;
+ case sc::FormulaResultValue::String:
+ cout << "'" << aRes.maString.getString() << "' (type: string)";
+ break;
+ case sc::FormulaResultValue::Error:
+ cout << "error (" << static_cast<int>(aRes.mnError) << ")";
+ break;
+ case sc::FormulaResultValue::Invalid:
+ cout << "invalid";
+ break;
+ }
+
+ cout << endl;
+ }
+#else
+ void printResult(const ScFormulaCell*) const
+ {
+ (void) this; /* loplugin:staticmethods */
+ }
+#endif
+};
+
+}
+
+void ScColumn::DumpColumnStorage() const
+{
+ cout << "-- table: " << nTab << "; column: " << nCol << endl;
+ std::for_each(maCells.begin(), maCells.end(), ColumnStorageDumper(GetDoc()));
+ cout << "--" << endl;
+}
+#endif
+
+void ScColumn::CopyCellTextAttrsToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol) const
+{
+ rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2); // Empty the destination range first.
+
+ sc::CellTextAttrStoreType::const_iterator itBlk = maCellTextAttrs.begin(), itBlkEnd = maCellTextAttrs.end();
+
+ // Locate the top row position.
+ size_t nBlockStart = 0, nRowPos = static_cast<size_t>(nRow1);
+ itBlk = std::find_if(itBlk, itBlkEnd, [&nRowPos, &nBlockStart](const auto& rAttr) {
+ return nBlockStart <= nRowPos && nRowPos < nBlockStart + rAttr.size; });
+
+ if (itBlk == itBlkEnd)
+ // Specified range not found. Bail out.
+ return;
+
+ size_t nBlockEnd;
+ size_t nOffsetInBlock = nRowPos - nBlockStart;
+
+ nRowPos = static_cast<size_t>(nRow2); // End row position.
+
+ // Keep copying until we hit the end row position.
+ sc::celltextattr_block::const_iterator itData, itDataEnd;
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+ if (!itBlk->data)
+ {
+ // Empty block.
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ // This block contains the end row.
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nRowPos);
+ else
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nBlockEnd-1);
+
+ continue;
+ }
+
+ // Non-empty block.
+ itData = sc::celltextattr_block::begin(*itBlk->data);
+ itDataEnd = sc::celltextattr_block::end(*itBlk->data);
+ std::advance(itData, nOffsetInBlock);
+
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // This block contains the end row. Only copy partially.
+ size_t nOffset = nRowPos - nBlockStart + 1;
+ itDataEnd = sc::celltextattr_block::begin(*itBlk->data);
+ std::advance(itDataEnd, nOffset);
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ break;
+ }
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ }
+}
+
+namespace {
+
+class CopyCellNotesHandler
+{
+ ScColumn& mrDestCol;
+ sc::CellNoteStoreType& mrDestNotes;
+ sc::CellNoteStoreType::iterator miPos;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ SCTAB mnDestTab;
+ SCCOL mnDestCol;
+ SCROW mnDestOffset; /// Add this to the source row position to get the destination row.
+ bool mbCloneCaption;
+
+public:
+ CopyCellNotesHandler( const ScColumn& rSrcCol, ScColumn& rDestCol, SCROW nDestOffset, bool bCloneCaption ) :
+ mrDestCol(rDestCol),
+ mrDestNotes(rDestCol.GetCellNoteStore()),
+ miPos(mrDestNotes.begin()),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnDestTab(rDestCol.GetTab()),
+ mnDestCol(rDestCol.GetCol()),
+ mnDestOffset(nDestOffset),
+ mbCloneCaption(bCloneCaption) {}
+
+ void operator() ( size_t nRow, const ScPostIt* p )
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+ ScAddress aSrcPos(mnSrcCol, nRow, mnSrcTab);
+ ScAddress aDestPos(mnDestCol, nDestRow, mnDestTab);
+ ScPostIt* pNew = p->Clone(aSrcPos, mrDestCol.GetDoc(), aDestPos, mbCloneCaption).release();
+ miPos = mrDestNotes.set(miPos, nDestRow, pNew);
+ // Notify our LOK clients also
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, &mrDestCol.GetDoc(), aDestPos, pNew);
+ }
+};
+
+}
+
+void ScColumn::CopyCellNotesToDocument(
+ SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ if (IsNotesEmptyBlock(nRow1, nRow2))
+ // The column has no cell notes to copy between specified rows.
+ return;
+
+ ScDrawLayer *pDrawLayer = rDestCol.GetDoc().GetDrawLayer();
+ bool bWasLocked = bool();
+ if (pDrawLayer)
+ {
+ // Avoid O(n^2) by temporary locking SdrModel which disables broadcasting.
+ // Each cell note adds undo listener, and all of them would be woken up in ScPostIt::CreateCaption.
+ bWasLocked = pDrawLayer->isLocked();
+ pDrawLayer->setLock(true);
+ }
+ CopyCellNotesHandler aFunc(*this, rDestCol, nRowOffsetDest, bCloneCaption);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+ if (pDrawLayer)
+ pDrawLayer->setLock(bWasLocked);
+}
+
+void ScColumn::DuplicateNotes(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol, sc::ColumnBlockPosition& maDestBlockPos,
+ bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ CopyCellNotesToDocument(nStartRow, nStartRow + nDataSize -1, rDestCol, bCloneCaption, nRowOffsetDest);
+ maDestBlockPos.miCellNotePos = rDestCol.maCellNotes.begin();
+}
+
+SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow)
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+void ScColumn::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 )
+{
+ rBlockPos.miBroadcasterPos =
+ maBroadcasters.set_empty(rBlockPos.miBroadcasterPos, nRow1, nRow2);
+}
+
+void ScColumn::PrepareBroadcastersForDestruction()
+{
+ for (auto& rBroadcaster : maBroadcasters)
+ {
+ if (rBroadcaster.type == sc::element_type_broadcaster)
+ {
+ sc::broadcaster_block::iterator it = sc::broadcaster_block::begin(*rBroadcaster.data);
+ sc::broadcaster_block::iterator itEnd = sc::broadcaster_block::end(*rBroadcaster.data);
+ for (; it != itEnd; ++it)
+ (*it)->PrepareForDestruction();
+ }
+ }
+}
+
+namespace
+{
+struct BroadcasterNoListenersPredicate
+{
+ bool operator()( size_t, const SvtBroadcaster* broadcaster )
+ {
+ return !broadcaster->HasListeners();
+ }
+};
+
+}
+
+void ScColumn::DeleteEmptyBroadcasters()
+{
+ if(!mbEmptyBroadcastersPending)
+ return;
+ // Clean up after ScDocument::EnableDelayDeletingBroadcasters().
+ BroadcasterNoListenersPredicate predicate;
+ sc::SetElementsToEmpty1<sc::broadcaster_block>( maBroadcasters, predicate );
+ mbEmptyBroadcastersPending = false;
+}
+
+// Sparklines
+
+namespace
+{
+
+class DeletingSparklinesHandler
+{
+ ScDocument& m_rDocument;
+ SCTAB m_nTab;
+
+public:
+ DeletingSparklinesHandler(ScDocument& rDocument, SCTAB nTab)
+ : m_rDocument(rDocument)
+ , m_nTab(nTab)
+ {}
+
+ void operator() (size_t /*nRow*/, const sc::SparklineCell* pCell)
+ {
+ auto* pList = m_rDocument.GetSparklineList(m_nTab);
+ pList->removeSparkline(pCell->getSparkline());
+ }
+};
+
+} // end anonymous ns
+
+sc::SparklineCell* ScColumn::GetSparklineCell(SCROW nRow)
+{
+ return maSparklines.get<sc::SparklineCell*>(nRow);
+}
+
+void ScColumn::CreateSparklineCell(SCROW nRow, std::shared_ptr<sc::Sparkline> const& pSparkline)
+{
+ auto* pList = GetDoc().GetSparklineList(GetTab());
+ pList->addSparkline(pSparkline);
+ maSparklines.set(nRow, new sc::SparklineCell(pSparkline));
+}
+
+void ScColumn::DeleteSparklineCells(sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2)
+{
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunction);
+
+ rBlockPos.miSparklinePos = maSparklines.set_empty(rBlockPos.miSparklinePos, nRow1, nRow2);
+}
+
+bool ScColumn::DeleteSparkline(SCROW nRow)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow, nRow, aFunction);
+
+ maSparklines.set_empty(nRow, nRow);
+ return true;
+}
+
+bool ScColumn::IsSparklinesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::SparklineStoreType::const_iterator,size_t> aPos = maSparklines.position(nStartRow);
+ sc::SparklineStoreType::const_iterator it = aPos.first;
+ if (it == maSparklines.end())
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+namespace
+{
+
+class CopySparklinesHandler
+{
+ ScColumn& mrDestColumn;
+ sc::SparklineStoreType& mrDestSparkline;
+ sc::SparklineStoreType::iterator miDestPosition;
+ SCROW mnDestOffset;
+
+public:
+ CopySparklinesHandler(ScColumn& rDestColumn, SCROW nDestOffset)
+ : mrDestColumn(rDestColumn)
+ , mrDestSparkline(mrDestColumn.GetSparklineStore())
+ , miDestPosition(mrDestSparkline.begin())
+ , mnDestOffset(nDestOffset)
+ {}
+
+ void operator() (size_t nRow, const sc::SparklineCell* pCell)
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+
+ auto const& pSparkline = pCell->getSparkline();
+ auto const& pGroup = pCell->getSparklineGroup();
+
+ auto& rDestDoc = mrDestColumn.GetDoc();
+ auto pDestinationGroup = rDestDoc.SearchSparklineGroup(pGroup->getID());
+ if (!pDestinationGroup)
+ pDestinationGroup = std::make_shared<sc::SparklineGroup>(*pGroup); // Copy the group
+ auto pNewSparkline = std::make_shared<sc::Sparkline>(mrDestColumn.GetCol(), nDestRow, pDestinationGroup);
+ pNewSparkline->setInputRange(pSparkline->getInputRange());
+
+ auto* pList = rDestDoc.GetSparklineList(mrDestColumn.GetTab());
+ pList->addSparkline(pNewSparkline);
+
+ miDestPosition = mrDestSparkline.set(miDestPosition, nDestRow, new sc::SparklineCell(pNewSparkline));
+ }
+};
+
+}
+
+void ScColumn::CopyCellSparklinesToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, SCROW nRowOffsetDest) const
+{
+ if (IsSparklinesEmptyBlock(nRow1, nRow2))
+ // The column has no cell sparklines to copy between specified rows.
+ return;
+
+ CopySparklinesHandler aFunctor(rDestCol, nRowOffsetDest);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunctor);
+}
+
+void ScColumn::DuplicateSparklines(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol,
+ sc::ColumnBlockPosition& rDestBlockPos, SCROW nRowOffsetDest) const
+{
+ CopyCellSparklinesToDocument(nStartRow, nStartRow + nDataSize - 1, rDestCol, nRowOffsetDest);
+ rDestBlockPos.miSparklinePos = rDestCol.maSparklines.begin();
+}
+
+bool ScColumn::HasSparklines() const
+{
+ if (maSparklines.block_size() == 1 && maSparklines.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be sparklines
+}
+
+SCROW ScColumn::GetSparklinesMaxRow() const
+{
+ SCROW maxRow = 0;
+ for (const auto& rSparkline : maSparklines)
+ {
+ if (rSparkline.type == sc::element_type_sparkline)
+ maxRow = rSparkline.position + rSparkline.size - 1;
+ }
+ return maxRow;
+}
+
+SCROW ScColumn::GetSparklinesMinRow() const
+{
+ SCROW minRow = 0;
+ sc::SparklineStoreType::const_iterator it = std::find_if(maSparklines.begin(), maSparklines.end(),
+ [](const auto& rSparkline)
+ {
+ return rSparkline.type == sc::element_type_sparkline;
+ });
+ if (it != maSparklines.end())
+ minRow = it->position;
+ return minRow;
+}
+
+// Notes
+
+ScPostIt* ScColumn::GetCellNote(SCROW nRow)
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ sc::CellNoteStoreType::const_position_type aPos = maCellNotes.position(rBlockPos.miCellNotePos, nRow);
+ rBlockPos.miCellNotePos = aPos.first;
+
+ if (aPos.first->type != sc::element_type_cellnote)
+ return nullptr;
+
+ return sc::cellnote_block::at(*aPos.first->data, aPos.second);
+}
+
+ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow )
+{
+ return const_cast<ScPostIt*>(const_cast<const ScColumn*>(this)->GetCellNote( rBlockPos, nRow ));
+}
+
+void ScColumn::SetCellNote(SCROW nRow, std::unique_ptr<ScPostIt> pNote)
+{
+ //pNote->UpdateCaptionPos(ScAddress(nCol, nRow, nTab)); // TODO notes useful ? slow import with many notes
+ maCellNotes.set(nRow, pNote.release());
+}
+
+namespace {
+ class CellNoteHandler
+ {
+ const ScDocument* m_pDocument;
+ const ScAddress m_aAddress; // 'incomplete' address consisting of tab, column
+ const bool m_bForgetCaptionOwnership;
+
+ public:
+ CellNoteHandler(const ScDocument* pDocument, const ScAddress& rPos, bool bForgetCaptionOwnership) :
+ m_pDocument(pDocument),
+ m_aAddress(rPos),
+ m_bForgetCaptionOwnership(bForgetCaptionOwnership) {}
+
+ void operator() ( size_t nRow, ScPostIt* p )
+ {
+ if (m_bForgetCaptionOwnership)
+ p->ForgetCaption();
+
+ // Create a 'complete' address object
+ ScAddress aAddr(m_aAddress);
+ aAddr.SetRow(nRow);
+ // Notify our LOK clients
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Remove, m_pDocument, aAddr, p);
+ }
+ };
+} // anonymous namespace
+
+void ScColumn::CellNotesDeleting(SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership)
+{
+ ScAddress aAddr(nCol, 0, nTab);
+ CellNoteHandler aFunc(&GetDoc(), aAddr, bForgetCaptionOwnership);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DeleteCellNotes( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership )
+{
+ CellNotesDeleting(nRow1, nRow2, bForgetCaptionOwnership);
+
+ rBlockPos.miCellNotePos =
+ maCellNotes.set_empty(rBlockPos.miCellNotePos, nRow1, nRow2);
+}
+
+bool ScColumn::HasCellNotes() const
+{
+ if (maCellNotes.block_size() == 1 && maCellNotes.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be notes
+}
+
+SCROW ScColumn::GetCellNotesMaxRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW maxRow = 0;
+ for (const auto& rCellNote : maCellNotes)
+ {
+ if (rCellNote.type == sc::element_type_cellnote)
+ maxRow = rCellNote.position + rCellNote.size -1;
+ }
+ return maxRow;
+}
+SCROW ScColumn::GetCellNotesMinRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW minRow = 0;
+ sc::CellNoteStoreType::const_iterator it = std::find_if(maCellNotes.begin(), maCellNotes.end(),
+ [](const auto& rCellNote) { return rCellNote.type == sc::element_type_cellnote; });
+ if (it != maCellNotes.end())
+ minRow = it->position;
+ return minRow;
+}
+
+sal_uInt16 ScColumn::GetTextWidth(SCROW nRow) const
+{
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnTextWidth;
+}
+
+void ScColumn::SetTextWidth(SCROW nRow, sal_uInt16 nWidth)
+{
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ return;
+
+ // Set new value only when the slot is not empty.
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnTextWidth = nWidth;
+ CellStorageModified();
+}
+
+SvtScriptType ScColumn::GetScriptType( SCROW nRow ) const
+{
+ if (!GetDoc().ValidRow(nRow) || maCellTextAttrs.is_empty(nRow))
+ return SvtScriptType::NONE;
+
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnScriptType;
+}
+
+SvtScriptType ScColumn::GetRangeScriptType(
+ sc::CellTextAttrStoreType::iterator& itPos, SCROW nRow1, SCROW nRow2, const sc::CellStoreType::iterator& itrCells_ )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return SvtScriptType::NONE;
+
+ SCROW nRow = nRow1;
+ std::pair<sc::CellTextAttrStoreType::iterator,size_t> aRet =
+ maCellTextAttrs.position(itPos, nRow1);
+
+ itPos = aRet.first; // Track the position of cell text attribute array.
+ sc::CellStoreType::iterator itrCells = itrCells_;
+
+ SvtScriptType nScriptType = SvtScriptType::NONE;
+ bool bUpdated = false;
+ if (itPos->type == sc::element_type_celltextattr)
+ {
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ std::advance(it, aRet.second);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+ else
+ {
+ // Skip this whole block.
+ nRow += itPos->size - aRet.second;
+ }
+
+ while (nRow <= nRow2)
+ {
+ ++itPos;
+ if (itPos == maCellTextAttrs.end())
+ return nScriptType;
+
+ if (itPos->type != sc::element_type_celltextattr)
+ {
+ // Skip this whole block.
+ nRow += itPos->size;
+ continue;
+ }
+
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+
+ if (bUpdated)
+ CellStorageModified();
+
+ return nScriptType;
+}
+
+void ScColumn::SetScriptType( SCROW nRow, SvtScriptType nType )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ // Set new value only when the slot is already set.
+ return;
+
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnScriptType = nType;
+ CellStorageModified();
+}
+
+formula::FormulaTokenRef ScColumn::ResolveStaticReference( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row. Return a null token.
+ return formula::FormulaTokenRef();
+
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal = sc::numeric_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(fVal));
+ }
+ case sc::element_type_formula:
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ if (p->IsValue())
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(p->GetValue()));
+
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(p->GetString()));
+ }
+ case sc::element_type_string:
+ {
+ const svl::SharedString& rSS = sc::string_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(rSS));
+ }
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pText = sc::edittext_block::at(*it->data, aPos.second);
+ OUString aStr = ScEditUtil::GetString(*pText, &GetDoc());
+ svl::SharedString aSS( GetDoc().GetSharedStringPool().intern(aStr));
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(aSS));
+ }
+ case sc::element_type_empty:
+ default:
+ // Return a value of 0.0 in all the other cases.
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
+ }
+}
+
+namespace {
+
+class ToMatrixHandler
+{
+ ScMatrix& mrMat;
+ SCCOL mnMatCol;
+ SCROW mnTopRow;
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrStrPool;
+public:
+ ToMatrixHandler(ScMatrix& rMat, SCCOL nMatCol, SCROW nTopRow, ScDocument* pDoc) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrStrPool(pDoc->GetSharedStringPool()) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ mrMat.PutDouble(fVal, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ // Formula cell may need to re-calculate.
+ ScFormulaCell& rCell = const_cast<ScFormulaCell&>(*p);
+ if (rCell.IsValue())
+ mrMat.PutDouble(rCell.GetValue(), mnMatCol, nRow - mnTopRow);
+ else
+ mrMat.PutString(rCell.GetString(), mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rSS)
+ {
+ mrMat.PutString(rSS, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* pStr)
+ {
+ mrMat.PutString(mrStrPool.intern(ScEditUtil::GetString(*pStr, mpDoc)), mnMatCol, nRow - mnTopRow);
+ }
+};
+
+}
+
+bool ScColumn::ResolveStaticReference( ScMatrix& rMat, SCCOL nMatCol, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return false;
+
+ ToMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc());
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return true;
+}
+
+namespace {
+
+struct CellBucket
+{
+ SCSIZE mnEmpValStart;
+ SCSIZE mnNumValStart;
+ SCSIZE mnStrValStart;
+ SCSIZE mnEmpValCount;
+ std::vector<double> maNumVals;
+ std::vector<svl::SharedString> maStrVals;
+
+ CellBucket() : mnEmpValStart(0), mnNumValStart(0), mnStrValStart(0), mnEmpValCount(0) {}
+
+ void flush(ScMatrix& rMat, SCSIZE nCol)
+ {
+ if (mnEmpValCount)
+ {
+ rMat.PutEmptyResultVector(mnEmpValCount, nCol, mnEmpValStart);
+ reset();
+ }
+ else if (!maNumVals.empty())
+ {
+ const double* p = maNumVals.data();
+ rMat.PutDouble(p, maNumVals.size(), nCol, mnNumValStart);
+ reset();
+ }
+ else if (!maStrVals.empty())
+ {
+ const svl::SharedString* p = maStrVals.data();
+ rMat.PutString(p, maStrVals.size(), nCol, mnStrValStart);
+ reset();
+ }
+ }
+
+ void reset()
+ {
+ mnEmpValStart = mnNumValStart = mnStrValStart = 0;
+ mnEmpValCount = 0;
+ maNumVals.clear();
+ maStrVals.clear();
+ }
+};
+
+class FillMatrixHandler
+{
+ ScMatrix& mrMat;
+ size_t mnMatCol;
+ size_t mnTopRow;
+
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrPool;
+ svl::SharedStringPool* mpPool; // if matrix is not in the same document
+
+public:
+ FillMatrixHandler(ScMatrix& rMat, size_t nMatCol, size_t nTopRow, ScDocument* pDoc, svl::SharedStringPool* pPool) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrPool(pDoc->GetSharedStringPool()), mpPool(pPool) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nMatRow = node.position + nOffset - mnTopRow;
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ const double* p = &sc::numeric_block::at(*node.data, nOffset);
+ mrMat.PutDouble(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!mpPool)
+ {
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ else
+ {
+ std::vector<svl::SharedString> aStrings;
+ aStrings.reserve(nDataSize);
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ aStrings.push_back(mpPool->intern(p[i].getString()));
+ }
+ mrMat.PutString(aStrings.data(), aStrings.size(), mnMatCol, nMatRow);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ std::vector<svl::SharedString> aSSs;
+ aSSs.reserve(nDataSize);
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ OUString aStr = ScEditUtil::GetString(**it, mpDoc);
+ if (!mpPool)
+ aSSs.push_back(mrPool.intern(aStr));
+ else
+ aSSs.push_back(mpPool->intern(aStr));
+ }
+
+ const svl::SharedString* p = aSSs.data();
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ CellBucket aBucket;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPrevRow = 0, nThisRow = node.position + nOffset;
+ for (; it != itEnd; ++it, nPrevRow = nThisRow, ++nThisRow)
+ {
+ ScFormulaCell& rCell = **it;
+
+ if (rCell.IsEmpty())
+ {
+ if (aBucket.mnEmpValCount && nThisRow == nPrevRow + 1)
+ {
+ // Secondary empty results.
+ ++aBucket.mnEmpValCount;
+ }
+ else
+ {
+ // First empty result.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnEmpValStart = nThisRow - mnTopRow;
+ ++aBucket.mnEmpValCount;
+ }
+ continue;
+ }
+
+ FormulaError nErr;
+ double fVal;
+ if (rCell.GetErrorOrValue(nErr, fVal))
+ {
+ if (nErr != FormulaError::NONE)
+ fVal = CreateDoubleError(nErr);
+
+ if (!aBucket.maNumVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary numbers.
+ aBucket.maNumVals.push_back(fVal);
+ }
+ else
+ {
+ // First number.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnNumValStart = nThisRow - mnTopRow;
+ aBucket.maNumVals.push_back(fVal);
+ }
+ continue;
+ }
+
+ svl::SharedString aStr = rCell.GetString();
+ if (mpPool)
+ aStr = mpPool->intern(aStr.getString());
+ if (!aBucket.maStrVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary strings.
+ aBucket.maStrVals.push_back(aStr);
+ }
+ else
+ {
+ // First string.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnStrValStart = nThisRow - mnTopRow;
+ aBucket.maStrVals.push_back(aStr);
+ }
+ }
+
+ aBucket.flush(mrMat, mnMatCol);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+}
+
+void ScColumn::FillMatrix( ScMatrix& rMat, size_t nMatCol, SCROW nRow1, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ FillMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc(), pPool);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+}
+
+namespace {
+
+template<typename Blk>
+void getBlockIterators(
+ const sc::CellStoreType::iterator& it, size_t& rLenRemain,
+ typename Blk::iterator& rData, typename Blk::iterator& rDataEnd )
+{
+ rData = Blk::begin(*it->data);
+ if (rLenRemain >= it->size)
+ {
+ // Block is shorter than the remaining requested length.
+ rDataEnd = Blk::end(*it->data);
+ rLenRemain -= it->size;
+ }
+ else
+ {
+ rDataEnd = rData;
+ std::advance(rDataEnd, rLenRemain);
+ rLenRemain = 0;
+ }
+}
+
+bool appendToBlock(
+ ScDocument* pDoc, sc::FormulaGroupContext& rCxt, sc::FormulaGroupContext::ColArray& rColArray,
+ size_t nPos, size_t nArrayLen, const sc::CellStoreType::iterator& _it, const sc::CellStoreType::iterator& itEnd )
+{
+ svl::SharedStringPool& rPool = pDoc->GetSharedStringPool();
+ size_t nLenRemain = nArrayLen - nPos;
+
+ for (sc::CellStoreType::iterator it = _it; it != itEnd; ++it)
+ {
+ switch (it->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::string_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpStrArray)[nPos] = itData->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::edittext_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ OUString aStr = ScEditUtil::GetString(**itData, pDoc);
+ (*rColArray.mpStrArray)[nPos] = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::formula_block>(it, nLenRemain, itData, itDataEnd);
+
+ /* tdf#91416 setting progress in triggers a resize of the window
+ and so ScTabView::DoResize and an InterpretVisible and
+ InterpretDirtyCells which resets the mpFormulaGroupCxt that
+ the current rCxt points to, which is bad, so disable progress
+ during GetResult
+ */
+ ScProgress *pProgress = ScProgress::GetInterpretProgress();
+ bool bTempDisableProgress = pProgress && pProgress->Enabled();
+ if (bTempDisableProgress)
+ pProgress->Disable();
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ ScFormulaCell& rFC = **itData;
+
+ sc::FormulaResultValue aRes = rFC.GetResult();
+
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return false;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::String)
+ {
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+ (*rColArray.mpStrArray)[nPos] = aRes.maString.getData();
+ }
+ else
+ {
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+ (*rColArray.mpNumArray)[nPos] = aRes.mfValue;
+ }
+ }
+
+ if (bTempDisableProgress)
+ pProgress->Enable();
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ if (nLenRemain > it->size)
+ {
+ nPos += it->size;
+ nLenRemain -= it->size;
+ }
+ else
+ {
+ nPos = nArrayLen;
+ nLenRemain = 0;
+ }
+ }
+ break;
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::numeric_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpNumArray)[nPos] = *itData;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (!nLenRemain)
+ return true;
+ }
+
+ return false;
+}
+
+void copyFirstStringBlock(
+ ScDocument& rDoc, sc::FormulaGroupContext::StrArrayType& rArray, size_t nLen, const sc::CellStoreType::iterator& itBlk )
+{
+ sc::FormulaGroupContext::StrArrayType::iterator itArray = rArray.begin();
+
+ switch (itBlk->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator it = sc::string_block::begin(*itBlk->data);
+ sc::string_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+ for (; it != itEnd; ++it, ++itArray)
+ *itArray = it->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator it = sc::edittext_block::begin(*itBlk->data);
+ sc::edittext_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+
+ svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
+ for (; it != itEnd; ++it, ++itArray)
+ {
+ EditTextObject* pText = *it;
+ OUString aStr = ScEditUtil::GetString(*pText, &rDoc);
+ *itArray = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+sc::FormulaGroupContext::ColArray*
+copyFirstFormulaBlock(
+ sc::FormulaGroupContext& rCxt, const sc::CellStoreType::iterator& itBlk, size_t nArrayLen,
+ SCTAB nTab, SCCOL nCol )
+{
+ size_t nLen = std::min(itBlk->size, nArrayLen);
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*itBlk->data);
+ sc::formula_block::iterator itEnd;
+
+ sc::FormulaGroupContext::NumArrayType* pNumArray = nullptr;
+ sc::FormulaGroupContext::StrArrayType* pStrArray = nullptr;
+
+ itEnd = it;
+ std::advance(itEnd, nLen);
+ size_t nPos = 0;
+ for (; it != itEnd; ++it, ++nPos)
+ {
+ ScFormulaCell& rFC = **it;
+ sc::FormulaResultValue aRes = rFC.GetResult();
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return nullptr;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::Value)
+ {
+ if (!pNumArray)
+ {
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nArrayLen,
+ std::numeric_limits<double>::quiet_NaN()));
+ pNumArray = rCxt.m_NumArrays.back().get();
+ }
+
+ (*pNumArray)[nPos] = aRes.mfValue;
+ }
+ else
+ {
+ if (!pStrArray)
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nArrayLen, nullptr));
+ pStrArray = rCxt.m_StrArrays.back().get();
+ }
+
+ (*pStrArray)[nPos] = aRes.maString.getData();
+ }
+ }
+
+ if (!pNumArray && !pStrArray)
+ // At least one of these arrays should be allocated.
+ return nullptr;
+
+ return rCxt.setCachedColArray(nTab, nCol, pNumArray, pStrArray);
+}
+
+struct NonNullStringFinder
+{
+ bool operator() (const rtl_uString* p) const { return p != nullptr; }
+};
+
+bool hasNonEmpty( const sc::FormulaGroupContext::StrArrayType& rArray, SCROW nRow1, SCROW nRow2 )
+{
+ // The caller has to make sure the array is at least nRow2+1 long.
+ sc::FormulaGroupContext::StrArrayType::const_iterator it = rArray.begin();
+ std::advance(it, nRow1);
+ sc::FormulaGroupContext::StrArrayType::const_iterator itEnd = it;
+ std::advance(itEnd, nRow2-nRow1+1);
+ return std::any_of(it, itEnd, NonNullStringFinder());
+}
+
+struct ProtectFormulaGroupContext
+{
+ ProtectFormulaGroupContext( ScDocument* d )
+ : doc( d ) { doc->BlockFormulaGroupContextDiscard( true ); }
+ ~ProtectFormulaGroupContext()
+ { doc->BlockFormulaGroupContextDiscard( false ); }
+ ScDocument* doc;
+};
+
+}
+
+formula::VectorRefArray ScColumn::FetchVectorRefArray( SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // See if the requested range is already cached.
+ ScDocument& rDocument = GetDoc();
+ sc::FormulaGroupContext& rCxt = *(rDocument.GetFormulaGroupContext());
+ sc::FormulaGroupContext::ColArray* pColArray = rCxt.getCachedColArray(nTab, nCol, nRow2+1);
+ if (pColArray)
+ {
+ const double* pNum = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ // ScColumn::CellStorageModified() simply discards the entire cache (FormulaGroupContext)
+ // on any modification. However getting cell values may cause this to be called
+ // if interpreting a cell results in a change to it (not just its result though).
+ // So temporarily block the discarding.
+ ProtectFormulaGroupContext protectContext(&GetDoc());
+
+ // We need to fetch all cell values from row 0 to nRow2 for caching purposes.
+ sc::CellStoreType::iterator itBlk = maCells.begin();
+ switch (itBlk->type)
+ {
+ case sc::element_type_numeric:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block. No need to cache.
+ const double* p = &sc::numeric_block::at(*itBlk->data, nRow1);
+ return formula::VectorRefArray(p);
+ }
+
+ // Allocate a new array and copy the values to it.
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*itBlk->data);
+ sc::numeric_block::const_iterator itEnd = sc::numeric_block::end(*itBlk->data);
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(it, itEnd));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ // allocate to the requested length.
+ rArray.resize(nRow2+1, std::numeric_limits<double>::quiet_NaN());
+
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nRow2+1, nullptr));
+ sc::FormulaGroupContext::StrArrayType& rArray = *rCxt.m_StrArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, nullptr, &rArray);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray();
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block.
+ copyFirstStringBlock(rDocument, rArray, nRow2+1, itBlk);
+ return formula::VectorRefArray(&rArray[nRow1]);
+ }
+
+ copyFirstStringBlock(rDocument, rArray, itBlk->size, itBlk);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ assert(pColArray->mpStrArray);
+
+ rtl_uString** pStr = nullptr;
+ if (hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ if (pColArray->mpNumArray)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ else
+ return formula::VectorRefArray(pStr);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested length is within a single block, and the data is
+ // not cached.
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray)
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ {
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ // Fill the whole length with NaN's.
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nRow2+1,
+ std::numeric_limits<double>::quiet_NaN()));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]);
+ else
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+}
+
+#ifdef DBG_UTIL
+static void assertNoInterpretNeededHelper( const sc::CellStoreType::value_type& node,
+ size_t nOffset, size_t nDataSize )
+{
+ switch (node.type)
+ {
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ assert( !pCell->NeedsInterpret());
+ }
+ break;
+ }
+ }
+}
+void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ assert(nRow2 >= nRow1);
+ sc::ParseBlock( maCells.begin(), maCells, assertNoInterpretNeededHelper, 0, nRow2 );
+}
+#endif
+
+void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Result array is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ const double* pResEnd = pResults + nLen;
+ for (; pResults != pResEnd; ++pResults, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ FormulaError nErr = GetDoubleErrorValue(*pResults);
+ if (nErr != FormulaError::NONE)
+ rCell.SetResultError(nErr);
+ else
+ rCell.SetResultDouble(*pResults);
+ rCell.ResetDirty();
+ rCell.SetChanged(true);
+ }
+}
+
+void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, size_t nLen, size_t nOffset,
+ unsigned nThisThread, unsigned nThreadsTotal)
+{
+ assert(GetDoc().IsThreadedGroupCalcInProgress());
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ if (nThreadsTotal > 0 && ((i + nOffset) % nThreadsTotal) != nThisThread)
+ continue;
+
+ ScFormulaCell& rCell = **itCell;
+ if (!rCell.NeedsInterpret())
+ continue;
+ // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is
+ // always in a threaded calculation.
+ rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL);
+ }
+}
+
+void ScColumn::HandleStuffAfterParallelCalculation( SCROW nRow, size_t nLen, ScInterpreter* pInterpreter )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ rCell.HandleStuffAfterParallelCalculation(pInterpreter);
+ }
+}
+
+void ScColumn::SetNumberFormat( SCROW nRow, sal_uInt32 nNumberFormat )
+{
+ ApplyAttr(nRow, SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat));
+}
+
+ScFormulaCell * const * ScColumn::GetFormulaCellBlockAddress( SCROW nRow, size_t& rBlockSize ) const
+{
+ if (!GetDoc().ValidRow(nRow))
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ if (it->type != sc::element_type_formula)
+ {
+ // Not a formula cell.
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ rBlockSize = it->size;
+ return &sc::formula_block::at(*it->data, aPos.second);
+}
+
+const ScFormulaCell* ScColumn::FetchFormulaCell( SCROW nRow ) const
+{
+ size_t nBlockSize = 0;
+ ScFormulaCell const * const * pp = GetFormulaCellBlockAddress( nRow, nBlockSize );
+ return pp ? *pp : nullptr;
+}
+
+void ScColumn::FindDataAreaPos(SCROW& rRow, bool bDown) const
+{
+ // If the cell is empty, find the next non-empty cell position. If the
+ // cell is not empty, find the last non-empty cell position in the current
+ // contiguous cell block.
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row.
+ return;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Current cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, rRow, bDown);
+ return;
+ }
+
+ // Current cell is not empty.
+ SCROW nNextRow = FindNextVisibleRow(rRow, bDown);
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type == sc::element_type_empty)
+ {
+ // Next visible cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, nNextRow, bDown);
+ return;
+ }
+
+ // Next visible cell is non-empty. Find the edge that's still visible.
+ SCROW nLastRow = nNextRow;
+ do
+ {
+ nNextRow = FindNextVisibleRow(nLastRow, bDown);
+ if (nNextRow == nLastRow)
+ break;
+
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ nLastRow = nNextRow;
+ }
+ while (it->type != sc::element_type_empty);
+
+ rRow = nLastRow;
+}
+
+bool ScColumn::HasDataAt(SCROW nRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ return maCells.get_type(nRow) != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras )
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+void ScColumn::GetDataExtrasAt( SCROW nRow, ScDataAreaExtras& rDataAreaExtras ) const
+{
+ if (rDataAreaExtras.mnStartRow <= nRow && nRow <= rDataAreaExtras.mnEndRow)
+ return;
+
+ // Check in order of likeliness.
+ if ( (rDataAreaExtras.mbCellFormats && HasVisibleAttrIn(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellNotes && !IsNotesEmptyBlock(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)))
+ {
+ if (rDataAreaExtras.mnStartRow > nRow)
+ rDataAreaExtras.mnStartRow = nRow;
+ if (rDataAreaExtras.mnEndRow < nRow)
+ rDataAreaExtras.mnEndRow = nRow;
+ }
+}
+
+namespace {
+
+class FindUsedRowsHandler
+{
+ typedef mdds::flat_segment_tree<SCROW,bool> UsedRowsType;
+ UsedRowsType& mrUsed;
+ UsedRowsType::const_iterator miUsed;
+public:
+ explicit FindUsedRowsHandler(UsedRowsType& rUsed) : mrUsed(rUsed), miUsed(rUsed.begin()) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ miUsed = mrUsed.insert(miUsed, nRow1, nRow2+1, true).first;
+ }
+};
+
+}
+
+void ScColumn::FindUsed( SCROW nStartRow, SCROW nEndRow, mdds::flat_segment_tree<SCROW,bool>& rUsed ) const
+{
+ FindUsedRowsHandler aFunc(rUsed);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+}
+
+namespace {
+
+void startListening(
+ sc::BroadcasterStoreType& rStore, sc::BroadcasterStoreType::iterator& itBlockPos, size_t nElemPos,
+ SCROW nRow, SvtListener& rLst)
+{
+ switch (itBlockPos->type)
+ {
+ case sc::element_type_broadcaster:
+ {
+ // Broadcaster already exists here.
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*itBlockPos->data, nElemPos);
+ rLst.StartListening(*pBC);
+ }
+ break;
+ case mdds::mtv::element_type_empty:
+ {
+ // No broadcaster exists at this position yet.
+ SvtBroadcaster* pBC = new SvtBroadcaster;
+ rLst.StartListening(*pBC);
+ itBlockPos = rStore.set(itBlockPos, nRow, pBC); // Store the block position for next iteration.
+ }
+ break;
+ default:
+ assert(false && "wrong block type encountered in the broadcaster storage.");
+ }
+}
+
+}
+
+void ScColumn::StartListening( SvtListener& rLst, SCROW nRow )
+{
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(nRow);
+ startListening(maBroadcasters, aPos.first, aPos.second, nRow, rLst);
+}
+
+void ScColumn::EndListening( SvtListener& rLst, SCROW nRow )
+{
+ SvtBroadcaster* pBC = GetBroadcaster(nRow);
+ if (!pBC)
+ return;
+
+ rLst.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ { // There is no more listeners for this cell. Remove the broadcaster.
+ if(GetDoc().IsDelayedDeletingBroadcasters())
+ mbEmptyBroadcastersPending = true;
+ else
+ maBroadcasters.set_empty(nRow, nRow);
+ }
+}
+
+void ScColumn::StartListening( sc::StartListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rLst )
+{
+ if (!GetDoc().ValidRow(rAddress.Row()))
+ return;
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ startListening(maBroadcasters, it, aPos.second, rAddress.Row(), rLst);
+}
+
+void ScColumn::EndListening( sc::EndListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rListener )
+{
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ if (it->type != sc::element_type_broadcaster)
+ return;
+
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*it->data, aPos.second);
+ assert(pBC);
+
+ rListener.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ // There is no more listeners for this cell. Add it to the purge list for later purging.
+ rCxt.addEmptyBroadcasterPosition(rAddress.Tab(), rAddress.Col(), rAddress.Row());
+}
+
+namespace {
+
+class CompileDBFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+
+public:
+ explicit CompileDBFormulaHandler( sc::CompileFormulaContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileDBFormula(mrCxt);
+ }
+};
+
+struct CompileColRowNameFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+public:
+ explicit CompileColRowNameFormulaHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileColRowNameFormula(mrCxt);
+ }
+};
+
+}
+
+void ScColumn::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileDBFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+void ScColumn::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileColRowNameFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+namespace {
+
+class UpdateSubTotalHandler
+{
+ ScFunctionData& mrData;
+
+ void update(double fVal, bool bVal)
+ {
+ if (mrData.getError())
+ return;
+
+ switch (mrData.getFunc())
+ {
+ case SUBTOTAL_FUNC_CNT2: // everything
+ mrData.update( fVal);
+ break;
+ default: // only numeric values
+ if (bVal)
+ mrData.update( fVal);
+ }
+ }
+
+public:
+ explicit UpdateSubTotalHandler(ScFunctionData& rData) : mrData(rData) {}
+
+ void operator() (size_t /*nRow*/, double fVal)
+ {
+ update(fVal, true);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString&)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject*)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ double fVal = 0.0;
+ bool bVal = false;
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT2) // it doesn't interest us
+ {
+
+ if (pCell->GetErrCode() != FormulaError::NONE)
+ {
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT) // simply remove from count
+ mrData.setError();
+ }
+ else if (pCell->IsValue())
+ {
+ fVal = pCell->GetValue();
+ bVal = true;
+ }
+ // otherwise text
+ }
+
+ update(fVal, bVal);
+ }
+};
+
+}
+
+// multiple selections:
+void ScColumn::UpdateSelectionFunction(
+ const ScRangeList& rRanges, ScFunctionData& rData, const ScFlatBoolRowSegments& rHiddenRows )
+{
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rRanges, nTab, nCol); // mark all selected rows.
+
+ if (aSpanSet.empty())
+ return; // nothing to do, bail out
+
+ // Exclude all hidden rows.
+ ScFlatBoolRowSegments::RangeData aRange;
+ SCROW nRow = 0;
+ while (nRow <= GetDoc().MaxRow())
+ {
+ if (!rHiddenRows.getRangeData(nRow, aRange))
+ break;
+
+ if (aRange.mbValue)
+ // Hidden range detected.
+ aSpanSet.set(nRow, aRange.mnRow2, false);
+
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ switch (rData.getFunc())
+ {
+ case SUBTOTAL_FUNC_SELECTION_COUNT:
+ {
+ // Simply count selected rows regardless of cell contents.
+ for (const auto& rSpan : aSpans)
+ rData.update( rSpan.mnRow2 - rSpan.mnRow1 + 1);
+ }
+ break;
+ case SUBTOTAL_FUNC_CNT2:
+ {
+ // We need to parse all non-empty cells.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseAllNonEmpty(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ break;
+ default:
+ {
+ // We need to parse only numeric values.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseFormulaNumeric(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ }
+}
+
+namespace {
+
+class WeightedCounter
+{
+ sal_uLong mnCount;
+public:
+ WeightedCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ mnCount += getWeight(node);
+ }
+
+ static sal_uLong getWeight(const sc::CellStoreType::value_type& node)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ case sc::element_type_string:
+ return node.size;
+ case sc::element_type_formula:
+ {
+ // Each formula cell is worth its code length plus 5.
+ return std::accumulate(sc::formula_block::begin(*node.data), sc::formula_block::end(*node.data), size_t(0),
+ [](const size_t& rCount, const ScFormulaCell* p) { return rCount + 5 + p->GetCode()->GetCodeLen(); });
+ }
+ case sc::element_type_edittext:
+ // each edit-text cell is worth 50.
+ return node.size * 50;
+ default:
+ return 0;
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+class WeightedCounterWithRows
+{
+ const SCROW mnStartRow;
+ const SCROW mnEndRow;
+ sal_uLong mnCount;
+
+public:
+ WeightedCounterWithRows(SCROW nStartRow, SCROW nEndRow)
+ : mnStartRow(nStartRow)
+ , mnEndRow(nEndRow)
+ , mnCount(0)
+ {
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ const SCROW nRow1 = node.position;
+ const SCROW nRow2 = nRow1 + 1;
+
+ if ((nRow2 >= mnStartRow) && (nRow1 <= mnEndRow))
+ {
+ mnCount += WeightedCounter::getWeight(node);
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetWeightedCount() const
+{
+ const WeightedCounter aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounter());
+ return aFunc.getCount();
+}
+
+sal_uInt64 ScColumn::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const
+{
+ const WeightedCounterWithRows aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounterWithRows(nStartRow, nEndRow));
+ return aFunc.getCount();
+}
+
+namespace {
+
+class CodeCounter
+{
+ sal_uInt64 mnCount;
+public:
+ CodeCounter() : mnCount(0) {}
+
+ void operator() (size_t, const ScFormulaCell* p)
+ {
+ mnCount += p->GetCode()->GetCodeLen();
+ }
+
+ sal_uInt64 getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetCodeCount() const
+{
+ CodeCounter aFunc;
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */