From 0753600cc28a2dc14e998c2fb7ec867625866e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Sat, 25 Jul 2026 22:40:21 +0200 Subject: [PATCH] sc: draw all pivot table output formatting in one class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The formatting of the pivot table output was spread across ScDPOutput: cell styles and frames were applied inline while the table content was written, and ScDPOutputImpl drew the block frames. Add sc::pivot::StyleOutput, which handles all the drawing. ScDPOutput now only records the sections of the table (layout geometry, field cells, member spans, subtotal and grand total rows and columns, indents and expand or collapse buttons). The cell styles are applied while the sections are recorded, at the same points where they were applied before, so they always come before any direct formatting (number formats, pivot table format attributes) reaches the cells. The block frames, the indents and the expander buttons are then drawn by apply() in one pass over the collected sections. The drawn output stays the same as before, and a different look, like the OOXML pivot table styles, can later be drawn from the same sections. Signed-off-by: Tomaž Vajngerl Change-Id: Ia628de6badf511d299f36bf474194ecad17a9c56 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8253 Reviewed-by: Mike Kaganski Tested-by: Tomaž Vajngerl (cherry picked from commit 9f5a36c822b170835c55143716be9eca28ac3e91) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209430 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- sc/Library_sc.mk | 1 + sc/inc/dpoutput.hxx | 7 +- sc/inc/pivot/StyleOutput.hxx | 189 ++++++++++ sc/source/core/data/dpoutput.cxx | 359 ++---------------- sc/source/core/data/pivot/StyleOutput.cxx | 437 ++++++++++++++++++++++ 5 files changed, 672 insertions(+), 321 deletions(-) create mode 100644 sc/inc/pivot/StyleOutput.hxx create mode 100644 sc/source/core/data/pivot/StyleOutput.cxx diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk index 22269a9a5781..3b9bffd444d5 100644 --- a/sc/Library_sc.mk +++ b/sc/Library_sc.mk @@ -217,6 +217,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\ sc/source/core/data/userdat \ sc/source/core/data/validat \ sc/source/core/data/pivot/PivotTableResultTraverser \ + sc/source/core/data/pivot/StyleOutput \ sc/source/core/tool/addincfg \ sc/source/core/tool/addincol \ sc/source/core/tool/addinhelpid \ diff --git a/sc/inc/dpoutput.hxx b/sc/inc/dpoutput.hxx index eee3f78caf24..385d50ede7df 100644 --- a/sc/inc/dpoutput.hxx +++ b/sc/inc/dpoutput.hxx @@ -44,14 +44,15 @@ namespace com::sun::star::sheet { namespace tools { class Rectangle; } class ScDocument; struct ScDPOutLevelData; -class ScDPOutputImpl; class ScDPObject; +namespace sc::pivot { class StyleOutput; } class ScDPOutput { private: ScDocument* mpDocument; sc::FormatOutput maFormatOutput; + std::unique_ptr mpStyleOutput; css::uno::Reference mxSource; ScAddress maStartPos; std::vector mpColFields; @@ -114,8 +115,8 @@ private: sal_Int32 GetRowFieldCompact(SCCOL nColQuery, SCROW nRowQuery) const; void outputPageFields(SCTAB nTab); - void outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl); - void outputRowHeader(SCTAB nTab, ScDPOutputImpl& rOutputImpl); + void outputColumnHeaders(SCTAB nTab); + void outputRowHeader(SCTAB nTab); void outputDataResults(SCTAB nTab); public: diff --git a/sc/inc/pivot/StyleOutput.hxx b/sc/inc/pivot/StyleOutput.hxx new file mode 100644 index 000000000000..7b4ebb14bc96 --- /dev/null +++ b/sc/inc/pivot/StyleOutput.hxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * Copyright the LibreOffice contributors. + * + * 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 . + */ + +#pragma once + +#include +#include +#include + +#include + +class ScDocument; + +namespace sc::pivot +{ +/** Cell positions that describe the layout of the pivot table output area. + * + * The whole table spans from the tab start to the tab end position. The data (result) cells + * start at the data start position; the cells left of the data start column hold the row + * field members and the cells above the data start row hold the column field members, which + * begin at the member start position. */ +struct Geometry +{ + SCTAB mnTab = -1; + SCCOL mnTabStartCol = -1; + SCROW mnTabStartRow = -1; + SCCOL mnMemberStartCol = -1; + SCROW mnMemberStartRow = -1; + SCCOL mnDataStartCol = -1; + SCROW mnDataStartRow = -1; + SCCOL mnTabEndCol = -1; + SCROW mnTabEndRow = -1; + + size_t mnColumnFieldCount = 0; + size_t mnRowFieldCount = 0; +}; + +/** A cell with a field caption. When bFrame is true, the cell is part of the table area and + * carries a thin frame around it. */ +struct FieldCell +{ + SCCOL nCol = -1; + SCROW nRow = -1; + bool bFrame = false; +}; + +/** A cell holding the selected value of a page field. */ +struct PageFieldValueCell +{ + SCCOL nCol = -1; + SCROW nRow = -1; +}; + +/** One member of a column field, spanning the columns nStartCol to nEndCol in the header row + * nRow. nField is the position of the field among the column fields. */ +struct ColumnMemberSpan +{ + size_t nField = 0; + SCCOL nStartCol = -1; + SCCOL nEndCol = -1; + SCROW nRow = -1; +}; + +/** One member of a row field, spanning the rows nStartRow to nEndRow in the header column + * nCol. nField is the position of the field among the row fields. */ +struct RowMemberSpan +{ + size_t nField = 0; + SCCOL nCol = -1; + SCROW nStartRow = -1; + SCROW nEndRow = -1; +}; + +/** A column holding subtotal results. The header cells reach from nStartRow down to the row + * above the data start row, and the result cells fill the column below. bGrandTotal is true + * for the grand total column. */ +struct SubtotalColumn +{ + SCCOL nCol = -1; + SCROW nStartRow = -1; + bool bGrandTotal = false; +}; + +/** A row holding subtotal results. The header cells reach from nStartCol to the column left + * of the data start column, and the result cells fill the row to the right. bGrandTotal is + * true for the grand total row. */ +struct SubtotalRow +{ + SCROW nRow = -1; + SCCOL nStartCol = -1; + bool bGrandTotal = false; +}; + +/** A row field member cell with indented content. The indent value is in twips. */ +struct Indent +{ + SCCOL nCol = -1; + SCROW nRow = -1; + tools::Long nIndent = 0; +}; + +/** A row field member cell that shows an expand or collapse button. */ +struct Expander +{ + SCCOL nCol = -1; + SCROW nRow = -1; + ScMF nFlags = ScMF::NONE; +}; + +/** Collects the sections of a pivot table while the table content is written to the sheet and + * handles the whole visual formatting of the pivot table. The cell styles are applied as the + * sections are added, before any direct formatting reaches the cells, and apply() then draws + * the block frames, the member indents and the expand and collapse buttons in one pass over + * the collected sections. */ +class StyleOutput +{ +private: + ScDocument& mrDocument; + + Geometry maGeometry; + + std::vector maFieldCells; + std::vector maPageFieldValueCells; + std::vector maColumnMemberSpans; + std::vector maRowMemberSpans; + std::vector maSubtotalColumns; + std::vector maSubtotalRows; + std::vector maIndentCells; + std::vector maExpanderCells; + +public: + StyleOutput(ScDocument& rDocument) + : mrDocument(rDocument) + { + } + + void setGeometry(Geometry const& rGeometry) { maGeometry = rGeometry; } + + /** Applies the base cell styles of the table: the header area on top and the data area + * below. The more specific styles of the sections are applied over them. */ + void applyAreaStyles(); + + void addFieldCell(SCCOL nCol, SCROW nRow, bool bFrame); + + void addPageFieldValueCell(SCCOL nCol, SCROW nRow) + { + maPageFieldValueCells.push_back(PageFieldValueCell{ nCol, nRow }); + } + + void addColumnMemberSpan(size_t nField, SCCOL nStartCol, SCCOL nEndCol, SCROW nRow); + + void addRowMemberSpan(size_t nField, SCCOL nCol, SCROW nStartRow, SCROW nEndRow); + + void addSubtotalColumn(SCCOL nCol, SCROW nStartRow, bool bGrandTotal); + + void addSubtotalRow(SCROW nRow, SCCOL nStartCol, bool bGrandTotal); + + void addIndent(SCCOL nCol, SCROW nRow, tools::Long nIndent) + { + maIndentCells.push_back(Indent{ nCol, nRow, nIndent }); + } + + void addExpander(SCCOL nCol, SCROW nRow, ScMF nFlags) + { + maExpanderCells.push_back(Expander{ nCol, nRow, nFlags }); + } + + void clear(); + void apply(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sc/source/core/data/dpoutput.cxx b/sc/source/core/data/dpoutput.cxx index 38546605482a..1cd640be669a 100644 --- a/sc/source/core/data/dpoutput.cxx +++ b/sc/source/core/data/dpoutput.cxx @@ -20,10 +20,6 @@ #include #include -#include -#include -#include -#include #include #include #include @@ -36,14 +32,13 @@ #include #include #include -#include -#include #include #include #include #include #include #include +#include #include #include @@ -73,11 +68,6 @@ using namespace com::sun::star; -#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 @@ -90,228 +80,9 @@ struct ScDPOutLevelDataComparator }; } // end anonymous namespace -class ScDPOutputImpl -{ - ScDocument* mpDoc; - sal_uInt16 mnTab; - std::vector< bool > 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 ) @@ -483,6 +254,7 @@ ScDPOutput::ScDPOutput(ScDocument* pDocument, uno::Reference(*pDocument)) , mxSource(std::move(xSource)) , maStartPos(rPosition) , mnColFormatCount(0) @@ -772,23 +544,11 @@ void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab, if ( !(nFlags & sheet::MemberResultFlags::SUBTOTAL) ) return; - ScDPOutputImpl outputimp(mpDocument, nTab, - mnTabStartCol, mnTabStartRow, - mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow); - //TODO: limit frames to horizontal or vertical? + bool bGrandTotal = (nFlags & sheet::MemberResultFlags::GRANDTOTAL) != 0; 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 ); - } + mpStyleOutput->addSubtotalColumn(nCol, mnMemberStartRow + SCROW(nLevel), bGrandTotal); 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); - } + mpStyleOutput->addSubtotalRow(nRow, mnMemberStartCol + SCCOL(nLevel), bGrandTotal); } void ScDPOutput::MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bRowField) @@ -808,7 +568,7 @@ void ScDPOutput::MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bRowFie nMergeFlag |= ScMF::ButtonPopup2; mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag); - lcl_SetStyleById(mpDocument, nTab, nCol, nRow, nCol, nRow, STR_PIVOT_STYLENAME_FIELDNAME); + mpStyleOutput->addFieldCell(nCol, nRow, /*bFrame*/ false); } void ScDPOutput::FieldCell( @@ -821,9 +581,6 @@ void ScDPOutput::FieldCell( 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) @@ -843,7 +600,7 @@ void ScDPOutput::FieldCell( mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag); } - lcl_SetStyleById(mpDocument, nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME); + mpStyleOutput->addFieldCell(nCol, nRow, bInTable); } static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) @@ -995,11 +752,11 @@ void ScDPOutput::outputPageFields(SCTAB nTab) aParam.setTextInput(); mpDocument->SetString(nFieldCol, nHeaderRow, nTab, aPageValue, &aParam); - lcl_SetFrame(mpDocument, nTab, nFieldCol, nHeaderRow, nFieldCol, nHeaderRow, 20); + mpStyleOutput->addPageFieldValueCell(nFieldCol, nHeaderRow); } } -void ScDPOutput::outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl) +void ScDPOutput::outputColumnHeaders(SCTAB nTab) { size_t nNumColFields = mpColFields.size(); @@ -1038,27 +795,7 @@ void ScDPOutput::outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl) ++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); + mpStyleOutput->addColumnMemberSpan(nField, nColPos, nEndColPos, nRowPos); } // Resolve formats @@ -1067,15 +804,11 @@ void ScDPOutput::outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl) // 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) +void ScDPOutput::outputRowHeader(SCTAB nTab) { - 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(); @@ -1104,31 +837,13 @@ void ScDPOutput::outputRowHeader(SCTAB nTab, ScDPOutputImpl& rOutputImpl) 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)) { - 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); + ++nEnd; } + SCROW nEndRowPos = mnDataStartRow + SCROW(nEnd); //TODO: check for overflow + mpStyleOutput->addRowMemberSpan(nField, nColPos, nRowPos, nEndRowPos); // Set flags for collapse/expand buttons and indent field header text { @@ -1137,18 +852,14 @@ void ScDPOutput::outputRowHeader(SCTAB nTab, ScDPOutputImpl& rOutputImpl) 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)); + mpStyleOutput->addIndent(nColPos, nRowPos, nIndent); if (mbExpandCollapse && !bLast) { - mpDocument->ApplyFlagsTab(nColPos, nRowPos, nColPos, nRowPos, nTab, + mpStyleOutput->addExpander(nColPos, nRowPos, 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); @@ -1200,6 +911,25 @@ void ScDPOutput::Output() if (mbSizeOverflow || mbResultsError) // does output area exceed sheet limits? return; // nothing + // Collect the pivot table sections while the content is written, so all the visual + // formatting can be drawn in one pass at the end. + mpStyleOutput->clear(); + { + sc::pivot::Geometry aGeometry; + aGeometry.mnTab = nTab; + aGeometry.mnTabStartCol = mnTabStartCol; + aGeometry.mnTabStartRow = mnTabStartRow; + aGeometry.mnMemberStartCol = mnMemberStartCol; + aGeometry.mnMemberStartRow = mnMemberStartRow; + aGeometry.mnDataStartCol = mnDataStartCol; + aGeometry.mnDataStartRow = mnDataStartRow; + aGeometry.mnTabEndCol = mnTabEndCol; + aGeometry.mnTabEndRow = mnTabEndRow; + aGeometry.mnColumnFieldCount = mpColFields.size(); + aGeometry.mnRowFieldCount = mpRowFields.size(); + mpStyleOutput->setGeometry(aGeometry); + } + // Prepare format output bool bColumnFieldIsDataOnly = mnColCount == 1 && mnRowCount > 0 && mpColFields.empty(); maFormatOutput.prepare(nTab, mpColFields, mpRowFields, bColumnFieldIsDataOnly); @@ -1239,18 +969,11 @@ void ScDPOutput::Output() } mpDocument->SetString(mnTabStartCol, mnTabStartRow, nTab, maDataDescription); - // set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten) + mpStyleOutput->applyAreaStyles(); - 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); + outputColumnHeaders(nTab); - ScDPOutputImpl aOutputImpl(mpDocument, nTab, mnTabStartCol, mnTabStartRow, - mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow); - - outputColumnHeaders(nTab, aOutputImpl); - - outputRowHeader(nTab, aOutputImpl); + outputRowHeader(nTab); if (bColumnFieldIsDataOnly) { @@ -1266,7 +989,7 @@ void ScDPOutput::Output() outputDataResults(nTab); - aOutputImpl.OutputDataArea(); + mpStyleOutput->apply(); } void ScDPOutput::Output(const ScRange& rOldRange, bool bCheckForSpill) diff --git a/sc/source/core/data/pivot/StyleOutput.cxx b/sc/source/core/data/pivot/StyleOutput.cxx new file mode 100644 index 000000000000..e5a707668b6e --- /dev/null +++ b/sc/source/core/data/pivot/StyleOutput.cxx @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * Copyright the LibreOffice contributors. + * + * 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 + +namespace +{ +constexpr sal_uInt16 SC_DP_FRAME_INNER_BOLD = 20; +constexpr sal_uInt16 SC_DP_FRAME_OUTER_BOLD = 40; + +constexpr Color SC_DP_FRAME_COLOR(0, 0, 0); //( 0x20, 0x40, 0x68 ) + +void lcl_SetStyleById(ScDocument& rDoc, 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 = rDoc.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)); + } + + rDoc.ApplyStyleAreaTab(nCol1, nRow1, nCol2, nRow2, nTab, *pStyle); +} + +void lcl_SetFrame(ScDocument& rDoc, 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); + + rDoc.ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo); +} + +/** Draws the block frames of the table: each block gets a box around it, with a bold line on the + * outer edges of the table. The rows and columns where subtotal and member blocks begin are + * collected with AddRow and AddCol, and OutputDataArea then frames the blocks between them. */ +class BlockFrameOutput +{ + ScDocument& mrDocument; + SCTAB mnTab; + std::vector mbNeedLineCols; + std::vector mnCols; + + std::vector mbNeedLineRows; + std::vector mnRows; + + SCCOL mnTabStartCol; + SCROW mnTabStartRow; + + SCCOL mnDataStartCol; + SCROW mnDataStartRow; + SCCOL mnTabEndCol; + SCROW mnTabEndRow; + +public: + BlockFrameOutput(ScDocument& rDocument, sc::pivot::Geometry const& rGeometry) + : mrDocument(rDocument) + , mnTab(rGeometry.mnTab) + , mnTabStartCol(rGeometry.mnTabStartCol) + , mnTabStartRow(rGeometry.mnTabStartRow) + , mnDataStartCol(rGeometry.mnDataStartCol) + , mnDataStartRow(rGeometry.mnDataStartRow) + , mnTabEndCol(rGeometry.mnTabEndCol) + , mnTabEndRow(rGeometry.mnTabEndRow) + { + mbNeedLineCols.resize(mnTabEndCol - mnDataStartCol + 1, false); + mbNeedLineRows.resize(mnTabEndRow - mnDataStartRow + 1, false); + } + + void AddRow(SCROW nRow) + { + if (!mbNeedLineRows[nRow - mnDataStartRow]) + { + mbNeedLineRows[nRow - mnDataStartRow] = true; + mnRows.push_back(nRow); + } + } + + void AddCol(SCCOL nCol) + { + if (!mbNeedLineCols[nCol - mnDataStartCol]) + { + mbNeedLineCols[nCol - mnDataStartCol] = true; + mnCols.push_back(nCol); + } + } + + void OutputBlockFrame(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + bool bHori = false) + { + 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); + + mrDocument.ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow, mnTab), + aBox, aBoxInfo); + } + + void 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); + } +}; + +} // end anonymous namespace + +namespace sc::pivot +{ +void StyleOutput::applyAreaStyles() +{ + SCTAB nTab = maGeometry.mnTab; + + if (maGeometry.mnDataStartRow > maGeometry.mnTabStartRow) + { + lcl_SetStyleById(mrDocument, nTab, maGeometry.mnTabStartCol, maGeometry.mnTabStartRow, + maGeometry.mnTabEndCol, maGeometry.mnDataStartRow - 1, + STR_PIVOT_STYLENAME_TOP); + } + lcl_SetStyleById(mrDocument, nTab, maGeometry.mnDataStartCol, maGeometry.mnDataStartRow, + maGeometry.mnTabEndCol, maGeometry.mnTabEndRow, STR_PIVOT_STYLENAME_INNER); +} + +void StyleOutput::addFieldCell(SCCOL nCol, SCROW nRow, bool bFrame) +{ + maFieldCells.push_back(FieldCell{ nCol, nRow, bFrame }); + + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nCol, nRow, nCol, nRow, + STR_PIVOT_STYLENAME_FIELDNAME); +} + +void StyleOutput::addColumnMemberSpan(size_t nField, SCCOL nStartCol, SCCOL nEndCol, SCROW nRow) +{ + maColumnMemberSpans.push_back(ColumnMemberSpan{ nField, nStartCol, nEndCol, nRow }); + + if (nField + 1 < maGeometry.mnColumnFieldCount) + { + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nStartCol, nRow, nEndCol, + maGeometry.mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY); + } + else + { + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nStartCol, nRow, nStartCol, + maGeometry.mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY); + } +} + +void StyleOutput::addRowMemberSpan(size_t nField, SCCOL nCol, SCROW nStartRow, SCROW nEndRow) +{ + maRowMemberSpans.push_back(RowMemberSpan{ nField, nCol, nStartRow, nEndRow }); + + if (nField + 1 < maGeometry.mnRowFieldCount) + { + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nCol, nStartRow, + maGeometry.mnDataStartCol - 1, nEndRow, STR_PIVOT_STYLENAME_CATEGORY); + } + else + { + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nCol, nStartRow, + maGeometry.mnDataStartCol - 1, nStartRow, STR_PIVOT_STYLENAME_CATEGORY); + } +} + +void StyleOutput::addSubtotalColumn(SCCOL nCol, SCROW nStartRow, bool bGrandTotal) +{ + maSubtotalColumns.push_back(SubtotalColumn{ nCol, nStartRow, bGrandTotal }); + + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nCol, nStartRow, nCol, + maGeometry.mnDataStartRow - 1, STR_PIVOT_STYLENAME_TITLE); + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nCol, maGeometry.mnDataStartRow, nCol, + maGeometry.mnTabEndRow, STR_PIVOT_STYLENAME_RESULT); +} + +void StyleOutput::addSubtotalRow(SCROW nRow, SCCOL nStartCol, bool bGrandTotal) +{ + maSubtotalRows.push_back(SubtotalRow{ nRow, nStartCol, bGrandTotal }); + + lcl_SetStyleById(mrDocument, maGeometry.mnTab, nStartCol, nRow, maGeometry.mnDataStartCol - 1, + nRow, STR_PIVOT_STYLENAME_TITLE); + lcl_SetStyleById(mrDocument, maGeometry.mnTab, maGeometry.mnDataStartCol, nRow, + maGeometry.mnTabEndCol, nRow, STR_PIVOT_STYLENAME_RESULT); +} + +void StyleOutput::clear() +{ + maGeometry = Geometry(); + maFieldCells.clear(); + maPageFieldValueCells.clear(); + maColumnMemberSpans.clear(); + maRowMemberSpans.clear(); + maSubtotalColumns.clear(); + maSubtotalRows.clear(); + maIndentCells.clear(); + maExpanderCells.clear(); +} + +void StyleOutput::apply() +{ + SCTAB nTab = maGeometry.mnTab; + + // Field caption cells and page field value cells + for (FieldCell const& rCell : maFieldCells) + { + if (rCell.bFrame) + lcl_SetFrame(mrDocument, nTab, rCell.nCol, rCell.nRow, rCell.nCol, rCell.nRow, + SC_DP_FRAME_INNER_BOLD); + } + for (PageFieldValueCell const& rCell : maPageFieldValueCells) + { + lcl_SetFrame(mrDocument, nTab, rCell.nCol, rCell.nRow, rCell.nCol, rCell.nRow, + SC_DP_FRAME_INNER_BOLD); + } + + BlockFrameOutput aBlockFrames(mrDocument, maGeometry); + + // Column field members + for (ColumnMemberSpan const& rSpan : maColumnMemberSpans) + { + if (rSpan.nField + 1 < maGeometry.mnColumnFieldCount) + { + if (rSpan.nField + 2 == maGeometry.mnColumnFieldCount) + { + aBlockFrames.AddCol(rSpan.nStartCol); + if (rSpan.nStartCol + 1 == rSpan.nEndCol) + aBlockFrames.OutputBlockFrame(rSpan.nStartCol, rSpan.nRow, rSpan.nEndCol, + rSpan.nRow + 1, true); + } + else + { + aBlockFrames.OutputBlockFrame(rSpan.nStartCol, rSpan.nRow, rSpan.nEndCol, + rSpan.nRow); + } + } + } + + // A single column field shares its header row with the data description, so the frame around + // the description row spans the whole width of the data columns. + if (maGeometry.mnColumnFieldCount == 1 + && maGeometry.mnMemberStartRow > maGeometry.mnTabStartRow) + { + aBlockFrames.OutputBlockFrame(maGeometry.mnDataStartCol, maGeometry.mnTabStartRow, + maGeometry.mnTabEndCol, maGeometry.mnMemberStartRow - 1); + } + + // Subtotal and grand total columns + for (SubtotalColumn const& rColumn : maSubtotalColumns) + { + aBlockFrames.AddCol(rColumn.nCol); + aBlockFrames.OutputBlockFrame(rColumn.nCol, rColumn.nStartRow, rColumn.nCol, + maGeometry.mnDataStartRow - 1); + } + + // Row field members. The first member that begins in a row also gets a frame around the whole + // width of the table for its row span. + std::vector aBorderSet(maGeometry.mnTabEndRow - maGeometry.mnDataStartRow + 1, false); + for (RowMemberSpan const& rSpan : maRowMemberSpans) + { + if (rSpan.nField + 1 < maGeometry.mnRowFieldCount) + { + aBlockFrames.AddRow(rSpan.nStartRow); + if (!aBorderSet[rSpan.nStartRow - maGeometry.mnDataStartRow]) + { + aBlockFrames.OutputBlockFrame(rSpan.nCol, rSpan.nStartRow, maGeometry.mnTabEndCol, + rSpan.nEndRow); + aBorderSet[rSpan.nStartRow - maGeometry.mnDataStartRow] = true; + } + aBlockFrames.OutputBlockFrame(rSpan.nCol, rSpan.nStartRow, rSpan.nCol, rSpan.nEndRow); + + if (rSpan.nField == maGeometry.mnRowFieldCount - 2) + aBlockFrames.OutputBlockFrame(rSpan.nCol + 1, rSpan.nStartRow, rSpan.nCol + 1, + rSpan.nEndRow); + } + } + + // Subtotal and grand total rows + for (SubtotalRow const& rRow : maSubtotalRows) + { + aBlockFrames.AddRow(rRow.nRow); + aBlockFrames.OutputBlockFrame(rRow.nStartCol, rRow.nRow, maGeometry.mnDataStartCol - 1, + rRow.nRow); + } + + // Member indents and expand/collapse buttons + for (Indent const& rIndent : maIndentCells) + mrDocument.ApplyAttr(rIndent.nCol, rIndent.nRow, nTab, ScIndentItem(rIndent.nIndent)); + + for (Expander const& rExpander : maExpanderCells) + { + mrDocument.ApplyFlagsTab(rExpander.nCol, rExpander.nRow, rExpander.nCol, rExpander.nRow, + nTab, rExpander.nFlags); + } + + aBlockFrames.OutputDataArea(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */