/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "column.hxx" #include "scitems.hxx" #include "formulacell.hxx" #include "document.hxx" #include "docpool.hxx" #include "drwlayer.hxx" #include "attarray.hxx" #include "patattr.hxx" #include "cellform.hxx" #include "stlsheet.hxx" #include "rechead.hxx" #include "brdcst.hxx" #include "editutil.hxx" #include "subtotal.hxx" #include "markdata.hxx" #include "compiler.hxx" #include "dbdata.hxx" #include "fillinfo.hxx" #include "segmenttree.hxx" #include "docparam.hxx" #include "cellvalue.hxx" #include "tokenarray.hxx" #include "globalnames.hxx" #include "formulagroup.hxx" #include "listenercontext.hxx" #include "mtvcellfunc.hxx" #include "scmatrix.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // factor from font size to optimal cell height (text width) #define SC_ROT_BREAK_FACTOR 6 inline bool IsAmbiguousScript( sal_uInt8 nScript ) { //TODO: move to a header file return ( nScript != SCRIPTTYPE_LATIN && nScript != SCRIPTTYPE_ASIAN && nScript != SCRIPTTYPE_COMPLEX ); } // Data operations 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 ) const { std::pair 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; long nValue = 0; ScRefCellValue aCell = GetCellValue(it, aPos.second); double nPPT = bWidth ? nPPTX : nPPTY; const ScPatternAttr* pPattern = rOptions.pPattern; if (!pPattern) pPattern = pAttrArray->GetPattern( nRow ); // merged? // Do not merge in conditional formatting const ScMergeAttr* pMerge = static_cast(&pPattern->GetItem(ATTR_MERGE)); const ScMergeFlagAttr* pFlag = static_cast(&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 const SfxItemSet* pCondSet = pDocument->GetCondResult( nCol, nRow, nTab ); // line break? const SfxPoolItem* pCondItem; SvxCellHorJustify eHorJust; if (pCondSet && pCondSet->GetItemState(ATTR_HOR_JUSTIFY, true, &pCondItem) == SfxItemState::SET) eHorJust = (SvxCellHorJustify)static_cast(pCondItem)->GetValue(); else eHorJust = (SvxCellHorJustify)static_cast( pPattern->GetItem( ATTR_HOR_JUSTIFY )).GetValue(); bool bBreak; if ( eHorJust == SVX_HOR_JUSTIFY_BLOCK ) bBreak = true; else if ( pCondSet && pCondSet->GetItemState(ATTR_LINEBREAK, true, &pCondItem) == SfxItemState::SET) bBreak = static_cast(pCondItem)->GetValue(); else bBreak = static_cast(pPattern->GetItem(ATTR_LINEBREAK)).GetValue(); SvNumberFormatter* pFormatter = pDocument->GetFormatTable(); sal_uLong nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet ); // #i111387# disable automatic line breaks only for "General" number format if (bBreak && ( nFormat % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 ) { // 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 ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) bBreak = false; } } } // get other attributes from pattern and conditional formatting SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet ); bool bAsianVertical = ( eOrient == SVX_ORIENTATION_STACKED && static_cast(pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet )).GetValue() ); if ( bAsianVertical ) bBreak = false; if ( bWidth && bBreak ) // after determining bAsianVertical (bBreak may be reset) return 0; long nRotate = 0; SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD; if ( eOrient == SVX_ORIENTATION_STANDARD ) { if (pCondSet && pCondSet->GetItemState(ATTR_ROTATE_VALUE, true, &pCondItem) == SfxItemState::SET) nRotate = static_cast(pCondItem)->GetValue(); else nRotate =static_cast(pPattern->GetItem(ATTR_ROTATE_VALUE)).GetValue(); if ( nRotate ) { if (pCondSet && pCondSet->GetItemState(ATTR_ROTATE_MODE, true, &pCondItem) == SfxItemState::SET) eRotMode = (SvxRotateMode)static_cast(pCondItem)->GetValue(); else eRotMode = (SvxRotateMode)static_cast( pPattern->GetItem(ATTR_ROTATE_MODE)).GetValue(); if ( nRotate == 18000 ) eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow } } if ( eHorJust == SVX_HOR_JUSTIFY_REPEAT ) { // ignore orientation/rotation if "repeat" is active eOrient = SVX_ORIENTATION_STANDARD; nRotate = 0; bAsianVertical = false; } const SvxMarginItem* pMargin; if (pCondSet && pCondSet->GetItemState(ATTR_MARGIN, true, &pCondItem) == SfxItemState::SET) pMargin = static_cast(pCondItem); else pMargin = static_cast(&pPattern->GetItem(ATTR_MARGIN)); sal_uInt16 nIndent = 0; if ( eHorJust == SVX_HOR_JUSTIFY_LEFT ) { if (pCondSet && pCondSet->GetItemState(ATTR_INDENT, true, &pCondItem) == SfxItemState::SET) nIndent = static_cast(pCondItem)->GetValue(); else nIndent = static_cast(pPattern->GetItem(ATTR_INDENT)).GetValue(); } sal_uInt8 nScript = pDocument->GetScriptType(nCol, nRow, nTab); if (nScript == 0) 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 == SVX_ORIENTATION_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 == SVX_ORIENTATION_STACKED || IsAmbiguousScript(nScript) || ((eCellType == CELLTYPE_FORMULA) && aCell.mpFormula->IsMultilineResult())); if (!bEditEngine) // direct output { Color* pColor; OUString aValStr; ScCellFormat::GetString( aCell, nFormat, aValStr, &pColor, *pFormatter, pDocument, true, rOptions.bFormula, ftCheck); if (!aValStr.isEmpty()) { // SetFont is moved up Size aSize( pDev->GetTextWidth( aValStr ), pDev->GetTextHeight() ); if ( eOrient != SVX_ORIENTATION_STANDARD ) { long nTemp = aSize.Width(); aSize.Width() = aSize.Height(); aSize.Height() = nTemp; } else if ( nRotate ) { //TODO: take different X/Y scaling into consideration double nRealOrient = nRotate * F_PI18000; // nRotate is in 1/100 Grad double nCosAbs = fabs( cos( nRealOrient ) ); double nSinAbs = fabs( sin( nRealOrient ) ); long nHeight = (long)( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); long nWidth; if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) nWidth = (long)( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs ); else if ( rOptions.bTotalSize ) { nWidth = (long) ( pDocument->GetColWidth( nCol,nTab ) * nPPT ); bAddMargin = false; // only to the right: //TODO: differ on direction up/down (only Text/whole height) if ( pPattern->GetRotateDir( pCondSet ) == SC_ROTDIR_RIGHT ) nWidth += (long)( pDocument->GetRowHeight( nRow,nTab ) * nPPT * nCosAbs / nSinAbs ); } else nWidth = (long)( aSize.Height() / nSinAbs ); //TODO: limit? if ( bBreak && !rOptions.bTotalSize ) { // limit size for line break long nCmp = pDev->GetFont().GetSize().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 += (long) ( pMargin->GetLeftMargin() * nPPT ) + (long) ( pMargin->GetRightMargin() * nPPT ); if ( nIndent ) nValue += (long) ( nIndent * nPPT ); } else nValue += (long) ( pMargin->GetTopMargin() * nPPT ) + (long) ( 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) long nDocPixel = (long) ( ( pDocument->GetColWidth( nCol,nTab ) - pMargin->GetLeftMargin() - pMargin->GetRightMargin() - nIndent ) * nPPT ); nDocPixel = (nDocPixel * 9) / 10; // for safety if ( aSize.Width() > nDocPixel ) bEditEngine = true; } } } if (bEditEngine) { // the font is not reset each time with !bEditEngine vcl::Font aOldFont = pDev->GetFont(); MapMode aHMMMode( MAP_100TH_MM, Point(), rZoomX, rZoomY ); // save in document ? ScFieldEditEngine* pEngine = pDocument->CreateFieldEditEngine(); pEngine->SetUpdateMode( false ); bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER ); sal_uLong nCtrl = pEngine->GetControlWord(); if ( bTextWysiwyg ) nCtrl |= EE_CNTRL_FORMAT100; else nCtrl &= ~EE_CNTRL_FORMAT100; pEngine->SetControlWord( nCtrl ); MapMode aOld = pDev->GetMapMode(); pDev->SetMapMode( aHMMMode ); pEngine->SetRefDevice( pDev ); pDocument->ApplyAsianEditSettings( *pEngine ); SfxItemSet* pSet = new SfxItemSet( pEngine->GetEmptyItemSet() ); if ( ScStyleSheet* pPreviewStyle = pDocument->GetPreviewCellStyle( nCol, nRow, nTab ) ) { boost::scoped_ptr pPreviewPattern(new ScPatternAttr( *pPattern )); pPreviewPattern->SetStyleSheet(pPreviewStyle); pPreviewPattern->FillEditItemSet( pSet, pCondSet ); } else { SfxItemSet* pFontSet = pDocument->GetPreviewFont( nCol, nRow, nTab ); pPattern->FillEditItemSet( pSet, pFontSet ? pFontSet : pCondSet ); } // no longer needed, are setted with the text (is faster) // pEngine->SetDefaults( pSet ); if ( static_cast(pSet->Get(EE_PARA_HYPHENATE)).GetValue() ) { com::sun::star::uno::Reference xXHyphenator( LinguMgr::GetHyphenator() ); pEngine->SetHyphenator( xXHyphenator ); } Size aPaper = Size( 1000000, 1000000 ); if ( eOrient==SVX_ORIENTATION_STACKED && !bAsianVertical ) aPaper.Width() = 1; else if (bBreak) { double fWidthFactor = 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 = HMM_PER_TWIPS; } // use original width for hidden columns: long nDocWidth = (long) ( pDocument->GetOriginalWidth(nCol,nTab) * fWidthFactor ); SCCOL nColMerge = pMerge->GetColMerge(); if (nColMerge > 1) for (SCCOL nColAdd=1; nColAddGetColWidth(nCol+nColAdd,nTab) * fWidthFactor ); nDocWidth -= (long) ( pMargin->GetLeftMargin() * fWidthFactor ) + (long) ( pMargin->GetRightMargin() * fWidthFactor ) + 1; // output size is width-1 pixel (due to gridline) if ( nIndent ) nDocWidth -= (long) ( nIndent * fWidthFactor ); // space for AutoFilter button: 20 * nZoom/100 if ( pFlag->HasAutoFilter() && !bTextWysiwyg ) nDocWidth -= (rZoomX.GetNumerator()*20)/rZoomX.GetDenominator(); aPaper.Width() = nDocWidth; if ( !bTextWysiwyg ) aPaper = pDev->PixelToLogic( aPaper, aHMMMode ); } pEngine->SetPaperSize(aPaper); if (aCell.meType == CELLTYPE_EDIT) { pEngine->SetTextNewDefaults(*aCell.mpEditText, pSet); } else { Color* pColor; OUString aString; ScCellFormat::GetString( aCell, nFormat, aString, &pColor, *pFormatter, pDocument, true, rOptions.bFormula, ftCheck); if (!aString.isEmpty()) pEngine->SetTextNewDefaults(aString, pSet); else pEngine->SetDefaults(pSet); } bool bEngineVertical = pEngine->IsVertical(); pEngine->SetVertical( bAsianVertical ); pEngine->SetUpdateMode( true ); bool bEdWidth = bWidth; if ( eOrient != SVX_ORIENTATION_STANDARD && eOrient != SVX_ORIENTATION_STACKED ) bEdWidth = !bEdWidth; if ( nRotate ) { //TODO: take different X/Y scaling into consideration Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() ); double nRealOrient = nRotate * F_PI18000; // nRotate is in 1/100 Grad double nCosAbs = fabs( cos( nRealOrient ) ); double nSinAbs = fabs( sin( nRealOrient ) ); long nHeight = (long)( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); long nWidth; if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) nWidth = (long)( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs ); else if ( rOptions.bTotalSize ) { nWidth = (long) ( pDocument->GetColWidth( nCol,nTab ) * nPPT ); bAddMargin = false; if ( pPattern->GetRotateDir( pCondSet ) == SC_ROTDIR_RIGHT ) nWidth += (long)( pDocument->GetRowHeight( nRow,nTab ) * nPPT * nCosAbs / nSinAbs ); } else nWidth = (long)( aSize.Height() / nSinAbs ); //TODO: limit? aSize = Size( nWidth, nHeight ); Size aPixSize = pDev->LogicToPixel( aSize, aHMMMode ); if ( bEdWidth ) nValue = aPixSize.Width(); else { nValue = aPixSize.Height(); if ( bBreak && !rOptions.bTotalSize ) { // limit size for line break long nCmp = aOldFont.GetSize().Height() * SC_ROT_BREAK_FACTOR; if ( nValue > nCmp ) nValue = nCmp; } } } else if ( bEdWidth ) { if (bBreak) nValue = 0; else nValue = pDev->LogicToPixel(Size( pEngine->CalcTextWidth(), 0 ), aHMMMode).Width(); } else // height { nValue = pDev->LogicToPixel(Size( 0, pEngine->GetTextHeight() ), 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 | EE_CNTRL_FORMAT100 ); pEngine->QuickFormatDoc( true ); long nSecondValue = pDev->LogicToPixel(Size( 0, pEngine->GetTextHeight() ), aHMMMode).Height(); if ( nSecondValue > nValue ) nValue = nSecondValue; } } if ( nValue && bAddMargin ) { if (bWidth) { nValue += (long) ( pMargin->GetLeftMargin() * nPPT ) + (long) ( pMargin->GetRightMargin() * nPPT ); if (nIndent) nValue += (long) ( nIndent * nPPT ); } else { nValue += (long) ( pMargin->GetTopMargin() * nPPT ) + (long) ( pMargin->GetBottomMargin() * nPPT ); if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER ) { // add 1pt extra (default margin value) for line breaks with SetVertical nValue += (long) ( 20 * nPPT ); } } } // EditEngine is cached and re-used, so the old vertical flag must be restored pEngine->SetVertical( bEngineVertical ); pDocument->DisposeFieldEditEngine(pEngine); pDev->SetMapMode( aOld ); pDev->SetFont( aOldFont ); } if (bWidth) { // place for Autofilter Button // 20 * nZoom/100 // Conditional formatting is not interesting here sal_Int16 nFlags = static_cast(pPattern->GetItem(ATTR_MERGE_FLAG)).GetValue(); if (nFlags & SC_MF_AUTO) nValue += (rZoomX.GetNumerator()*20)/rZoomX.GetDenominator(); } return nValue; } namespace { class MaxStrLenFinder { ScDocument& mrDoc; sal_uInt32 mnFormat; OUString maMaxLenStr; sal_Int32 mnMaxLen; void checkLength(ScRefCellValue& rCell) { Color* pColor; OUString aValStr; ScCellFormat::GetString( rCell, mnFormat, aValStr, &pColor, *mrDoc.GetFormatTable(), &mrDoc, true, false, ftCheck); if (aValStr.getLength() > mnMaxLen) { mnMaxLen = aValStr.getLength(); maMaxLenStr = 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) { mnMaxLen = rSS.getLength(); maMaxLenStr = 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(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; 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.push_back(sc::RowSpan(0, MAXROW)); sal_uInt16 nWidth = static_cast(nOldWidth*nPPTX); bool bFound = false; if ( pParam && pParam->mbSimpleText ) { // all the same except for number format const ScPatternAttr* pPattern = GetPattern( 0 ); vcl::Font aFont; // font color doesn't matter here pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &rZoomX, NULL ); pDev->SetFont( aFont ); const SvxMarginItem* pMargin = static_cast(&pPattern->GetItem(ATTR_MARGIN)); long nMargin = (long) ( pMargin->GetLeftMargin() * nPPTX ) + (long) ( pMargin->GetRightMargin() * nPPTX ); // Try to find the row that has the longest string, and measure the width of that string. SvNumberFormatter* pFormatter = pDocument->GetFormatTable(); sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter ); OUString aLongStr; Color* pColor; if (pParam->mnMaxTextRow >= 0) { ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow); ScCellFormat::GetString( aCell, nFormat, aLongStr, &pColor, *pFormatter, pDocument, true, false, ftCheck); } else { // Go though all non-empty cells within selection. MaxStrLenFinder aFunc(*pDocument, nFormat); sc::CellStoreType::const_iterator itPos = maCells.begin(); sc::SingleColumnSpanSet::SpansType::const_iterator it = aMarkedSpans.begin(), itEnd = aMarkedSpans.end(); for (; it != itEnd; ++it) itPos = sc::ParseAllNonEmpty(itPos, maCells, it->mnRow1, it->mnRow2, aFunc); aLongStr = aFunc.getMaxLenStr(); } if (!aLongStr.isEmpty()) { nWidth = pDev->GetTextWidth(aLongStr) + static_cast(nMargin); bFound = true; } } else { ScNeededSizeOptions aOptions; aOptions.bFormula = bFormula; const ScPatternAttr* pOldPattern = NULL; sal_uInt8 nOldScript = 0; // Go though all non-empty cells within selection. sc::CellStoreType::const_iterator itPos = maCells.begin(); sc::SingleColumnSpanSet::SpansType::const_iterator it = aMarkedSpans.begin(), itEnd = aMarkedSpans.end(); for (; it != itEnd; ++it) { SCROW nRow1 = it->mnRow1, nRow2 = it->mnRow2; SCROW nRow = nRow1; while (nRow <= nRow2) { std::pair 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) { sal_uInt8 nScript = pDocument->GetScriptType(nCol, nRow, nTab); if (nScript == 0) nScript = ScGlobal::GetDefaultScriptType(); const ScPatternAttr* pPattern = GetPattern(nRow); aOptions.pPattern = pPattern; aOptions.bGetFont = (pPattern != pOldPattern || nScript != nOldScript); pOldPattern = pPattern; sal_uInt16 nThis = (sal_uInt16) GetNeededSize( nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern); if (nThis) { if (nThis > nWidth || !bFound) { nWidth = nThis; bFound = true; } } } } } } if (bFound) { nWidth += 2; sal_uInt16 nTwips = (sal_uInt16) (nWidth / nPPTX); return nTwips; } else return nOldWidth; } static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId ) { const SvxFontHeightItem& rFontHeight = static_cast(rPattern.GetItem(nFontHeightId)); sal_uInt16 nHeight = rFontHeight.GetHeight(); nHeight *= 1.18; if ( static_cast(rPattern. GetItem(ATTR_FONT_EMPHASISMARK)).GetEmphasisMark() != EMPHASISMARK_NONE ) { // add height for emphasis marks //TODO: font metrics should be used instead nHeight += nHeight / 4; } const SvxMarginItem& rMargin = static_cast(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 ) { std::vector& rHeights = rCxt.getHeightArray(); ScAttrIterator aIter( pAttrArray, nStartRow, nEndRow ); 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 = static_cast(&pPattern->GetItem(ATTR_MERGE)); const ScMergeFlagAttr* pFlag = static_cast(&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() == SVX_ORIENTATION_STANDARD); bool bStdOnly = false; if (bStdAllowed) { bool bBreak = static_cast(pPattern->GetItem(ATTR_LINEBREAK)).GetValue() || ((SvxCellHorJustify)static_cast(pPattern-> GetItem( ATTR_HOR_JUSTIFY )).GetValue() == SVX_HOR_JUSTIFY_BLOCK); bStdOnly = !bBreak; // conditional formatting: loop all cells if (bStdOnly && !static_cast(pPattern->GetItem( ATTR_CONDITIONAL)).GetCondFormatData().empty()) { bStdOnly = false; } // rotated text: loop all cells if ( bStdOnly && static_cast(pPattern-> GetItem(ATTR_ROTATE_VALUE)).GetValue() ) bStdOnly = false; } if (bStdOnly) if (HasEditCells(nStart,nEnd,nEditPos)) // 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; 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; sal_uInt8 nDefScript = ScGlobal::GetDefaultScriptType(); if ( nDefScript == SCRIPTTYPE_ASIAN ) nDefHeight = nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT ); else if ( nDefScript == SCRIPTTYPE_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; for (SCROW nRow = nStart; nRow <= nStdEnd; ++nRow) if (nDefHeight > rHeights[nRow-nStartRow]) rHeights[nRow-nStartRow] = nDefHeight; if ( bStdOnly ) { // if cells are not handled individually below, // check for cells with different script type sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin(); sc::SingleColumnSpanSet::SpansType::const_iterator it = aSpans.begin(), itEnd = aSpans.end(); sc::CellStoreType::iterator itCells = maCells.begin(); for (; it != itEnd; ++it) { for (SCROW nRow = it->mnRow1; nRow <= it->mnRow2; ++nRow) { sal_uInt8 nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells); if (nScript == nDefScript) continue; if ( nScript == SCRIPTTYPE_ASIAN ) { if ( nCjkHeight == 0 ) nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT ); if (nCjkHeight > rHeights[nRow-nStartRow]) rHeights[nRow-nStartRow] = nCjkHeight; } else if ( nScript == SCRIPTTYPE_COMPLEX ) { if ( nCtlHeight == 0 ) nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT ); if (nCtlHeight > rHeights[nRow-nStartRow]) rHeights[nRow-nStartRow] = nCtlHeight; } else { if ( nLatHeight == 0 ) nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT ); if (nLatHeight > rHeights[nRow-nStartRow]) rHeights[nRow-nStartRow] = nLatHeight; } } } } } if (!bStdOnly) // search covered cells { ScNeededSizeOptions aOptions; sc::SingleColumnSpanSet::SpansType::const_iterator it = aSpans.begin(), itEnd = aSpans.end(); for (; it != itEnd; ++it) { for (SCROW nRow = it->mnRow1; nRow <= it->mnRow2; ++nRow) { // only calculate the cell height when it's used later (#37928#) if (rCxt.isForceAutoSize() || !(pDocument->GetRowFlags(nRow, nTab) & CR_MANUALSIZE) ) { aOptions.pPattern = pPattern; const ScPatternAttr* pOldPattern = pPattern; sal_uInt16 nHeight = (sal_uInt16) ( GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(), rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions, &pPattern) / rCxt.getPPTY() ); if (nHeight > rHeights[nRow-nStartRow]) rHeights[nRow-nStartRow] = 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 { bool bStop = false; 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, HASATTR_PROTECTED) && pDocument->IsTabProtected(nTab)) ) return true; } while (!bStop) { if (bInSel) { nRow = rData.GetNextMarked(nCol, nRow, false); if (!ValidRow(nRow)) { nRow = MAXROW+1; bStop = true; } else { it = maCells.position(it, nRow).first; eType = it->type; if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) && !(HasAttrib( nRow, nRow, HASATTR_PROTECTED) && pDocument->IsTabProtected(nTab)) ) return true; else nRow++; } } else if (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, HASATTR_PROTECTED) && pDocument->IsTabProtected(nTab)) ) return true; else nRow++; } else { nRow = MAXROW+1; bStop = true; } } 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 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(); std::vector::iterator itStr = maStrEntries.begin(), itStrEnd = maStrEntries.end(); for (; itStr != itStrEnd; ++itStr) it = mrCells.set(it, itStr->mnRow, rPool.intern(itStr->maStr)); } }; class RemoveEditAttribsHandler : public StrEntries { boost::scoped_ptr 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())); // EE_CNTRL_ONLINESPELLING if there are errors already mpEngine->SetControlWord(mpEngine->GetControlWord() | EE_CNTRL_ONLINESPELLING); mpDoc->ApplyAsianEditSettings(*mpEngine); } mpEngine->SetText(*pObj); sal_Int32 nParCount = mpEngine->GetParagraphCount(); for (sal_Int32 nPar=0; nParRemoveCharAttribs(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(true); 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 { sal_uInt32 nCtrl = mpEngine->GetControlWord(); sal_uInt32 nWantBig = bSpellErrors ? EE_CNTRL_ALLOWBIGOBJS : 0; if ( ( nCtrl & EE_CNTRL_ALLOWBIGOBJS ) != nWantBig ) mpEngine->SetControlWord( (nCtrl & ~EE_CNTRL_ALLOWBIGOBJS) | nWantBig ); // Overwrite the existing object. delete pObj; pObj = mpEngine->CreateTextObject(); } else // create String { // Store the string replacement for later commits. OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine); maStrEntries.push_back(StrEntry(nRow, aText)); } } }; class TestTabRefAbsHandler { SCTAB mnTab; bool mbTestResult; public: TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {} void operator() (size_t /*nRow*/, const ScFormulaCell* pCell) { if (const_cast(pCell)->TestTabRefAbs(mnTab)) mbTestResult = true; } bool getTestResult() const { return mbTestResult; } }; } void ScColumn::RemoveEditAttribs( SCROW nStartRow, SCROW nEndRow ) { RemoveEditAttribsHandler aFunc(maCells, pDocument); sc::ProcessEditText(maCells.begin(), 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 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::IsEmptyAttr() const { if (pAttrArray) return pAttrArray->IsEmpty(); else return true; } bool ScColumn::IsEmpty() const { return (IsEmptyData() && IsEmptyAttr()); } bool ScColumn::IsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const { std::pair 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 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. Skip the start row. switch (eDir) { case DIR_TOP: { // Determine the length of empty head segment. size_t nLength = nEndRow - nStartRow; std::pair 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; std::pair 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 MAXROW; return MAXROW - static_cast(it->size); } SCROW ScColumn::GetLastDataPos( SCROW nLastRow ) const { 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(aPos.first->position - 1); } bool ScColumn::GetPrevDataPos(SCROW& rRow) const { std::pair 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 an 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 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; } SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const { if(bForward) { nRow++; SCROW nEndRow = 0; bool bHidden = pDocument->RowHidden(nRow, nTab, NULL, &nEndRow); if(bHidden) return std::min(MAXROW, nEndRow + 1); else return nRow; } else { nRow--; SCROW nStartRow = MAXROW; bool bHidden = pDocument->RowHidden(nRow, nTab, &nStartRow, NULL); if(bHidden) return std::max(0, nStartRow - 1); else return nRow; } } SCROW ScColumn::FindNextVisibleRowWithContent( sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const { if (bForward) { do { nRow++; SCROW nEndRow = 0; bool bHidden = pDocument->RowHidden(nRow, nTab, NULL, &nEndRow); if (bHidden) { nRow = nEndRow + 1; if(nRow >= MAXROW) return MAXROW; } std::pair aPos = maCells.position(itPos, nRow); itPos = aPos.first; if (itPos == maCells.end()) // Invalid row. return 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 < MAXROW); return MAXROW; } do { nRow--; SCROW nStartRow = MAXROW; bool bHidden = pDocument->RowHidden(nRow, nTab, &nStartRow, NULL); if (bHidden) { nRow = nStartRow - 1; if(nRow <= 0) return 0; } std::pair 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() { // TODO: Update column's "last updated" timestamp here. mbDirtyGroups = true; #if DEBUG_COLUMN_STORAGE if (maCells.size() != MAXROWCOUNT) { cout << "ScColumn::CellStorageModified: Size of the cell array is incorrect." << endl; cout.flush(); abort(); } if (maCellTextAttrs.size() != MAXROWCOUNT) { cout << "ScColumn::CellStorageModified: Size of the cell text attribute array is incorrect." << endl; cout.flush(); abort(); } if (maBroadcasters.size() != MAXROWCOUNT) { cout << "ScColumn::CellStorageModified: Size of the broadcaster array is incorrect." << endl; cout.flush(); abort(); } // Make sure that these two containers are synchronized wrt empty segments. sc::CellStoreType::const_iterator itCell = maCells.begin(); sc::CellTextAttrStoreType::const_iterator itAttr = maCellTextAttrs.begin(); // Move to the first empty blocks. while (itCell != maCells.end() && itCell->type != sc::element_type_empty) ++itCell; while (itAttr != maCellTextAttrs.end() && itAttr->type != sc::element_type_empty) ++itAttr; 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; while (itCell != maCells.end() && itCell->type != sc::element_type_empty) ++itCell; ++itAttr; while (itAttr != maCellTextAttrs.end() && itAttr->type != sc::element_type_empty) ++itAttr; } #endif } #if DEBUG_COLUMN_STORAGE namespace { struct FormulaGroupDumper : std::unary_function { void operator() (const sc::CellStoreType::value_type& rNode) const { if (rNode.type != sc::element_type_formula) return; cout << " -- formula block" << 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& rCell = **it; if (!rCell.IsShared()) { cout << " + row " << rCell.aPos.Row() << " not shared" << endl; continue; } if (rCell.GetSharedTopRow() != rCell.aPos.Row()) { cout << " + row " << rCell.aPos.Row() << " shared with top row " << rCell.GetSharedTopRow() << " with length " << rCell.GetSharedLength() << endl; continue; } SCROW nLen = rCell.GetSharedLength(); cout << " * group: start=" << rCell.aPos.Row() << ", length=" << nLen << endl; std::advance(it, nLen-1); } } }; } void ScColumn::DumpFormulaGroups() const { cout << "-- formua groups" << endl; std::for_each(maCells.begin(), maCells.end(), FormulaGroupDumper()); 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 nOffsetInBlock = 0; size_t nBlockStart = 0, nBlockEnd = 0, nRowPos = static_cast(nRow1); for (; itBlk != itBlkEnd; ++itBlk) { nBlockEnd = nBlockStart + itBlk->size; if (nBlockStart <= nRowPos && nRowPos < nBlockEnd) { // Found. nOffsetInBlock = nRowPos - nBlockStart; break; } } if (itBlk == itBlkEnd) // Specified range not found. Bail out. return; nRowPos = static_cast(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); miPos = mrDestNotes.set(miPos, nDestRow, p->Clone(aSrcPos, mrDestCol.GetDoc(), aDestPos, mbCloneCaption)); } }; } 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(nRow); } const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const { return maBroadcasters.get(nRow); } const SvtBroadcaster* ScColumn::GetBroadcaster( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const { sc::BroadcasterStoreType::const_position_type aPos = maBroadcasters.position(rBlockPos.miBroadcasterPos, nRow); rBlockPos.miBroadcasterPos = aPos.first; if (aPos.first->type != sc::element_type_broadcaster) return NULL; return sc::broadcaster_block::at(*aPos.first->data, aPos.second); } void ScColumn::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 ) { rBlockPos.miBroadcasterPos = maBroadcasters.set_empty(rBlockPos.miBroadcasterPos, nRow1, nRow2); } void ScColumn::PrepareBroadcastersForDestruction() { sc::BroadcasterStoreType::iterator itPos = maBroadcasters.begin(), itPosEnd = maBroadcasters.end(); for (; itPos != itPosEnd; ++itPos) { if (itPos->type == sc::element_type_broadcaster) { sc::broadcaster_block::iterator it = sc::broadcaster_block::begin(*itPos->data); sc::broadcaster_block::iterator itEnd = sc::broadcaster_block::end(*itPos->data); for (; it != itEnd; ++it) (*it)->PrepareForDestruction(); } } } bool ScColumn::HasBroadcaster() const { sc::BroadcasterStoreType::const_iterator it = maBroadcasters.begin(), itEnd = maBroadcasters.end(); for (; it != itEnd; ++it) { if (it->type == sc::element_type_broadcaster) // Having a broadcaster block automatically means there is at least one broadcaster. return true; } return false; } ScPostIt* ScColumn::GetCellNote(SCROW nRow) { return maCellNotes.get(nRow); } const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const { return maCellNotes.get(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 NULL; return sc::cellnote_block::at(*aPos.first->data, aPos.second); } void ScColumn::SetCellNote(SCROW nRow, ScPostIt* pNote) { //pNote->UpdateCaptionPos(ScAddress(nCol, nRow, nTab)); // TODO notes useful ? slow import with many notes maCellNotes.set(nRow, pNote); } void ScColumn::DeleteCellNotes( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 ) { rBlockPos.miCellNotePos = maCellNotes.set_empty(rBlockPos.miCellNotePos, nRow1, nRow2); } bool ScColumn::HasCellNotes() const { sc::CellNoteStoreType::const_iterator it = maCellNotes.begin(), itEnd = maCellNotes.end(); for (; it != itEnd; ++it) { if (it->type == sc::element_type_cellnote) // Having a cellnote block automatically means there is at least one cell note. return true; } return false; } SCROW ScColumn::GetCellNotesMaxRow() const { // hypothesis : the column has cell notes (should be checked before) SCROW maxRow = 0; sc::CellNoteStoreType::const_iterator it = maCellNotes.begin(), itEnd = maCellNotes.end(); for (; it != itEnd; ++it) { if (it->type == sc::element_type_cellnote) maxRow = it->position + it->size -1; } return maxRow; } SCROW ScColumn::GetCellNotesMinRow() const { // hypothesis : the column has cell notes (should be checked before) SCROW minRow = 0; bool bFound = false; sc::CellNoteStoreType::const_iterator it = maCellNotes.begin(), itEnd = maCellNotes.end(); for (; it != itEnd && !bFound; ++it) { if (it->type == sc::element_type_cellnote) { bFound = true; minRow = it->position; } } return minRow; } sal_uInt16 ScColumn::GetTextWidth(SCROW nRow) const { return maCellTextAttrs.get(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(); } sal_uInt8 ScColumn::GetScriptType( SCROW nRow ) const { if (!ValidRow(nRow) || maCellTextAttrs.is_empty(nRow)) return 0; return maCellTextAttrs.get(nRow).mnScriptType; } sal_uInt8 ScColumn::GetRangeScriptType( sc::CellTextAttrStoreType::iterator& itPos, SCROW nRow1, SCROW nRow2, const sc::CellStoreType::iterator& itrCells ) { if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2) return 0; SCROW nRow = nRow1; std::pair aRet = maCellTextAttrs.position(itPos, nRow1); itPos = aRet.first; // Track the position of cell text attribute array. sal_uInt8 nScriptType = 0; 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, sal_uInt8 nType ) { if (!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(); } size_t ScColumn::GetFormulaHash( SCROW nRow ) const { const ScFormulaCell* pCell = FetchFormulaCell(nRow); return pCell ? pCell->GetHash() : 0; } ScFormulaVectorState ScColumn::GetFormulaVectorState( SCROW nRow ) const { const ScFormulaCell* pCell = FetchFormulaCell(nRow); return pCell ? pCell->GetVectorState() : FormulaVectorUnknown; } formula::FormulaTokenRef ScColumn::ResolveStaticReference( SCROW nRow ) { std::pair 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.getString())); } case sc::element_type_edittext: { const EditTextObject* pText = sc::edittext_block::at(*it->data, aPos.second); OUString aStr = ScEditUtil::GetString(*pText, pDocument); return formula::FormulaTokenRef(new formula::FormulaStringToken(aStr)); } 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(*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, pDocument); sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc); return true; } namespace { struct CellBucket { SCSIZE mnEmpValStart; SCSIZE mnNumValStart; SCSIZE mnStrValStart; SCSIZE mnEmpValCount; std::vector maNumVals; std::vector 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[0]; rMat.PutDouble(p, maNumVals.size(), nCol, mnNumValStart); reset(); } else if (!maStrVals.empty()) { const svl::SharedString* p = &maStrVals[0]; 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; SCCOL mnCol; SCTAB mnTab; ScDocument* mpDoc; svl::SharedStringPool& mrPool; public: FillMatrixHandler(ScMatrix& rMat, size_t nMatCol, size_t nTopRow, SCCOL nCol, SCTAB nTab, ScDocument* pDoc) : mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow), mnCol(nCol), mnTab(nTab), mpDoc(pDoc), mrPool(pDoc->GetSharedStringPool()) {} 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: { const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset); mrMat.PutString(p, nDataSize, mnMatCol, nMatRow); } break; case sc::element_type_edittext: { std::vector 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); aSSs.push_back(mrPool.intern(aStr)); } const svl::SharedString* p = &aSSs[0]; 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 = const_cast(**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; } sal_uInt16 nErr; double fVal; if (rCell.GetErrorOrValue(nErr, fVal)) { ScAddress aAdr(mnCol, nThisRow, mnTab); if (nErr) 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 (!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 ) const { FillMatrixHandler aFunc(rMat, nMatCol, nRow1, nCol, nTab, pDocument); sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); } namespace { template 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; double fNan; rtl::math::setNan(&fNan); for (sc::CellStoreType::iterator it = _it; it != itEnd; ++it) { switch (it->type) { case sc::element_type_string: { sc::string_block::iterator itData, itDataEnd; getBlockIterators(it, nLenRemain, itData, itDataEnd); rCxt.ensureStrArray(rColArray, nArrayLen); for (; itData != itDataEnd; ++itData, ++nPos) (*rColArray.mpStrArray)[nPos] = itData->getDataIgnoreCase(); } break; case sc::element_type_edittext: { sc::edittext_block::iterator itData, itDataEnd; getBlockIterators(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).getDataIgnoreCase(); } } break; case sc::element_type_formula: { sc::formula_block::iterator itData, itDataEnd; getBlockIterators(it, nLenRemain, itData, itDataEnd); for (; itData != itDataEnd; ++itData, ++nPos) { ScFormulaCell& rFC = **itData; sc::FormulaResultValue aRes = rFC.GetResult(); if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError) { if (aRes.mnError == ScErrorCodes::errCircularReference) { // This cell needs to be recalculated on next visit. rFC.SetErrCode(0); rFC.SetDirtyVar(); } return false; } if (aRes.meType == sc::FormulaResultValue::String) { rCxt.ensureStrArray(rColArray, nArrayLen); (*rColArray.mpStrArray)[nPos] = aRes.maString.getDataIgnoreCase(); } else { rCxt.ensureNumArray(rColArray, nArrayLen); (*rColArray.mpNumArray)[nPos] = aRes.mfValue; } } } 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(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->getDataIgnoreCase(); } 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).getDataIgnoreCase(); } } break; default: ; } } sc::FormulaGroupContext::ColArray* copyFirstFormulaBlock( sc::FormulaGroupContext& rCxt, const sc::CellStoreType::iterator& itBlk, size_t nArrayLen, SCTAB nTab, SCCOL nCol ) { double fNan; rtl::math::setNan(&fNan); 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 = NULL; sc::FormulaGroupContext::StrArrayType* pStrArray = NULL; 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) { if (aRes.mnError == ScErrorCodes::errCircularReference) { // This cell needs to be recalculated on next visit. rFC.SetErrCode(0); rFC.SetDirtyVar(); } return NULL; } if (aRes.meType == sc::FormulaResultValue::Value) { if (!pNumArray) { rCxt.maNumArrays.push_back( new sc::FormulaGroupContext::NumArrayType(nArrayLen, fNan)); pNumArray = &rCxt.maNumArrays.back(); } (*pNumArray)[nPos] = aRes.mfValue; } else { if (!pStrArray) { rCxt.maStrArrays.push_back( new sc::FormulaGroupContext::StrArrayType(nArrayLen, NULL)); pStrArray = &rCxt.maStrArrays.back(); } (*pStrArray)[nPos] = aRes.maString.getDataIgnoreCase(); } } if (!pNumArray && !pStrArray) // At least one of these arrays should be allocated. return NULL; return rCxt.setCachedColArray(nTab, nCol, pNumArray, pStrArray); } struct NonNullStringFinder : std::unary_function { bool operator() (const rtl_uString* p) const { return p != NULL; } }; 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::find_if(it, itEnd, NonNullStringFinder()) != itEnd; } } 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. sc::FormulaGroupContext& rCxt = pDocument->GetFormulaGroupContext(); sc::FormulaGroupContext::ColArray* pColArray = rCxt.getCachedColArray(nTab, nCol, nRow2+1); if (pColArray) { const double* pNum = NULL; if (pColArray->mpNumArray) pNum = &(*pColArray->mpNumArray)[nRow1]; rtl_uString** pStr = NULL; if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2)) pStr = &(*pColArray->mpStrArray)[nRow1]; return formula::VectorRefArray(pNum, pStr); } double fNan; rtl::math::setNan(&fNan); // 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 (static_cast(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.maNumArrays.push_back(new sc::FormulaGroupContext::NumArrayType(it, itEnd)); sc::FormulaGroupContext::NumArrayType& rArray = rCxt.maNumArrays.back(); rArray.resize(nRow2+1, fNan); // allocate to the requested length. pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, NULL); 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(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) return formula::VectorRefArray(formula::VectorRefArray::Invalid); if (pColArray->mpStrArray) return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]); else return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]); } break; case sc::element_type_string: case sc::element_type_edittext: { rCxt.maStrArrays.push_back(new sc::FormulaGroupContext::StrArrayType(nRow2+1, NULL)); sc::FormulaGroupContext::StrArrayType& rArray = rCxt.maStrArrays.back(); pColArray = rCxt.setCachedColArray(nTab, nCol, NULL, &rArray); if (!pColArray) // Failed to insert a new cached column array. return formula::VectorRefArray(); if (static_cast(nRow2) < itBlk->size) { // Requested range falls within the first block. copyFirstStringBlock(*pDocument, rArray, nRow2+1, itBlk); return formula::VectorRefArray(&rArray[nRow1]); } copyFirstStringBlock(*pDocument, rArray, itBlk->size, itBlk); // Fill the remaining array with values from the following blocks. size_t nPos = itBlk->size; ++itBlk; if (!appendToBlock(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) return formula::VectorRefArray(formula::VectorRefArray::Invalid); if (pColArray->mpNumArray) return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]); else return formula::VectorRefArray(&(*pColArray->mpStrArray)[nRow1]); } break; case sc::element_type_formula: { if (static_cast(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 = NULL; rtl_uString** pStr = NULL; 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(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) return formula::VectorRefArray(formula::VectorRefArray::Invalid); const double* pNum = NULL; rtl_uString** pStr = NULL; if (pColArray->mpNumArray) pNum = &(*pColArray->mpNumArray)[nRow1]; if (pColArray->mpStrArray) pStr = &(*pColArray->mpStrArray)[nRow1]; return formula::VectorRefArray(pNum, pStr); } break; case sc::element_type_empty: { // Fill the whole length with NaN's. rCxt.maNumArrays.push_back(new sc::FormulaGroupContext::NumArrayType(nRow2+1, fNan)); sc::FormulaGroupContext::NumArrayType& rArray = rCxt.maNumArrays.back(); pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, NULL); if (!pColArray) // Failed to insert a new cached column array. return formula::VectorRefArray(formula::VectorRefArray::Invalid); if (static_cast(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(pDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end())) 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); } 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. 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; rCell.SetResultDouble(*pResults); rCell.ResetDirty(); rCell.SetChanged(true); } } void ScColumn::SetFormulaResults( SCROW nRow, const formula::FormulaTokenRef* 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. 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 formula::FormulaTokenRef* pResEnd = pResults + nLen; for (; pResults != pResEnd; ++pResults, ++itCell) { ScFormulaCell& rCell = **itCell; rCell.SetResultToken(pResults->get()); rCell.ResetDirty(); rCell.SetChanged(true); } } void ScColumn::SetNumberFormat( SCROW nRow, sal_uInt32 nNumberFormat ) { ApplyAttr(nRow, SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat)); } const ScFormulaCell* ScColumn::FetchFormulaCell( SCROW nRow ) const { if (!ValidRow(nRow)) return NULL; std::pair aPos = maCells.position(nRow); sc::CellStoreType::const_iterator it = aPos.first; if (it == maCells.end()) return NULL; if (it->type != sc::element_type_formula) // Not a formula cell. return NULL; return sc::formula_block::at(*it->data, aPos.second); } 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 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) const { return maCells.get_type(nRow) != sc::element_type_empty; } bool ScColumn::IsAllAttrEqual( const ScColumn& rCol, SCROW nStartRow, SCROW nEndRow ) const { if (pAttrArray && rCol.pAttrArray) return pAttrArray->IsAllEqual( *rCol.pAttrArray, nStartRow, nEndRow ); else return !pAttrArray && !rCol.pAttrArray; } bool ScColumn::IsVisibleAttrEqual( const ScColumn& rCol, SCROW nStartRow, SCROW nEndRow ) const { if (pAttrArray && rCol.pAttrArray) return pAttrArray->IsVisibleEqual( *rCol.pAttrArray, nStartRow, nEndRow ); else return !pAttrArray && !rCol.pAttrArray; } bool ScColumn::GetFirstVisibleAttr( SCROW& rFirstRow ) const { if (pAttrArray) return pAttrArray->GetFirstVisibleAttr( rFirstRow ); else return false; } bool ScColumn::GetLastVisibleAttr( SCROW& rLastRow, bool bFullFormattedArea ) const { if (pAttrArray) { // row of last cell is needed SCROW nLastData = GetLastDataPos(); // always including notes, 0 if none return pAttrArray->GetLastVisibleAttr( rLastRow, nLastData, bFullFormattedArea ); } else return false; } bool ScColumn::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const { if (pAttrArray) return pAttrArray->HasVisibleAttrIn( nStartRow, nEndRow ); else return false; } namespace { class FindUsedRowsHandler { typedef mdds::flat_segment_tree UsedRowsType; UsedRowsType& mrUsed; UsedRowsType::const_iterator miUsed; public: 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& 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: #if DEBUG_COLUMN_STORAGE cout << "ScColumn::StartListening: wrong block type encountered in the broadcaster storage." << endl; cout.flush(); abort(); #else ; #endif } } } void ScColumn::StartListening( SvtListener& rLst, SCROW nRow ) { std::pair 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. maBroadcasters.set_empty(nRow, nRow); } void ScColumn::StartListening( sc::StartListeningContext& rCxt, SCROW nRow, SvtListener& rLst ) { if (!ValidRow(nRow)) return; sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol); if (!p) return; sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos; std::pair aPos = maBroadcasters.position(it, nRow); it = aPos.first; // store the block position for next iteration. startListening(maBroadcasters, it, aPos.second, nRow, rLst); } void ScColumn::EndListening( sc::EndListeningContext& rCxt, SCROW nRow, SvtListener& rListener ) { sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol); if (!p) return; sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos; std::pair aPos = maBroadcasters.position(it, nRow); 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); OSL_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(nTab, nCol, nRow); } namespace { class CompileDBFormulaHandler { sc::CompileFormulaContext& mrCxt; public: CompileDBFormulaHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {} void operator() (size_t, ScFormulaCell* p) { p->CompileDBFormula(mrCxt); } }; struct CompileColRowNameFormulaHandler { sc::CompileFormulaContext& mrCxt; public: 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.bError) return; switch (mrData.eFunc) { case SUBTOTAL_FUNC_SUM: case SUBTOTAL_FUNC_AVE: { if (!bVal) return; ++mrData.nCount; if (!SubTotal::SafePlus(mrData.nVal, fVal)) mrData.bError = true; } break; case SUBTOTAL_FUNC_CNT: // only the value { if (!bVal) return; ++mrData.nCount; } break; case SUBTOTAL_FUNC_CNT2: // everything ++mrData.nCount; break; case SUBTOTAL_FUNC_MAX: { if (!bVal) return; if (++mrData.nCount == 1 || fVal > mrData.nVal) mrData.nVal = fVal; } break; case SUBTOTAL_FUNC_MIN: { if (!bVal) return; if (++mrData.nCount == 1 || fVal < mrData.nVal) mrData.nVal = fVal; } break; default: { // added to avoid warnings } } } public: 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.eFunc != SUBTOTAL_FUNC_CNT2) // it doesn't interest us { if (pCell->GetErrCode()) { if (mrData.eFunc != SUBTOTAL_FUNC_CNT) // simply remove from count mrData.bError = true; } else if (pCell->IsValue()) { fVal = pCell->GetValue(); bVal = true; } // otherwise text } update(fVal, bVal); } }; } // multiple selections: void ScColumn::UpdateSelectionFunction( const ScRangeList& rRanges, ScFunctionData& rData, ScFlatBoolRowSegments& rHiddenRows ) { sc::SingleColumnSpanSet aSpanSet; aSpanSet.scan(rRanges, nTab, nCol); // mark all selected rows. // Exclude all hidden rows. ScFlatBoolRowSegments::RangeData aRange; SCROW nRow = 0; while (nRow <= 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); sc::SingleColumnSpanSet::SpansType::const_iterator it = aSpans.begin(), itEnd = aSpans.end(); switch (rData.eFunc) { case SUBTOTAL_FUNC_SELECTION_COUNT: { // Simply count selected rows regardless of cell contents. for (; it != itEnd; ++it) rData.nCount += it->mnRow2 - it->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 (; it != itEnd; ++it) { itCellPos = sc::ParseAllNonEmpty( itCellPos, maCells, it->mnRow1, it->mnRow2, aFunc); } } break; default: { // We need to parse only numeric values. sc::CellStoreType::const_iterator itCellPos = maCells.begin(); UpdateSubTotalHandler aFunc(rData); for (; it != itEnd; ++it) { itCellPos = sc::ParseFormulaNumeric( itCellPos, maCells, it->mnRow1, it->mnRow2, aFunc); } } } } namespace { class WeightedCounter { size_t mnCount; public: WeightedCounter() : mnCount(0) {} void operator() (const sc::CellStoreType::value_type& node) { switch (node.type) { case sc::element_type_numeric: case sc::element_type_string: mnCount += node.size; break; case sc::element_type_formula: { // Each formula cell is worth its code length plus 5. sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data); sc::formula_block::const_iterator itEnd = sc::formula_block::end(*node.data); for (; it != itEnd; ++it) { const ScFormulaCell* p = *it; mnCount += 5 + p->GetCode()->GetCodeLen(); } } break; case sc::element_type_edittext: // each edit-text cell is worth 50. mnCount += node.size * 50; break; default: ; } } size_t getCount() const { return mnCount; } }; } sal_uInt32 ScColumn::GetWeightedCount() const { WeightedCounter aFunc; std::for_each(maCells.begin(), maCells.end(), aFunc); return aFunc.getCount(); } namespace { class CodeCounter { size_t mnCount; public: CodeCounter() : mnCount(0) {} void operator() (size_t, const ScFormulaCell* p) { mnCount += p->GetCode()->GetCodeLen(); } size_t getCount() const { return mnCount; } }; } sal_uInt32 ScColumn::GetCodeCount() const { CodeCounter aFunc; sc::ParseFormula(maCells, aFunc); return aFunc.getCount(); } SCSIZE ScColumn::GetPatternCount() const { return pAttrArray ? pAttrArray->Count() : 0; } SCSIZE ScColumn::GetPatternCount( SCROW nRow1, SCROW nRow2 ) const { return pAttrArray ? pAttrArray->Count( nRow1, nRow2 ) : 0; } bool ScColumn::ReservePatternCount( SCSIZE nReserve ) { return pAttrArray && pAttrArray->Reserve( nReserve ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */