/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; using ::std::vector; using ::com::sun::star::beans::XPropertySet; using ::com::sun::star::uno::Sequence; using ::com::sun::star::uno::UNO_QUERY; using ::com::sun::star::uno::Reference; using ::com::sun::star::sheet::DataPilotTablePositionData; using ::com::sun::star::sheet::DataPilotTableResultData; #define SC_DP_FRAME_INNER_BOLD 20 #define SC_DP_FRAME_OUTER_BOLD 40 #define SC_DP_FRAME_COLOR Color(0,0,0) //( 0x20, 0x40, 0x68 ) namespace { struct ScDPOutLevelDataComparator { bool operator()(const ScDPOutLevelData & rA, const ScDPOutLevelData & rB) { return rA.mnDimPos mbNeedLineCols; ::std::vector< SCCOL > mnCols; ::std::vector< bool > mbNeedLineRows; ::std::vector< SCROW > mnRows; SCCOL mnTabStartCol; SCROW mnTabStartRow; SCCOL mnDataStartCol; SCROW mnDataStartRow; SCCOL mnTabEndCol; SCROW mnTabEndRow; public: ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab, SCCOL nTabStartCol, SCROW nTabStartRow, SCCOL nDataStartCol, SCROW nDataStartRow, SCCOL nTabEndCol, SCROW nTabEndRow ); bool AddRow( SCROW nRow ); bool AddCol( SCCOL nCol ); void OutputDataArea(); void OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori = false ); }; void ScDPOutputImpl::OutputDataArea() { AddRow( mnDataStartRow ); AddCol( mnDataStartCol ); mnCols.push_back( mnTabEndCol+1); //set last row bottom mnRows.push_back( mnTabEndRow+1); //set last col bottom bool bAllRows = ( ( mnTabEndRow - mnDataStartRow + 2 ) == static_cast(mnRows.size()) ); std::sort( mnCols.begin(), mnCols.end()); std::sort( mnRows.begin(), mnRows.end()); for( SCCOL nCol = 0; nCol < static_cast(mnCols.size())-1; nCol ++ ) { if ( !bAllRows ) { if ( nCol < static_cast(mnCols.size())-2) { for ( SCROW i = nCol%2; i < static_cast(mnRows.size())-2; i +=2 ) OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 ); if ( mnRows.size()>=2 ) OutputBlockFrame( mnCols[nCol], mnRows[mnRows.size()-2], mnCols[nCol+1]-1, mnRows[mnRows.size()-1]-1 ); } else { for ( SCROW i = 0 ; i < static_cast(mnRows.size())-1; i++ ) OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 ); } } else OutputBlockFrame( mnCols[nCol], mnRows.front(), mnCols[nCol+1]-1, mnRows.back()-1, bAllRows ); } //out put rows area outer framer if ( mnTabStartCol != mnDataStartCol ) { if ( mnTabStartRow != mnDataStartRow ) OutputBlockFrame( mnTabStartCol, mnTabStartRow, mnDataStartCol-1, mnDataStartRow-1 ); OutputBlockFrame( mnTabStartCol, mnDataStartRow, mnDataStartCol-1, mnTabEndRow ); } //out put cols area outer framer OutputBlockFrame( mnDataStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow-1 ); } ScDPOutputImpl::ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab, SCCOL nTabStartCol, SCROW nTabStartRow, SCCOL nDataStartCol, SCROW nDataStartRow, SCCOL nTabEndCol, SCROW nTabEndRow ): mpDoc( pDoc ), mnTab( nTab ), mnTabStartCol( nTabStartCol ), mnTabStartRow( nTabStartRow ), mnDataStartCol ( nDataStartCol ), mnDataStartRow ( nDataStartRow ), mnTabEndCol( nTabEndCol ), mnTabEndRow( nTabEndRow ) { mbNeedLineCols.resize( nTabEndCol-nDataStartCol+1, false ); mbNeedLineRows.resize( nTabEndRow-nDataStartRow+1, false ); } bool ScDPOutputImpl::AddRow( SCROW nRow ) { if ( !mbNeedLineRows[ nRow - mnDataStartRow ] ) { mbNeedLineRows[ nRow - mnDataStartRow ] = true; mnRows.push_back( nRow ); return true; } else return false; } bool ScDPOutputImpl::AddCol( SCCOL nCol ) { if ( !mbNeedLineCols[ nCol - mnDataStartCol ] ) { mbNeedLineCols[ nCol - mnDataStartCol ] = true; mnCols.push_back( nCol ); return true; } else return false; } void ScDPOutputImpl::OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori ) { Color color = SC_DP_FRAME_COLOR; ::editeng::SvxBorderLine aLine( &color, SC_DP_FRAME_INNER_BOLD ); ::editeng::SvxBorderLine aOutLine( &color, SC_DP_FRAME_OUTER_BOLD ); SvxBoxItem aBox( ATTR_BORDER ); if ( nStartCol == mnTabStartCol ) aBox.SetLine(&aOutLine, SvxBoxItemLine::LEFT); else aBox.SetLine(&aLine, SvxBoxItemLine::LEFT); if ( nStartRow == mnTabStartRow ) aBox.SetLine(&aOutLine, SvxBoxItemLine::TOP); else aBox.SetLine(&aLine, SvxBoxItemLine::TOP); if ( nEndCol == mnTabEndCol ) //bottom row aBox.SetLine(&aOutLine, SvxBoxItemLine::RIGHT); else aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT); if ( nEndRow == mnTabEndRow ) //bottom aBox.SetLine(&aOutLine, SvxBoxItemLine::BOTTOM); else aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM); SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER ); aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false ); if ( bHori ) { aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI); aBoxInfo.SetLine( &aLine, SvxBoxInfoItemLine::HORI ); } else aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false ); aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false); mpDoc->ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow , mnTab), aBox, aBoxInfo); } namespace { void lcl_SetStyleById(ScDocument* pDoc, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, TranslateId pStrId) { if ( nCol1 > nCol2 || nRow1 > nRow2 ) { OSL_FAIL("SetStyleById: invalid range"); return; } OUString aStyleName = ScResId(pStrId); ScStyleSheetPool* pStlPool = pDoc->GetStyleSheetPool(); ScStyleSheet* pStyle = static_cast( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) ); if (!pStyle) { // create new style (was in ScPivot::SetStyle) pStyle = static_cast( &pStlPool->Make( aStyleName, SfxStyleFamily::Para, SfxStyleSearchBits::UserDefined ) ); pStyle->SetParent( ScResId(STR_STYLENAME_STANDARD) ); SfxItemSet& rSet = pStyle->GetItemSet(); if (pStrId == STR_PIVOT_STYLENAME_RESULT || pStrId == STR_PIVOT_STYLENAME_TITLE){ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT ) ); rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT ) ); } if (pStrId == STR_PIVOT_STYLENAME_CATEGORY || pStrId == STR_PIVOT_STYLENAME_TITLE) rSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) ); } pDoc->ApplyStyleAreaTab( nCol1, nRow1, nCol2, nRow2, nTab, *pStyle ); } void lcl_SetFrame( ScDocument* pDoc, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sal_uInt16 nWidth ) { ::editeng::SvxBorderLine aLine(nullptr, nWidth, SvxBorderLineStyle::SOLID); SvxBoxItem aBox( ATTR_BORDER ); aBox.SetLine(&aLine, SvxBoxItemLine::LEFT); aBox.SetLine(&aLine, SvxBoxItemLine::TOP); aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT); aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM); SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER ); aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false); aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false); aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false); pDoc->ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo); } void lcl_FillNumberFormats( std::unique_ptr& rFormats, sal_Int32& rCount, const uno::Reference& xLevRes, const uno::Reference& xDims ) { if ( rFormats ) return; // already set // xLevRes is from the data layout dimension //TODO: use result sequence from ScDPOutLevelData! uno::Sequence aResult = xLevRes->getResults(); tools::Long nSize = aResult.getLength(); if (!nSize) return; // get names/formats for all data dimensions //TODO: merge this with the loop to collect ScDPOutLevelData? std::vector aDataNames; std::vector aDataFormats; sal_Int32 nDimCount = xDims->getCount(); sal_Int32 nDim = 0; for ( ; nDim < nDimCount ; nDim++) { uno::Reference xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDimProp( xDim, uno::UNO_QUERY ); uno::Reference xDimName( xDim, uno::UNO_QUERY ); if ( xDimProp.is() && xDimName.is() ) { sheet::DataPilotFieldOrientation eDimOrient = ScUnoHelpFunctions::GetEnumProperty( xDimProp, SC_UNO_DP_ORIENTATION, sheet::DataPilotFieldOrientation_HIDDEN ); if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) { aDataNames.push_back(xDimName->getName()); tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNONAME_NUMFMT ); aDataFormats.push_back(nFormat); } } } if (aDataFormats.empty()) return; const sheet::MemberResult* pArray = aResult.getConstArray(); OUString aName; sal_uInt32* pNumFmt = new sal_uInt32[nSize]; if (aDataFormats.size() == 1) { // only one data dimension -> use its numberformat everywhere tools::Long nFormat = aDataFormats[0]; for (tools::Long nPos=0; nPos& xDims ) { tools::Long nDimCount = xDims->getCount(); for (tools::Long nDim=0; nDim xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); if ( xDimProp.is() ) { sheet::DataPilotFieldOrientation eDimOrient = ScUnoHelpFunctions::GetEnumProperty( xDimProp, SC_UNO_DP_ORIENTATION, sheet::DataPilotFieldOrientation_HIDDEN ); if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) { tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNONAME_NUMFMT ); return nFormat; // use format from first found data dimension } } } return 0; // none found } bool lcl_MemberEmpty( const uno::Sequence& rSeq ) { // used to skip levels that have no members return std::none_of(rSeq.begin(), rSeq.end(), [](const sheet::MemberResult& rMem) { return rMem.Flags & sheet::MemberResultFlags::HASMEMBER; }); } /** * Get visible page dimension members as results, except that, if all * members are visible, then this function returns empty result. */ uno::Sequence getVisiblePageMembersAsResults( const uno::Reference& xLevel ) { if (!xLevel.is()) return uno::Sequence(); uno::Reference xMSupplier(xLevel, UNO_QUERY); if (!xMSupplier.is()) return uno::Sequence(); uno::Reference xNA = xMSupplier->getMembers(); if (!xNA.is()) return uno::Sequence(); std::vector aRes; const uno::Sequence aNames = xNA->getElementNames(); for (const OUString& rName : aNames) { xNA->getByName(rName); uno::Reference xMemPS(xNA->getByName(rName), UNO_QUERY); if (!xMemPS.is()) continue; OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xMemPS, SC_UNO_DP_LAYOUTNAME, OUString()); if (aCaption.isEmpty()) aCaption = rName; bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xMemPS, SC_UNO_DP_ISVISIBLE); if (bVisible) { /* TODO: any numeric value to obtain? */ aRes.emplace_back(rName, aCaption, 0, std::numeric_limits::quiet_NaN()); } } if (o3tl::make_unsigned(aNames.getLength()) == aRes.size()) // All members are visible. Return empty result. return uno::Sequence(); return comphelper::containerToSequence(aRes); } } // end anonymous namespace ScDPOutput::ScDPOutput(ScDocument* pDocument, uno::Reference xSource, const ScAddress& rPosition, bool bFilter, bool bExpandCollapse, ScDPObject& rObject, bool bHideHeader) : mpDocument(pDocument) , maFormatOutput(rObject) , mxSource(std::move(xSource)) , maStartPos(rPosition) , mnColFormatCount(0) , mnRowFormatCount(0) , mnSingleNumberFormat(0) , mnRowDims(0) , mnColCount(0) , mnRowCount(0) , mnHeaderSize(0) , mbDoFilter(bFilter) , mbResultsError(false) , mbSizesValid(false) , mbSizeOverflow(false) , mbHeaderLayout(false) , mbHasCompactRowField(false) , mbExpandCollapse(bExpandCollapse) , mbHideHeader(bHideHeader) { mnTabStartCol = mnMemberStartCol = mnDataStartCol = mnTabEndCol = 0; mnTabStartRow = mnMemberStartRow = mnDataStartRow = mnTabEndRow = 0; uno::Reference xResult(mxSource, uno::UNO_QUERY); if (mxSource.is() && xResult.is()) { // get dimension results: uno::Reference xDims = new ScNameToIndexAccess(mxSource->getDimensions()); tools::Long nDimCount = xDims->getCount(); for (tools::Long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDimProp( xDim, uno::UNO_QUERY ); uno::Reference xDimSupp( xDim, uno::UNO_QUERY ); if ( xDimProp.is() && xDimSupp.is() ) { sheet::DataPilotFieldOrientation eDimOrient = ScUnoHelpFunctions::GetEnumProperty( xDimProp, SC_UNO_DP_ORIENTATION, sheet::DataPilotFieldOrientation_HIDDEN ); tools::Long nDimPos = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNO_DP_POSITION ); bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp, SC_UNO_DP_ISDATALAYOUT); bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty( xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER); sal_Int32 nNumFmt = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNO_DP_NUMBERFO); if ( eDimOrient != sheet::DataPilotFieldOrientation_HIDDEN ) { uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, SC_UNO_DP_USEDHIERARCHY ); if ( nHierarchy >= xHiers->getCount() ) nHierarchy = 0; uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), uno::UNO_QUERY); if ( xHierSupp.is() ) { uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); tools::Long nLevCount = xLevels->getCount(); for (tools::Long nLev=0; nLev xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY); uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); uno::Reference xLevRes( xLevel, uno::UNO_QUERY ); if ( xLevNam.is() && xLevRes.is() ) { OUString aName = xLevNam->getName(); Reference xPropSet(xLevel, UNO_QUERY); // Caption equals the field name by default. // #i108948# use ScUnoHelpFunctions::GetStringProperty, because // LayoutName is new and may not be present in external implementation OUString aCaption = ScUnoHelpFunctions::GetStringProperty( xPropSet, SC_UNO_DP_LAYOUTNAME, aName ); switch ( eDimOrient ) { case sheet::DataPilotFieldOrientation_COLUMN: { uno::Sequence aResult = xLevRes->getResults(); if (!lcl_MemberEmpty(aResult)) { mpColFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, aCaption, bHasHiddenMember, bIsDataLayout, false); } } break; case sheet::DataPilotFieldOrientation_ROW: { uno::Sequence aResult = xLevRes->getResults(); ++mnRowDims; // We want only to remove the DATA column if it is empty // and not any other empty columns (to still show the // header columns) bool bSkip = lcl_MemberEmpty(aResult) && bIsDataLayout; if (!bSkip) { bool bFieldCompact = false; try { sheet::DataPilotFieldLayoutInfo aLayoutInfo; xPropSet->getPropertyValue( SC_UNO_DP_LAYOUT ) >>= aLayoutInfo; bFieldCompact = (aLayoutInfo.LayoutMode == sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT); } catch (uno::Exception&) { } mpRowFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, aCaption, bHasHiddenMember, bIsDataLayout, false); maRowCompactFlags.push_back(bFieldCompact); mbHasCompactRowField |= bFieldCompact; } } break; case sheet::DataPilotFieldOrientation_PAGE: { uno::Sequence aResult = getVisiblePageMembersAsResults(xLevel); // no check on results for page fields mpPageFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName, aCaption, bHasHiddenMember, false, true); } break; default: { // added to avoid warnings } } // get number formats from data dimensions if ( bIsDataLayout ) { OSL_ENSURE( nLevCount == 1, "data layout: multiple levels?" ); if ( eDimOrient == sheet::DataPilotFieldOrientation_COLUMN ) lcl_FillNumberFormats(mpColNumberFormat, mnColFormatCount, xLevRes, xDims); else if ( eDimOrient == sheet::DataPilotFieldOrientation_ROW ) lcl_FillNumberFormats(mpRowNumberFormat, mnRowFormatCount, xLevRes, xDims); } } } } } else if ( bIsDataLayout ) { // data layout dimension is hidden (allowed if there is only one data dimension) // -> use the number format from the first data dimension for all results mnSingleNumberFormat = lcl_GetFirstNumberFormat( xDims ); } } } std::sort(mpColFields.begin(), mpColFields.end(), ScDPOutLevelDataComparator()); std::sort(mpRowFields.begin(), mpRowFields.end(), ScDPOutLevelDataComparator()); std::sort(mpPageFields.begin(), mpPageFields.end(), ScDPOutLevelDataComparator()); // get data results: try { maData = xResult->getResults(); } catch (const uno::RuntimeException&) { mbResultsError = true; } } // get "DataDescription" property (may be missing in external sources) uno::Reference xSrcProp(mxSource, uno::UNO_QUERY); if ( !xSrcProp.is() ) return; try { uno::Any aAny = xSrcProp->getPropertyValue( SC_UNO_DP_DATADESC ); OUString aUStr; aAny >>= aUStr; maDataDescription = aUStr; } catch(const uno::Exception&) { } } ScDPOutput::~ScDPOutput() { } void ScDPOutput::SetPosition(const ScAddress& rPosition) { maStartPos = rPosition; mbSizesValid = mbSizeOverflow = false; } void ScDPOutput::DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::DataResult& rData ) { tools::Long nFlags = rData.Flags; if ( nFlags & sheet::DataResultFlags::ERROR ) { mpDocument->SetError( nCol, nRow, nTab, FormulaError::NoValue ); } else if ( nFlags & sheet::DataResultFlags::HASDATA ) { mpDocument->SetValue( nCol, nRow, nTab, rData.Value ); // use number formats from source OSL_ENSURE(mbSizesValid, "DataCell: !bSizesValid"); sal_uInt32 nFormat = 0; bool bApplyFormat = false; if (mpColNumberFormat) { if (nCol >= mnDataStartCol) { tools::Long nIndex = nCol - mnDataStartCol; if (nIndex < mnColFormatCount) { nFormat = mpColNumberFormat[nIndex]; bApplyFormat = true; } } } else if (mpRowNumberFormat) { if (nRow >= mnDataStartRow) { tools::Long nIndex = nRow - mnDataStartRow; if (nIndex < mnRowFormatCount) { nFormat = mpRowNumberFormat[nIndex]; bApplyFormat = true; } } } else if (mnSingleNumberFormat != 0) { nFormat = mnSingleNumberFormat; // single format is used everywhere bApplyFormat = true; } if (bApplyFormat) mpDocument->ApplyAttr(nCol, nRow, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat)); } // SubTotal formatting is controlled by headers } void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::MemberResult& rData, bool bColHeader, tools::Long nLevel ) { tools::Long nFlags = rData.Flags; if ( nFlags & sheet::MemberResultFlags::HASMEMBER ) { bool bNumeric = (nFlags & sheet::MemberResultFlags::NUMERIC) != 0; if (bNumeric && std::isfinite( rData.Value)) { mpDocument->SetValue( nCol, nRow, nTab, rData.Value); } else { ScSetStringParam aParam; if (bNumeric) aParam.setNumericInput(); else aParam.setTextInput(); mpDocument->SetString(nCol, nRow, nTab, rData.Caption, &aParam); } } if ( !(nFlags & sheet::MemberResultFlags::SUBTOTAL) ) return; ScDPOutputImpl outputimp(mpDocument, nTab, mnTabStartCol, mnTabStartRow, mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow); //TODO: limit frames to horizontal or vertical? if (bColHeader) { outputimp.OutputBlockFrame(nCol, mnMemberStartRow+static_cast(nLevel), nCol, mnDataStartRow - 1); lcl_SetStyleById(mpDocument, nTab, nCol, mnMemberStartRow + static_cast(nLevel), nCol, mnDataStartRow - 1, STR_PIVOT_STYLENAME_TITLE); lcl_SetStyleById(mpDocument, nTab, nCol, mnDataStartRow, nCol, mnTabEndRow, STR_PIVOT_STYLENAME_RESULT ); } else { outputimp.OutputBlockFrame(mnMemberStartCol + static_cast(nLevel), nRow, mnDataStartCol - 1, nRow); lcl_SetStyleById(mpDocument, nTab, mnMemberStartCol + static_cast(nLevel), nRow, mnDataStartCol - 1, nRow, STR_PIVOT_STYLENAME_TITLE); lcl_SetStyleById(mpDocument, nTab, mnDataStartCol, nRow, mnTabEndCol, nRow, STR_PIVOT_STYLENAME_RESULT); } } void ScDPOutput::MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bRowField) { mpDocument->SetString(nCol, nRow, nTab, ScResId(bRowField ? STR_PIVOT_ROW_LABELS : STR_PIVOT_COL_LABELS)); ScMF nMergeFlag = ScMF::Button; for (auto& rData : mpRowFields) { if (rData.mbHasHiddenMember) { nMergeFlag |= ScMF::HiddenMember; break; } } nMergeFlag |= ScMF::ButtonPopup2; mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag); lcl_SetStyleById(mpDocument, nTab, nCol, nRow, nCol, nRow, STR_PIVOT_STYLENAME_FIELDNAME); } void ScDPOutput::FieldCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool bInTable) { // Avoid unwanted automatic format detection. ScSetStringParam aParam; aParam.mbDetectNumberFormat = false; aParam.meSetTextNumFormat = ScSetStringParam::Always; aParam.mbHandleApostrophe = false; mpDocument->SetString(nCol, nRow, nTab, rData.maCaption, &aParam); if (bInTable) lcl_SetFrame(mpDocument, nTab, nCol,nRow, nCol,nRow, 20); // For field button drawing ScMF nMergeFlag = ScMF::NONE; if (rData.mbHasHiddenMember) nMergeFlag |= ScMF::HiddenMember; if (rData.mbPageDim) { nMergeFlag |= ScMF::ButtonPopup; mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button); mpDocument->ApplyFlagsTab(nCol+1, nRow, nCol+1, nRow, nTab, nMergeFlag); } else { nMergeFlag |= ScMF::Button; if (!rData.mbDataLayout) nMergeFlag |= ScMF::ButtonPopup; mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag); } lcl_SetStyleById(mpDocument, nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME); } static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) { pDoc->SetString( nCol, nRow, nTab, ScResId(STR_CELL_FILTER) ); pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button); } SCCOL ScDPOutput::GetColumnsForRowFields() const { if (!mbHasCompactRowField) return static_cast(mpRowFields.size()); SCCOL nNum = 0; for (const auto bCompact: maRowCompactFlags) if (!bCompact) ++nNum; if (maRowCompactFlags.back()) ++nNum; return nNum; } void ScDPOutput::CalcSizes() { if (mbSizesValid) return; // get column size of data from first row //TODO: allow different sizes (and clear following areas) ??? mnRowCount = maData.getLength(); const uno::Sequence* pRowAry = maData.getConstArray(); mnColCount = mnRowCount ? ( pRowAry[0].getLength() ) : 0; mnHeaderSize = 1; if (mbHideHeader) mnHeaderSize = 0; else if (GetHeaderLayout() && mpColFields.empty()) // Insert an extra header row only when there is no column field. mnHeaderSize = 2; // calculate output positions and sizes tools::Long nPageSize = 0; // use page fields! if (mbDoFilter || !mpPageFields.empty()) { nPageSize += mpPageFields.size() + 1; // plus one empty row if (mbDoFilter) ++nPageSize; // filter button above the page fields } if (maStartPos.Col() + static_cast(mpRowFields.size()) + mnColCount - 1 > mpDocument->MaxCol() || maStartPos.Row() + nPageSize + mnHeaderSize + static_cast(mpColFields.size()) + mnRowCount > mpDocument->MaxRow()) { mbSizeOverflow = true; } mnTabStartCol = maStartPos.Col(); mnTabStartRow = maStartPos.Row() + static_cast(nPageSize); // below page fields mnMemberStartCol = mnTabStartCol; mnMemberStartRow = mnTabStartRow + static_cast(mnHeaderSize); mnDataStartCol = mnMemberStartCol + GetColumnsForRowFields(); mnDataStartRow = mnMemberStartRow + static_cast(mpColFields.size()); if (mnColCount > 0) mnTabEndCol = mnDataStartCol + static_cast(mnColCount) - 1; else mnTabEndCol = mnDataStartCol; // single column will remain empty // if page fields are involved, include the page selection cells if (!mpPageFields.empty() && mnTabEndCol < mnTabStartCol + 1) mnTabEndCol = mnTabStartCol + 1; if (mnRowCount > 0) mnTabEndRow = mnDataStartRow + static_cast(mnRowCount) - 1; else mnTabEndRow = mnDataStartRow; // single row will remain empty mbSizesValid = true; } sal_Int32 ScDPOutput::GetPositionType(const ScAddress& rPos) { using namespace ::com::sun::star::sheet; SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if ( nTab != maStartPos.Tab() ) return DataPilotTablePositionType::NOT_IN_TABLE; CalcSizes(); // Make sure the cursor is within the table. if (nCol < mnTabStartCol || nRow < mnTabStartRow || nCol > mnTabEndCol || nRow > mnTabEndRow) return DataPilotTablePositionType::NOT_IN_TABLE; // test for result data area. if (nCol >= mnDataStartCol && nCol <= mnTabEndCol && nRow >= mnDataStartRow && nRow <= mnTabEndRow) return DataPilotTablePositionType::RESULT; bool bInColHeader = (nRow >= mnTabStartRow && nRow < mnDataStartRow); bool bInRowHeader = (nCol >= mnTabStartCol && nCol < mnDataStartCol); if (bInColHeader && bInRowHeader) // probably in that ugly little box at the upper-left corner of the table. return DataPilotTablePositionType::OTHER; if (bInColHeader) { if (nRow == mnTabStartRow) // first row in the column header area is always used for column // field buttons. return DataPilotTablePositionType::OTHER; return DataPilotTablePositionType::COLUMN_HEADER; } if (bInRowHeader) return DataPilotTablePositionType::ROW_HEADER; return DataPilotTablePositionType::OTHER; } void ScDPOutput::outputPageFields(SCTAB nTab) { for (size_t nField = 0; nField < mpPageFields.size(); ++nField) { SCCOL nHeaderCol = maStartPos.Col(); SCROW nHeaderRow = maStartPos.Row() + nField + (mbDoFilter ? 1 : 0); // draw without frame for consistency with filter button: FieldCell(nHeaderCol, nHeaderRow, nTab, mpPageFields[nField], false); SCCOL nFieldCol = nHeaderCol + 1; OUString aPageValue = ScResId(SCSTR_ALL); const uno::Sequence& rRes = mpPageFields[nField].maResult; sal_Int32 n = rRes.getLength(); if (n == 1) { if (rRes[0].Caption.isEmpty()) aPageValue = ScResId(STR_EMPTYDATA); else aPageValue = rRes[0].Caption; } else if (n > 1) { aPageValue = ScResId(SCSTR_MULTIPLE); } ScSetStringParam aParam; aParam.setTextInput(); mpDocument->SetString(nFieldCol, nHeaderRow, nTab, aPageValue, &aParam); lcl_SetFrame(mpDocument, nTab, nFieldCol, nHeaderRow, nFieldCol, nHeaderRow, 20); } } void ScDPOutput::outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl) { size_t nNumColFields = mpColFields.size(); for (size_t nField = 0; nField < nNumColFields; nField++) { SCCOL nHeaderCol = mnDataStartCol + SCCOL(nField); //TODO: check for overflow if (mnMemberStartRow > mnTabStartRow) { if (!mbHasCompactRowField || nNumColFields == 1) FieldCell(nHeaderCol, mnTabStartRow, nTab, mpColFields[nField], true); else if (!nField) MultiFieldCell(nHeaderCol, mnTabStartRow, nTab, false /* bRowField */); } SCROW nRowPos = mnMemberStartRow + SCROW(nField); //TODO: check for overflow const uno::Sequence rMemberSequence = mpColFields[nField].maResult; const sheet::MemberResult* pMemberArray = rMemberSequence.getConstArray(); tools::Long nThisColCount = rMemberSequence.getLength(); OSL_ENSURE(nThisColCount == mnColCount, "count mismatch"); //TODO: ??? for (tools::Long nColumn = 0; nColumn < nThisColCount; nColumn++) { sheet::MemberResult const& rMember = rMemberSequence[nColumn]; SCCOL nColPos = mnDataStartCol + SCCOL(nColumn); //TODO: check for overflow HeaderCell(nColPos, nRowPos, nTab, rMember, true, nField); if ((rMember.Flags & sheet::MemberResultFlags::HASMEMBER) && !(rMember.Flags & sheet::MemberResultFlags::SUBTOTAL)) { // Check the number of columns this spreads tools::Long nEnd = nColumn; while (nEnd + 1 < nThisColCount && (pMemberArray[nEnd + 1].Flags & sheet::MemberResultFlags::CONTINUE)) ++nEnd; SCCOL nEndColPos = mnDataStartCol + SCCOL(nEnd); //TODO: check for overflow if (nField + 1 < mpColFields.size()) { if (nField + 2 == mpColFields.size()) { rOutputImpl.AddCol( nColPos ); if (nColPos + 1 == nEndColPos) rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nEndColPos, nRowPos + 1, true); } else rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nEndColPos, nRowPos); lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, nEndColPos, mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY); } else { lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, nColPos, mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY); } } else if (rMember.Flags & sheet::MemberResultFlags::SUBTOTAL) { rOutputImpl.AddCol(nColPos); } // Resolve formats maFormatOutput.insertFieldMember(nField, mpColFields[nField], nColumn, rMember, nColPos, nRowPos, sc::FormatResultDirection::COLUMN); // Apply the same number format as in data source. mpDocument->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, mpColFields[nField].mnSrcNumFmt)); } if (nField == 0 && mpColFields.size() == 1 && mnMemberStartRow > mnTabStartRow) rOutputImpl.OutputBlockFrame(mnDataStartCol, mnTabStartRow, mnTabEndCol, nRowPos - 1); } } void ScDPOutput::outputRowHeader(SCTAB nTab, ScDPOutputImpl& rOutputImpl) { std::vector vbSetBorder; vbSetBorder.resize(mnTabEndRow - mnDataStartRow + 1, false); size_t nFieldColOffset = 0; size_t nFieldIndentLevel = 0; // To calculate indent level for fields packed in a column. size_t nNumRowFields = mpRowFields.size(); for (size_t nField = 0; nField < nNumRowFields; nField++) { const bool bCompactField = maRowCompactFlags[nField]; SCCOL nHdrCol = mnTabStartCol + SCCOL(nField); //TODO: check for overflow SCROW nHdrRow = mnDataStartRow - 1; if (!mbHasCompactRowField || nNumRowFields == 1) FieldCell(nHdrCol, nHdrRow, nTab, mpRowFields[nField], true); else if (!nField) MultiFieldCell(nHdrCol, nHdrRow, nTab, true /* bRowField */); SCCOL nColPos = mnMemberStartCol + SCCOL(nFieldColOffset); //TODO: check for overflow const uno::Sequence rMemberSequence = mpRowFields[nField].maResult; const sheet::MemberResult* pMemberArray = rMemberSequence.getConstArray(); sal_Int32 nThisRowCount = rMemberSequence.getLength(); OSL_ENSURE(nThisRowCount == mnRowCount, "count mismatch"); //TODO: ??? for (sal_Int32 nRow = 0; nRow < nThisRowCount; nRow++) { sheet::MemberResult const& rMember = rMemberSequence[nRow]; const sheet::MemberResult& rData = rMember; const bool bHasMember = rData.Flags & sheet::MemberResultFlags::HASMEMBER; const bool bSubtotal = rData.Flags & sheet::MemberResultFlags::SUBTOTAL; SCROW nRowPos = mnDataStartRow + SCROW(nRow); //TODO: check for overflow HeaderCell( nColPos, nRowPos, nTab, rData, false, nFieldColOffset ); if (bHasMember && !bSubtotal) { if (nField + 1 < mpRowFields.size()) { tools::Long nEnd = nRow; while (nEnd + 1 < nThisRowCount && (pMemberArray[nEnd + 1].Flags & sheet::MemberResultFlags::CONTINUE)) { ++nEnd; } SCROW nEndRowPos = mnDataStartRow + SCROW(nEnd); //TODO: check for overflow rOutputImpl.AddRow(nRowPos); if (!vbSetBorder[nRow] ) { rOutputImpl.OutputBlockFrame(nColPos, nRowPos, mnTabEndCol, nEndRowPos); vbSetBorder[nRow] = true; } rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nColPos, nEndRowPos); if (nField == mpRowFields.size() - 2) rOutputImpl.OutputBlockFrame(nColPos + 1, nRowPos, nColPos + 1, nEndRowPos); lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, mnDataStartCol - 1, nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY); } else { lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, mnDataStartCol - 1, nRowPos, STR_PIVOT_STYLENAME_CATEGORY); } // Set flags for collapse/expand buttons and indent field header text { bool bLast = mnRowDims == (nField + 1); size_t nMinIndentLevel = mbExpandCollapse ? 1 : 0; tools::Long nIndent = o3tl::convert(13 * (bLast ? nFieldIndentLevel : nMinIndentLevel + nFieldIndentLevel), o3tl::Length::px, o3tl::Length::twip); bool bHasContinue = !bLast && nRow + 1 < nThisRowCount && (pMemberArray[nRow + 1].Flags & sheet::MemberResultFlags::CONTINUE); if (nIndent) mpDocument->ApplyAttr(nColPos, nRowPos, nTab, ScIndentItem(nIndent)); if (mbExpandCollapse && !bLast) { mpDocument->ApplyFlagsTab(nColPos, nRowPos, nColPos, nRowPos, nTab, bHasContinue ? ScMF::DpCollapse : ScMF::DpExpand); } } } else if (bSubtotal) { rOutputImpl.AddRow(nRowPos); } // Resolve formats maFormatOutput.insertFieldMember(nField, mpRowFields[nField], nRow, rMember, nColPos, nRowPos, sc::FormatResultDirection::ROW); // Apply the same number format as in data source. mpDocument->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, mpRowFields[nField].mnSrcNumFmt)); } if (!bCompactField) { // Next field should be placed in next column only if current field has a non-compact layout. ++nFieldColOffset; nFieldIndentLevel = 0; // Reset indent level. } else { ++nFieldIndentLevel; } } } void ScDPOutput::outputDataResults(SCTAB nTab) { const uno::Sequence* pRowAry = maData.getConstArray(); for (sal_Int32 nRow = 0; nRow < mnRowCount; nRow++) { SCROW nRowPos = mnDataStartRow + SCROW(nRow); //TODO: check for overflow const sheet::DataResult* pColAry = pRowAry[nRow].getConstArray(); sal_Int32 nThisColCount = pRowAry[nRow].getLength(); OSL_ENSURE(nThisColCount == mnColCount, "count mismatch"); //TODO: ??? for (sal_Int32 nCol = 0; nCol < nThisColCount; nCol++) { SCCOL nColPos = mnDataStartCol + SCCOL(nCol); //TODO: check for overflow DataCell(nColPos, nRowPos, nTab, pColAry[nCol]); } } maFormatOutput.apply(*mpDocument); } void ScDPOutput::Output() { SCTAB nTab = maStartPos.Tab(); // calculate output positions and sizes CalcSizes(); if (mbSizeOverflow || mbResultsError) // does output area exceed sheet limits? return; // nothing // Prepare format output bool bColumnFieldIsDataOnly = mnColCount == 1 && mnRowCount > 0 && mpColFields.empty(); maFormatOutput.prepare(nTab, mpColFields, mpRowFields, bColumnFieldIsDataOnly); // clear whole (new) output area // when modifying table, clear old area ! //TODO: include InsertDeleteFlags::OBJECTS ??? mpDocument->DeleteAreaTab(maStartPos.Col(), maStartPos.Row(), mnTabEndCol, mnTabEndRow, nTab, InsertDeleteFlags::ALL ); if (mbDoFilter) lcl_DoFilterButton(mpDocument, maStartPos.Col(), maStartPos.Row(), nTab); outputPageFields(nTab); // data description // (may get overwritten by first row field) if (maDataDescription.isEmpty()) { //TODO: use default string ("result") ? } mpDocument->SetString(mnTabStartCol, mnTabStartRow, nTab, maDataDescription); // set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten) if (mnDataStartRow > mnTabStartRow) lcl_SetStyleById(mpDocument, nTab, mnTabStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow - 1, STR_PIVOT_STYLENAME_TOP); lcl_SetStyleById(mpDocument, nTab, mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow, STR_PIVOT_STYLENAME_INNER); ScDPOutputImpl aOutputImpl(mpDocument, nTab, mnTabStartCol, mnTabStartRow, mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow); outputColumnHeaders(nTab, aOutputImpl); outputRowHeader(nTab, aOutputImpl); if (bColumnFieldIsDataOnly) { // the table contains exactly one data field and no column fields. // Display data description at top right corner. ScSetStringParam aParam; aParam.setTextInput(); SCCOL nCol = mnDataStartCol; SCCOL nRow = mnDataStartRow - 1; mpDocument->SetString(nCol, nRow, nTab, maDataDescription, &aParam); maFormatOutput.insertEmptyDataColumn(nCol, nRow); } outputDataResults(nTab); aOutputImpl.OutputDataArea(); } ScRange ScDPOutput::GetOutputRange( sal_Int32 nRegionType ) { using namespace ::com::sun::star::sheet; CalcSizes(); SCTAB nTab = maStartPos.Tab(); switch (nRegionType) { case DataPilotOutputRangeType::RESULT: return ScRange(mnDataStartCol, mnDataStartRow, nTab, mnTabEndCol, mnTabEndRow, nTab); case DataPilotOutputRangeType::TABLE: return ScRange(maStartPos.Col(), mnTabStartRow, nTab, mnTabEndCol, mnTabEndRow, nTab); default: OSL_ENSURE(nRegionType == DataPilotOutputRangeType::WHOLE, "ScDPOutput::GetOutputRange: unknown region type"); break; } return ScRange(maStartPos.Col(), maStartPos.Row(), nTab, mnTabEndCol, mnTabEndRow, nTab); } bool ScDPOutput::HasError() { CalcSizes(); return mbSizeOverflow || mbResultsError; } sal_Int32 ScDPOutput::GetHeaderRows() const { return mpPageFields.size() + (mbDoFilter ? 1 : 0); } namespace { void insertNames(ScDPUniqueStringSet& rNames, const uno::Sequence& rMemberResults) { for (const sheet::MemberResult& rMemberResult : rMemberResults) { if (rMemberResult.Flags & sheet::MemberResultFlags::HASMEMBER) rNames.insert(rMemberResult.Name); } } } void ScDPOutput::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension) { // Return the list of all member names in a dimension's MemberResults. // Only the dimension has to be compared because this is only used with table data, // where each dimension occurs only once. auto lFindDimension = [nDimension](const ScDPOutLevelData& rField) { return rField.mnDim == nDimension; }; // look in column fields auto colit = std::find_if(mpColFields.begin(), mpColFields.end(), lFindDimension); if (colit != mpColFields.end()) { // collect the member names insertNames(rNames, colit->maResult); return; } // look in row fields auto rowit = std::find_if(mpRowFields.begin(), mpRowFields.end(), lFindDimension); if (rowit != mpRowFields.end()) { // collect the member names insertNames(rNames, rowit->maResult); } } void ScDPOutput::SetHeaderLayout(bool bUseGrid) { mbHeaderLayout = bUseGrid; mbSizesValid = false; } namespace { void lcl_GetTableVars( sal_Int32& rGrandTotalCols, sal_Int32& rGrandTotalRows, sal_Int32& rDataLayoutIndex, std::vector& rDataNames, std::vector& rGivenNames, sheet::DataPilotFieldOrientation& rDataOrient, const uno::Reference& xSource ) { rDataLayoutIndex = -1; // invalid rGrandTotalCols = 0; rGrandTotalRows = 0; rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN; uno::Reference xSrcProp( xSource, uno::UNO_QUERY ); bool bColGrand = ScUnoHelpFunctions::GetBoolProperty( xSrcProp, SC_UNO_DP_COLGRAND); if ( bColGrand ) rGrandTotalCols = 1; // default if data layout not in columns bool bRowGrand = ScUnoHelpFunctions::GetBoolProperty( xSrcProp, SC_UNO_DP_ROWGRAND); if ( bRowGrand ) rGrandTotalRows = 1; // default if data layout not in rows if ( !xSource.is() ) return; // find index and orientation of "data layout" dimension, count data dimensions sal_Int32 nDataCount = 0; uno::Reference xDims = new ScNameToIndexAccess( xSource->getDimensions() ); tools::Long nDimCount = xDims->getCount(); for (tools::Long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); uno::Reference xDimProp( xDim, uno::UNO_QUERY ); if ( xDimProp.is() ) { sheet::DataPilotFieldOrientation eDimOrient = ScUnoHelpFunctions::GetEnumProperty( xDimProp, SC_UNO_DP_ORIENTATION, sheet::DataPilotFieldOrientation_HIDDEN ); if ( ScUnoHelpFunctions::GetBoolProperty( xDimProp, SC_UNO_DP_ISDATALAYOUT ) ) { rDataLayoutIndex = nDim; rDataOrient = eDimOrient; } if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA ) { OUString aSourceName; OUString aGivenName; ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xDim ); try { uno::Any aValue = xDimProp->getPropertyValue( SC_UNO_DP_LAYOUTNAME ); if( aValue.hasValue() ) { OUString strLayoutName; if( ( aValue >>= strLayoutName ) && !strLayoutName.isEmpty() ) aGivenName = strLayoutName; } } catch(const uno::Exception&) { } rDataNames.push_back( aSourceName ); rGivenNames.push_back( aGivenName ); ++nDataCount; } } } if ( ( rDataOrient == sheet::DataPilotFieldOrientation_COLUMN ) && bColGrand ) rGrandTotalCols = nDataCount; else if ( ( rDataOrient == sheet::DataPilotFieldOrientation_ROW ) && bRowGrand ) rGrandTotalRows = nDataCount; } } void ScDPOutput::GetRowFieldRange(SCCOL nCol, sal_Int32& nRowFieldStart, sal_Int32& nRowFieldEnd) const { if (!mbHasCompactRowField) { nRowFieldStart = nCol; nRowFieldEnd = nCol + 1; return; } if (nCol >= static_cast(maRowCompactFlags.size())) { nRowFieldStart = nRowFieldEnd = 0; return; } nRowFieldStart = -1; nRowFieldEnd = -1; SCCOL nCurCol = 0; sal_Int32 nField = 0; for (const auto bCompact: maRowCompactFlags) { if (nCurCol == nCol && nRowFieldStart == -1) nRowFieldStart = nField; if (!bCompact) ++nCurCol; ++nField; if (nCurCol == (nCol + 1) && nRowFieldStart != -1 && nRowFieldEnd == -1) { nRowFieldEnd = nField; break; } } if (nRowFieldStart != -1 && nRowFieldEnd == -1 && nCurCol == nCol) nRowFieldEnd = static_cast(maRowCompactFlags.size()); if (nRowFieldStart == -1 || nRowFieldEnd == -1) { SAL_WARN("sc.core", "ScDPOutput::GetRowFieldRange : unable to find field range for nCol = " << nCol); nRowFieldStart = nRowFieldEnd = 0; } } sal_Int32 ScDPOutput::GetRowFieldCompact(SCCOL nColQuery, SCROW nRowQuery) const { if (!mbHasCompactRowField) return nColQuery - mnTabStartCol; SCCOL nCol = nColQuery - mnTabStartCol; sal_Int32 nStartField = 0; sal_Int32 nEndField = 0; GetRowFieldRange(nCol, nStartField, nEndField); for (sal_Int32 nField = nEndField - 1; nField >= nStartField; --nField) { const uno::Sequence rSequence = mpRowFields[nField].maResult; const sheet::MemberResult* pArray = rSequence.getConstArray(); sal_Int32 nThisRowCount = rSequence.getLength(); SCROW nRow = nRowQuery - mnDataStartRow; if (nRow >= 0 && nRow < nThisRowCount) { const sheet::MemberResult& rData = pArray[nRow]; if ((rData.Flags & sheet::MemberResultFlags::HASMEMBER) && !(rData.Flags & sheet::MemberResultFlags::SUBTOTAL)) { return nField; } } } return -1; } void ScDPOutput::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData) { using namespace ::com::sun::star::sheet; SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if (nTab != maStartPos.Tab()) return; // wrong sheet // calculate output positions and sizes CalcSizes(); rPosData.PositionType = GetPositionType(rPos); switch (rPosData.PositionType) { case DataPilotTablePositionType::RESULT: { vector aFilters; GetDataResultPositionData(aFilters, rPos); DataPilotTableResultData aResData; aResData.FieldFilters = comphelper::containerToSequence(aFilters); aResData.DataFieldIndex = 0; Reference xPropSet(mxSource, UNO_QUERY); if (xPropSet.is()) { sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet, SC_UNO_DP_DATAFIELDCOUNT ); if (nDataFieldCount > 0) aResData.DataFieldIndex = (nRow - mnDataStartRow) % nDataFieldCount; } // Copy appropriate DataResult object from the cached sheet::DataResult table. if (maData.getLength() > nRow - mnDataStartRow && maData[nRow - mnDataStartRow].getLength() > nCol - mnDataStartCol) aResData.Result = maData[nRow - mnDataStartRow][nCol - mnDataStartCol]; rPosData.PositionData <<= aResData; return; } case DataPilotTablePositionType::COLUMN_HEADER: { tools::Long nField = nRow - mnTabStartRow - 1; // 1st line is used for the buttons if (nField < 0) break; if (mpColFields.size() < o3tl::make_unsigned(nField) + 1 ) break; const uno::Sequence rSequence = mpColFields[nField].maResult; if (!rSequence.hasElements()) break; const sheet::MemberResult* pArray = rSequence.getConstArray(); tools::Long nItem = nCol - mnDataStartCol; // get origin of "continue" fields while (nItem > 0 && ( pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) --nItem; if (nItem < 0) break; DataPilotTableHeaderData aHeaderData; aHeaderData.MemberName = pArray[nItem].Name; aHeaderData.Flags = pArray[nItem].Flags; aHeaderData.Dimension = static_cast(mpColFields[nField].mnDim); aHeaderData.Hierarchy = static_cast(mpColFields[nField].mnHier); aHeaderData.Level = static_cast(mpColFields[nField].mnLevel); rPosData.PositionData <<= aHeaderData; return; } case DataPilotTablePositionType::ROW_HEADER: { tools::Long nField = GetRowFieldCompact(nCol, nRow); if (nField < 0) break; if (mpRowFields.size() < o3tl::make_unsigned(nField) + 1 ) break; const uno::Sequence rSequence = mpRowFields[nField].maResult; if (!rSequence.hasElements()) break; const sheet::MemberResult* pArray = rSequence.getConstArray(); tools::Long nItem = nRow - mnDataStartRow; // get origin of "continue" fields while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) --nItem; if (nItem < 0) break; DataPilotTableHeaderData aHeaderData; aHeaderData.MemberName = pArray[nItem].Name; aHeaderData.Flags = pArray[nItem].Flags; aHeaderData.Dimension = static_cast(mpRowFields[nField].mnDim); aHeaderData.Hierarchy = static_cast(mpRowFields[nField].mnHier); aHeaderData.Level = static_cast(mpRowFields[nField].mnLevel); rPosData.PositionData <<= aHeaderData; return; } } } bool ScDPOutput::GetDataResultPositionData(vector& rFilters, const ScAddress& rPos) { // Check to make sure there is at least one data field. Reference xPropSet(mxSource, UNO_QUERY); if (!xPropSet.is()) return false; sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet, SC_UNO_DP_DATAFIELDCOUNT ); if (nDataFieldCount == 0) // No data field is present in this datapilot table. return false; // #i111421# use lcl_GetTableVars for correct size of totals and data layout position sal_Int32 nGrandTotalCols; sal_Int32 nGrandTotalRows; sal_Int32 nDataLayoutIndex; std::vector aDataNames; std::vector aGivenNames; sheet::DataPilotFieldOrientation eDataOrient; lcl_GetTableVars( nGrandTotalCols, nGrandTotalRows, nDataLayoutIndex, aDataNames, aGivenNames, eDataOrient, mxSource); SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if (nTab != maStartPos.Tab()) return false; // wrong sheet CalcSizes(); // test for data area. if (nCol < mnDataStartCol || nCol > mnTabEndCol || nRow < mnDataStartRow || nRow > mnTabEndRow) { // Cell is outside the data field area. return false; } bool bFilterByCol = (nCol <= static_cast(mnTabEndCol - nGrandTotalCols)); bool bFilterByRow = (nRow <= static_cast(mnTabEndRow - nGrandTotalRows)); // column fields for (size_t nColField = 0; nColField < mpColFields.size() && bFilterByCol; ++nColField) { if (mpColFields[nColField].mnDim == nDataLayoutIndex) // There is no sense including the data layout field for filtering. continue; sheet::DataPilotFieldFilter filter; filter.FieldName = mpColFields[nColField].maName; const uno::Sequence rSequence = mpColFields[nColField].maResult; const sheet::MemberResult* pArray = rSequence.getConstArray(); OSL_ENSURE(mnDataStartCol + rSequence.getLength() - 1 == mnTabEndCol, "ScDPOutput::GetDataFieldCellData: error in geometric assumption"); tools::Long nItem = nCol - mnDataStartCol; // get origin of "continue" fields while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) --nItem; filter.MatchValueName = pArray[nItem].Name; rFilters.push_back(filter); } // row fields for (size_t nRowField = 0; nRowField < mpRowFields.size() && bFilterByRow; ++nRowField) { if (mpRowFields[nRowField].mnDim == nDataLayoutIndex) // There is no sense including the data layout field for filtering. continue; sheet::DataPilotFieldFilter filter; filter.FieldName = mpRowFields[nRowField].maName; const uno::Sequence rSequence = mpRowFields[nRowField].maResult; const sheet::MemberResult* pArray = rSequence.getConstArray(); OSL_ENSURE(mnDataStartRow + rSequence.getLength() - 1 == mnTabEndRow, "ScDPOutput::GetDataFieldCellData: error in geometric assumption"); tools::Long nItem = nRow - mnDataStartRow; // get origin of "continue" fields while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) ) --nItem; filter.MatchValueName = pArray[nItem].Name; rFilters.push_back(filter); } return true; } namespace { OUString lcl_GetDataFieldName( std::u16string_view rSourceName, sal_Int16 eFunc ) { TranslateId pStrId; switch ( eFunc ) { case sheet::GeneralFunction2::SUM: pStrId = STR_FUN_TEXT_SUM; break; case sheet::GeneralFunction2::COUNT: case sheet::GeneralFunction2::COUNTNUMS: pStrId = STR_FUN_TEXT_COUNT; break; case sheet::GeneralFunction2::AVERAGE: pStrId = STR_FUN_TEXT_AVG; break; case sheet::GeneralFunction2::MEDIAN: pStrId = STR_FUN_TEXT_MEDIAN; break; case sheet::GeneralFunction2::MAX: pStrId = STR_FUN_TEXT_MAX; break; case sheet::GeneralFunction2::MIN: pStrId = STR_FUN_TEXT_MIN; break; case sheet::GeneralFunction2::PRODUCT: pStrId = STR_FUN_TEXT_PRODUCT; break; case sheet::GeneralFunction2::STDEV: case sheet::GeneralFunction2::STDEVP: pStrId = STR_FUN_TEXT_STDDEV; break; case sheet::GeneralFunction2::VAR: case sheet::GeneralFunction2::VARP: pStrId = STR_FUN_TEXT_VAR; break; case sheet::GeneralFunction2::NONE: case sheet::GeneralFunction2::AUTO: break; default: { assert(false); } } if (!pStrId) return OUString(); return ScResId(pStrId) + " - " + rSourceName; } } void ScDPOutput::GetDataDimensionNames( OUString& rSourceName, OUString& rGivenName, const uno::Reference& xDim ) { uno::Reference xDimProp( xDim, uno::UNO_QUERY ); uno::Reference xDimName( xDim, uno::UNO_QUERY ); if ( !(xDimProp.is() && xDimName.is()) ) return; // Asterisks are added in ScDPSaveData::WriteToSource to create unique names. //TODO: preserve original name there? rSourceName = ScDPUtil::getSourceDimensionName(xDimName->getName()); // Generate "given name" the same way as in dptabres. //TODO: Should use a stored name when available sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty( xDimProp, SC_UNO_DP_FUNCTION2, sheet::GeneralFunction2::NONE ); rGivenName = lcl_GetDataFieldName( rSourceName, eFunc ); } bool ScDPOutput::IsFilterButton( const ScAddress& rPos ) { SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if (nTab != maStartPos.Tab() || !mbDoFilter) return false; // wrong sheet or no button at all // filter button is at top left return nCol == maStartPos.Col() && nRow == maStartPos.Row(); } tools::Long ScDPOutput::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient ) { SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if (nTab != maStartPos.Tab()) return -1; // wrong sheet // calculate output positions and sizes CalcSizes(); // test for column header if ( nRow == mnTabStartRow && nCol >= mnDataStartCol && o3tl::make_unsigned(nCol) < mnDataStartCol + mpColFields.size()) { rOrient = sheet::DataPilotFieldOrientation_COLUMN; tools::Long nField = nCol - mnDataStartCol; return mpColFields[nField].mnDim; } // test for row header if ( nRow+1 == mnDataStartRow && nCol >= mnTabStartCol && o3tl::make_unsigned(nCol) < mnTabStartCol + mpRowFields.size() ) { rOrient = sheet::DataPilotFieldOrientation_ROW; tools::Long nField = nCol - mnTabStartCol; return mpRowFields[nField].mnDim; } // test for page field SCROW nPageStartRow = maStartPos.Row() + (mbDoFilter ? 1 : 0); if ( nCol == maStartPos.Col() && nRow >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + mpPageFields.size() ) { rOrient = sheet::DataPilotFieldOrientation_PAGE; tools::Long nField = nRow - nPageStartRow; return mpPageFields[nField].mnDim; } //TODO: single data field (?) rOrient = sheet::DataPilotFieldOrientation_HIDDEN; return -1; // invalid } bool ScDPOutput::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, tools::Long nDragDim, tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos ) { // Rectangle instead of ScRange for rPosRect to allow for negative values SCCOL nCol = rPos.Col(); SCROW nRow = rPos.Row(); SCTAB nTab = rPos.Tab(); if ( nTab != maStartPos.Tab() ) return false; // wrong sheet // calculate output positions and sizes CalcSizes(); // test for column header if ( nCol >= mnDataStartCol && nCol <= mnTabEndCol && nRow + 1 >= mnMemberStartRow && o3tl::make_unsigned(nRow) < mnMemberStartRow + mpColFields.size()) { tools::Long nField = nRow - mnMemberStartRow; if (nField < 0) { nField = 0; bMouseTop = true; } //TODO: find start of dimension rPosRect = tools::Rectangle(mnDataStartCol, mnMemberStartRow + nField, mnTabEndCol, mnMemberStartRow + nField - 1); bool bFound = false; // is this within the same orientation? bool bBeforeDrag = false; bool bAfterDrag = false; for (tools::Long nPos=0; o3tl::make_unsigned(nPos) nPos ) bAfterDrag = true; } } if ( bFound ) { if (!bBeforeDrag) { rPosRect.AdjustBottom( 1 ); if (bAfterDrag) rPosRect.AdjustTop( 1 ); } } else { if ( !bMouseTop ) { rPosRect.AdjustTop( 1 ); rPosRect.AdjustBottom( 1 ); ++nField; } } rOrient = sheet::DataPilotFieldOrientation_COLUMN; rDimPos = nField; //!... return true; } // test for row header // special case if no row fields bool bSpecial = ( nRow+1 >= mnDataStartRow && nRow <= mnTabEndRow && mpRowFields.empty() && nCol == mnTabStartCol && bMouseLeft ); if ( bSpecial || ( nRow+1 >= mnDataStartRow && nRow <= mnTabEndRow && nCol + 1 >= mnTabStartCol && o3tl::make_unsigned(nCol) < mnTabStartCol + mpRowFields.size() ) ) { tools::Long nField = nCol - mnTabStartCol; //TODO: find start of dimension rPosRect = tools::Rectangle(mnTabStartCol + nField, mnDataStartRow - 1, mnTabStartCol + nField - 1, mnTabEndRow); bool bFound = false; // is this within the same orientation? bool bBeforeDrag = false; bool bAfterDrag = false; for (tools::Long nPos = 0; o3tl::make_unsigned(nPos) < mpRowFields.size() && !bFound; nPos++) { if (mpRowFields[nPos].mnDim == nDragDim) { bFound = true; if ( nField < nPos ) bBeforeDrag = true; else if ( nField > nPos ) bAfterDrag = true; } } if ( bFound ) { if (!bBeforeDrag) { rPosRect.AdjustRight( 1 ); if (bAfterDrag) rPosRect.AdjustLeft( 1 ); } } else { if ( !bMouseLeft ) { rPosRect.AdjustLeft( 1 ); rPosRect.AdjustRight( 1 ); ++nField; } } rOrient = sheet::DataPilotFieldOrientation_ROW; rDimPos = nField; //!... return true; } // test for page fields SCROW nPageStartRow = maStartPos.Row() + (mbDoFilter ? 1 : 0); if (nCol >= maStartPos.Col() && nCol <= mnTabEndCol && nRow + 1 >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + mpPageFields.size()) { tools::Long nField = nRow - nPageStartRow; if (nField < 0) { nField = 0; bMouseTop = true; } //TODO: find start of dimension rPosRect = tools::Rectangle(maStartPos.Col(), nPageStartRow + nField, mnTabEndCol, nPageStartRow + nField - 1); bool bFound = false; // is this within the same orientation? bool bBeforeDrag = false; bool bAfterDrag = false; for (tools::Long nPos = 0; o3tl::make_unsigned(nPos) < mpPageFields.size() && !bFound; nPos++) { if (mpPageFields[nPos].mnDim == nDragDim) { bFound = true; if ( nField < nPos ) bBeforeDrag = true; else if ( nField > nPos ) bAfterDrag = true; } } if ( bFound ) { if (!bBeforeDrag) { rPosRect.AdjustBottom( 1 ); if (bAfterDrag) rPosRect.AdjustTop( 1 ); } } else { if ( !bMouseTop ) { rPosRect.AdjustTop( 1 ); rPosRect.AdjustBottom( 1 ); ++nField; } } rOrient = sheet::DataPilotFieldOrientation_PAGE; rDimPos = nField; //!... return true; } return false; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */