xref: /core/sc/source/core/data/dpoutput.cxx (revision c3791687)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <scitems.hxx>
21 
22 #include <comphelper/sequence.hxx>
23 #include <editeng/borderline.hxx>
24 #include <editeng/boxitem.hxx>
25 #include <editeng/wghtitem.hxx>
26 #include <editeng/justifyitem.hxx>
27 #include <o3tl/safeint.hxx>
28 #include <osl/diagnose.h>
29 #include <svl/itemset.hxx>
30 
31 #include <dpoutput.hxx>
32 #include <dpobject.hxx>
33 #include <document.hxx>
34 #include <attrib.hxx>
35 #include <formula/errorcodes.hxx>
36 #include <miscuno.hxx>
37 #include <globstr.hrc>
38 #include <stlpool.hxx>
39 #include <stlsheet.hxx>
40 #include <scresid.hxx>
41 #include <unonames.hxx>
42 #include <strings.hrc>
43 #include <stringutil.hxx>
44 #include <dputil.hxx>
45 #include <pivot/DPOutLevelData.hxx>
46 
47 #include <com/sun/star/beans/XPropertySet.hpp>
48 #include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
49 #include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
50 #include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
51 #include <com/sun/star/sheet/DataPilotTableResultData.hpp>
52 #include <com/sun/star/sheet/MemberResultFlags.hpp>
53 #include <com/sun/star/sheet/DataResultFlags.hpp>
54 #include <com/sun/star/sheet/DataPilotTablePositionType.hpp>
55 #include <com/sun/star/sheet/GeneralFunction2.hpp>
56 #include <com/sun/star/sheet/MemberResult.hpp>
57 #include <com/sun/star/sheet/XDataPilotMemberResults.hpp>
58 #include <com/sun/star/sheet/XDataPilotResults.hpp>
59 #include <com/sun/star/sheet/XDimensionsSupplier.hpp>
60 #include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
61 #include <com/sun/star/sheet/XLevelsSupplier.hpp>
62 #include <com/sun/star/sheet/XMembersAccess.hpp>
63 #include <com/sun/star/sheet/XMembersSupplier.hpp>
64 #include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
65 #include <com/sun/star/sheet/DataPilotFieldLayoutMode.hpp>
66 
67 #include <limits>
68 #include <string_view>
69 #include <utility>
70 #include <vector>
71 #include <iostream>
72 
73 using namespace com::sun::star;
74 using ::std::vector;
75 using ::com::sun::star::beans::XPropertySet;
76 using ::com::sun::star::uno::Sequence;
77 using ::com::sun::star::uno::UNO_QUERY;
78 using ::com::sun::star::uno::Reference;
79 using ::com::sun::star::sheet::DataPilotTablePositionData;
80 using ::com::sun::star::sheet::DataPilotTableResultData;
81 
82 #define SC_DP_FRAME_INNER_BOLD      20
83 #define SC_DP_FRAME_OUTER_BOLD      40
84 
85 #define SC_DP_FRAME_COLOR           Color(0,0,0) //( 0x20, 0x40, 0x68 )
86 
87 namespace
88 {
89 struct ScDPOutLevelDataComparator
90 {
operator ()__anone6cdbf010111::ScDPOutLevelDataComparator91     bool operator()(const ScDPOutLevelData & rA, const ScDPOutLevelData & rB)
92     {
93         return rA.mnDimPos<rB.mnDimPos || ( rA.mnDimPos==rB.mnDimPos && rA.mnHier<rB.mnHier ) ||
94         ( rA.mnDimPos==rB.mnDimPos && rA.mnHier==rB.mnHier && rA.mnLevel<rB.mnLevel );
95     }
96 };
97 } // end anonymous namespace
98 
99 class ScDPOutputImpl
100 {
101     ScDocument*         mpDoc;
102     sal_uInt16          mnTab;
103     ::std::vector< bool > mbNeedLineCols;
104     ::std::vector< SCCOL > mnCols;
105 
106     ::std::vector< bool > mbNeedLineRows;
107     ::std::vector< SCROW > mnRows;
108 
109     SCCOL   mnTabStartCol;
110     SCROW   mnTabStartRow;
111 
112     SCCOL   mnDataStartCol;
113     SCROW   mnDataStartRow;
114     SCCOL   mnTabEndCol;
115     SCROW   mnTabEndRow;
116 
117 public:
118     ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
119         SCCOL   nTabStartCol,
120         SCROW   nTabStartRow,
121         SCCOL nDataStartCol,
122         SCROW nDataStartRow,
123         SCCOL nTabEndCol,
124         SCROW nTabEndRow );
125     bool AddRow( SCROW nRow );
126     bool AddCol( SCCOL nCol );
127 
128     void OutputDataArea();
129     void OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori = false );
130 
131 };
132 
OutputDataArea()133 void ScDPOutputImpl::OutputDataArea()
134 {
135     AddRow( mnDataStartRow );
136     AddCol( mnDataStartCol );
137 
138     mnCols.push_back( mnTabEndCol+1); //set last row bottom
139     mnRows.push_back( mnTabEndRow+1); //set last col bottom
140 
141     bool bAllRows = ( ( mnTabEndRow - mnDataStartRow + 2 ) == static_cast<SCROW>(mnRows.size()) );
142 
143     std::sort( mnCols.begin(), mnCols.end());
144     std::sort( mnRows.begin(), mnRows.end());
145 
146     for( SCCOL nCol = 0; nCol < static_cast<SCCOL>(mnCols.size())-1; nCol ++ )
147     {
148         if ( !bAllRows )
149         {
150             if ( nCol < static_cast<SCCOL>(mnCols.size())-2)
151             {
152                 for ( SCROW i = nCol%2; i < static_cast<SCROW>(mnRows.size())-2; i +=2 )
153                     OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 );
154                 if ( mnRows.size()>=2 )
155                     OutputBlockFrame(  mnCols[nCol], mnRows[mnRows.size()-2], mnCols[nCol+1]-1, mnRows[mnRows.size()-1]-1 );
156             }
157             else
158             {
159                 for ( SCROW i = 0 ; i < static_cast<SCROW>(mnRows.size())-1; i++ )
160                     OutputBlockFrame(  mnCols[nCol], mnRows[i], mnCols[nCol+1]-1,  mnRows[i+1]-1 );
161             }
162         }
163         else
164             OutputBlockFrame( mnCols[nCol], mnRows.front(), mnCols[nCol+1]-1, mnRows.back()-1, bAllRows );
165     }
166     //out put rows area outer framer
167     if ( mnTabStartCol != mnDataStartCol )
168     {
169         if ( mnTabStartRow != mnDataStartRow )
170             OutputBlockFrame( mnTabStartCol, mnTabStartRow, mnDataStartCol-1, mnDataStartRow-1 );
171         OutputBlockFrame( mnTabStartCol, mnDataStartRow, mnDataStartCol-1, mnTabEndRow );
172     }
173     //out put cols area outer framer
174     OutputBlockFrame( mnDataStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow-1 );
175 }
176 
ScDPOutputImpl(ScDocument * pDoc,sal_uInt16 nTab,SCCOL nTabStartCol,SCROW nTabStartRow,SCCOL nDataStartCol,SCROW nDataStartRow,SCCOL nTabEndCol,SCROW nTabEndRow)177 ScDPOutputImpl::ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
178         SCCOL   nTabStartCol,
179         SCROW   nTabStartRow,
180         SCCOL nDataStartCol,
181         SCROW nDataStartRow,
182         SCCOL nTabEndCol,
183         SCROW nTabEndRow ):
184     mpDoc( pDoc ),
185     mnTab( nTab ),
186     mnTabStartCol( nTabStartCol ),
187     mnTabStartRow( nTabStartRow ),
188     mnDataStartCol ( nDataStartCol ),
189     mnDataStartRow ( nDataStartRow ),
190     mnTabEndCol(  nTabEndCol ),
191     mnTabEndRow(  nTabEndRow )
192 {
193     mbNeedLineCols.resize( nTabEndCol-nDataStartCol+1, false );
194     mbNeedLineRows.resize( nTabEndRow-nDataStartRow+1, false );
195 
196 }
197 
AddRow(SCROW nRow)198 bool ScDPOutputImpl::AddRow( SCROW nRow )
199 {
200     if ( !mbNeedLineRows[ nRow - mnDataStartRow ] )
201     {
202         mbNeedLineRows[ nRow - mnDataStartRow ] = true;
203         mnRows.push_back( nRow );
204         return true;
205     }
206     else
207         return false;
208 }
209 
AddCol(SCCOL nCol)210 bool ScDPOutputImpl::AddCol( SCCOL nCol )
211 {
212 
213     if ( !mbNeedLineCols[ nCol - mnDataStartCol ] )
214     {
215         mbNeedLineCols[ nCol - mnDataStartCol ] = true;
216         mnCols.push_back( nCol );
217         return true;
218     }
219     else
220         return false;
221 }
222 
OutputBlockFrame(SCCOL nStartCol,SCROW nStartRow,SCCOL nEndCol,SCROW nEndRow,bool bHori)223 void ScDPOutputImpl::OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori )
224 {
225     Color color = SC_DP_FRAME_COLOR;
226     ::editeng::SvxBorderLine aLine( &color, SC_DP_FRAME_INNER_BOLD );
227     ::editeng::SvxBorderLine aOutLine( &color, SC_DP_FRAME_OUTER_BOLD );
228 
229     SvxBoxItem aBox( ATTR_BORDER );
230 
231     if ( nStartCol == mnTabStartCol )
232         aBox.SetLine(&aOutLine, SvxBoxItemLine::LEFT);
233     else
234         aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
235 
236     if ( nStartRow == mnTabStartRow )
237         aBox.SetLine(&aOutLine, SvxBoxItemLine::TOP);
238     else
239         aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
240 
241     if ( nEndCol == mnTabEndCol ) //bottom row
242         aBox.SetLine(&aOutLine, SvxBoxItemLine::RIGHT);
243     else
244         aBox.SetLine(&aLine,  SvxBoxItemLine::RIGHT);
245 
246     if ( nEndRow == mnTabEndRow ) //bottom
247         aBox.SetLine(&aOutLine,  SvxBoxItemLine::BOTTOM);
248     else
249         aBox.SetLine(&aLine,  SvxBoxItemLine::BOTTOM);
250 
251     SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
252     aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false );
253     if ( bHori )
254     {
255         aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI);
256         aBoxInfo.SetLine( &aLine, SvxBoxInfoItemLine::HORI );
257     }
258     else
259         aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false );
260 
261     aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
262 
263     mpDoc->ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow , mnTab), aBox, aBoxInfo);
264 
265 }
266 
267 namespace
268 {
269 
lcl_SetStyleById(ScDocument * pDoc,SCTAB nTab,SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2,TranslateId pStrId)270 void lcl_SetStyleById(ScDocument* pDoc, SCTAB nTab,
271                       SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
272                       TranslateId pStrId)
273 {
274     if ( nCol1 > nCol2 || nRow1 > nRow2 )
275     {
276         OSL_FAIL("SetStyleById: invalid range");
277         return;
278     }
279 
280     OUString aStyleName = ScResId(pStrId);
281     ScStyleSheetPool* pStlPool = pDoc->GetStyleSheetPool();
282     ScStyleSheet* pStyle = static_cast<ScStyleSheet*>( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) );
283     if (!pStyle)
284     {
285         //  create new style (was in ScPivot::SetStyle)
286 
287         pStyle = static_cast<ScStyleSheet*>( &pStlPool->Make( aStyleName, SfxStyleFamily::Para,
288                                                     SfxStyleSearchBits::UserDefined ) );
289         pStyle->SetParent( ScResId(STR_STYLENAME_STANDARD) );
290         SfxItemSet& rSet = pStyle->GetItemSet();
291         if (pStrId == STR_PIVOT_STYLENAME_RESULT || pStrId == STR_PIVOT_STYLENAME_TITLE){
292             rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
293             rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT ) );
294             rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT ) );
295         }
296         if (pStrId == STR_PIVOT_STYLENAME_CATEGORY || pStrId == STR_PIVOT_STYLENAME_TITLE)
297             rSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
298     }
299 
300     pDoc->ApplyStyleAreaTab( nCol1, nRow1, nCol2, nRow2, nTab, *pStyle );
301 }
302 
lcl_SetFrame(ScDocument * pDoc,SCTAB nTab,SCCOL nCol1,SCROW nRow1,SCCOL nCol2,SCROW nRow2,sal_uInt16 nWidth)303 void lcl_SetFrame( ScDocument* pDoc, SCTAB nTab,
304                     SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
305                     sal_uInt16 nWidth )
306 {
307     ::editeng::SvxBorderLine aLine(nullptr, nWidth, SvxBorderLineStyle::SOLID);
308     SvxBoxItem aBox( ATTR_BORDER );
309     aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
310     aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
311     aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT);
312     aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
313     SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
314     aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false);
315     aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false);
316     aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
317 
318     pDoc->ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo);
319 }
320 
lcl_FillNumberFormats(std::unique_ptr<sal_uInt32[]> & rFormats,sal_Int32 & rCount,const uno::Reference<sheet::XDataPilotMemberResults> & xLevRes,const uno::Reference<container::XIndexAccess> & xDims)321 void lcl_FillNumberFormats( std::unique_ptr<sal_uInt32[]>& rFormats, sal_Int32& rCount,
322                             const uno::Reference<sheet::XDataPilotMemberResults>& xLevRes,
323                             const uno::Reference<container::XIndexAccess>& xDims )
324 {
325     if ( rFormats )
326         return;                         // already set
327 
328     //  xLevRes is from the data layout dimension
329     //TODO: use result sequence from ScDPOutLevelData!
330 
331     uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
332 
333     tools::Long nSize = aResult.getLength();
334     if (!nSize)
335         return;
336 
337     //  get names/formats for all data dimensions
338     //TODO: merge this with the loop to collect ScDPOutLevelData?
339 
340     std::vector <OUString> aDataNames;
341     std::vector <sal_uInt32> aDataFormats;
342     sal_Int32 nDimCount = xDims->getCount();
343     sal_Int32 nDim = 0;
344     for ( ; nDim < nDimCount ; nDim++)
345     {
346         uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
347         uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
348         uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
349         if ( xDimProp.is() && xDimName.is() )
350         {
351             sheet::DataPilotFieldOrientation eDimOrient =
352                 ScUnoHelpFunctions::GetEnumProperty(
353                     xDimProp, SC_UNO_DP_ORIENTATION,
354                     sheet::DataPilotFieldOrientation_HIDDEN );
355             if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
356             {
357                 aDataNames.push_back(xDimName->getName());
358                 tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
359                                         xDimProp,
360                                         SC_UNONAME_NUMFMT );
361                 aDataFormats.push_back(nFormat);
362             }
363         }
364     }
365 
366     if (aDataFormats.empty())
367         return;
368 
369     const sheet::MemberResult* pArray = aResult.getConstArray();
370 
371     OUString aName;
372     sal_uInt32* pNumFmt = new sal_uInt32[nSize];
373     if (aDataFormats.size() == 1)
374     {
375         //  only one data dimension -> use its numberformat everywhere
376         tools::Long nFormat = aDataFormats[0];
377         for (tools::Long nPos=0; nPos<nSize; nPos++)
378             pNumFmt[nPos] = nFormat;
379     }
380     else
381     {
382         for (tools::Long nPos=0; nPos<nSize; nPos++)
383         {
384             //  if CONTINUE bit is set, keep previous name
385             //TODO: keep number format instead!
386             if ( !(pArray[nPos].Flags & sheet::MemberResultFlags::CONTINUE) )
387                 aName = pArray[nPos].Name;
388 
389             sal_uInt32 nFormat = 0;
390             for (size_t i=0; i<aDataFormats.size(); i++)
391                 if (aName == aDataNames[i])         //TODO: search more efficiently?
392                 {
393                     nFormat = aDataFormats[i];
394                     break;
395                 }
396             pNumFmt[nPos] = nFormat;
397         }
398     }
399 
400     rFormats.reset( pNumFmt );
401     rCount = nSize;
402 }
403 
lcl_GetFirstNumberFormat(const uno::Reference<container::XIndexAccess> & xDims)404 sal_uInt32 lcl_GetFirstNumberFormat( const uno::Reference<container::XIndexAccess>& xDims )
405 {
406     tools::Long nDimCount = xDims->getCount();
407     for (tools::Long nDim=0; nDim<nDimCount; nDim++)
408     {
409         uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
410         if ( xDimProp.is() )
411         {
412             sheet::DataPilotFieldOrientation eDimOrient =
413                 ScUnoHelpFunctions::GetEnumProperty(
414                     xDimProp, SC_UNO_DP_ORIENTATION,
415                     sheet::DataPilotFieldOrientation_HIDDEN );
416             if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
417             {
418                 tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
419                                         xDimProp,
420                                         SC_UNONAME_NUMFMT );
421 
422                 return nFormat;     // use format from first found data dimension
423             }
424         }
425     }
426 
427     return 0;       // none found
428 }
429 
lcl_MemberEmpty(const uno::Sequence<sheet::MemberResult> & rSeq)430 bool lcl_MemberEmpty( const uno::Sequence<sheet::MemberResult>& rSeq )
431 {
432     //  used to skip levels that have no members
433 
434     return std::none_of(rSeq.begin(), rSeq.end(),
435         [](const sheet::MemberResult& rMem) {
436             return rMem.Flags & sheet::MemberResultFlags::HASMEMBER; });
437 }
438 
439 /**
440  * Get visible page dimension members as results, except that, if all
441  * members are visible, then this function returns empty result.
442  */
getVisiblePageMembersAsResults(const uno::Reference<uno::XInterface> & xLevel)443 uno::Sequence<sheet::MemberResult> getVisiblePageMembersAsResults( const uno::Reference<uno::XInterface>& xLevel )
444 {
445     if (!xLevel.is())
446         return uno::Sequence<sheet::MemberResult>();
447 
448     uno::Reference<sheet::XMembersSupplier> xMSupplier(xLevel, UNO_QUERY);
449     if (!xMSupplier.is())
450         return uno::Sequence<sheet::MemberResult>();
451 
452     uno::Reference<sheet::XMembersAccess> xNA = xMSupplier->getMembers();
453     if (!xNA.is())
454         return uno::Sequence<sheet::MemberResult>();
455 
456     std::vector<sheet::MemberResult> aRes;
457     const uno::Sequence<OUString> aNames = xNA->getElementNames();
458     for (const OUString& rName : aNames)
459     {
460         xNA->getByName(rName);
461 
462         uno::Reference<beans::XPropertySet> xMemPS(xNA->getByName(rName), UNO_QUERY);
463         if (!xMemPS.is())
464             continue;
465 
466         OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xMemPS, SC_UNO_DP_LAYOUTNAME, OUString());
467         if (aCaption.isEmpty())
468             aCaption = rName;
469 
470         bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xMemPS, SC_UNO_DP_ISVISIBLE);
471 
472         if (bVisible)
473         {
474             /* TODO: any numeric value to obtain? */
475             aRes.emplace_back(rName, aCaption, 0, std::numeric_limits<double>::quiet_NaN());
476         }
477     }
478 
479     if (o3tl::make_unsigned(aNames.getLength()) == aRes.size())
480         // All members are visible.  Return empty result.
481         return uno::Sequence<sheet::MemberResult>();
482 
483     return comphelper::containerToSequence(aRes);
484 }
485 
486 } // end anonymous namespace
487 
ScDPOutput(ScDocument * pDocument,uno::Reference<sheet::XDimensionsSupplier> xSource,const ScAddress & rPosition,bool bFilter,bool bExpandCollapse,ScDPObject & rObject)488 ScDPOutput::ScDPOutput(ScDocument* pDocument, uno::Reference<sheet::XDimensionsSupplier> xSource,
489                        const ScAddress& rPosition, bool bFilter, bool bExpandCollapse, ScDPObject& rObject)
490     : mpDocument(pDocument)
491     , maFormatOutput(rObject)
492     , mxSource(std::move(xSource))
493     , maStartPos(rPosition)
494     , mnColFormatCount(0)
495     , mnRowFormatCount(0)
496     , mnSingleNumberFormat(0)
497     , mnRowDims(0)
498     , mnColCount(0)
499     , mnRowCount(0)
500     , mnHeaderSize(0)
501     , mbDoFilter(bFilter)
502     , mbResultsError(false)
503     , mbSizesValid(false)
504     , mbSizeOverflow(false)
505     , mbHeaderLayout(false)
506     , mbHasCompactRowField(false)
507     , mbExpandCollapse(bExpandCollapse)
508 {
509     mnTabStartCol = mnMemberStartCol = mnDataStartCol = mnTabEndCol = 0;
510     mnTabStartRow = mnMemberStartRow = mnDataStartRow = mnTabEndRow = 0;
511 
512     uno::Reference<sheet::XDataPilotResults> xResult(mxSource, uno::UNO_QUERY);
513     if (mxSource.is() && xResult.is())
514     {
515         //  get dimension results:
516 
517         uno::Reference<container::XIndexAccess> xDims =
518                 new ScNameToIndexAccess(mxSource->getDimensions());
519         tools::Long nDimCount = xDims->getCount();
520         for (tools::Long nDim=0; nDim<nDimCount; nDim++)
521         {
522             uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
523             uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
524             uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY );
525             if ( xDimProp.is() && xDimSupp.is() )
526             {
527                 sheet::DataPilotFieldOrientation eDimOrient =
528                     ScUnoHelpFunctions::GetEnumProperty(
529                         xDimProp, SC_UNO_DP_ORIENTATION,
530                         sheet::DataPilotFieldOrientation_HIDDEN );
531                 tools::Long nDimPos = ScUnoHelpFunctions::GetLongProperty( xDimProp,
532                         SC_UNO_DP_POSITION );
533                 bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty(
534                     xDimProp, SC_UNO_DP_ISDATALAYOUT);
535                 bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty(
536                     xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER);
537                 sal_Int32 nNumFmt = ScUnoHelpFunctions::GetLongProperty(
538                     xDimProp, SC_UNO_DP_NUMBERFO);
539 
540                 if ( eDimOrient != sheet::DataPilotFieldOrientation_HIDDEN )
541                 {
542                     uno::Reference<container::XIndexAccess> xHiers =
543                             new ScNameToIndexAccess( xDimSupp->getHierarchies() );
544                     tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty(
545                                             xDimProp,
546                                             SC_UNO_DP_USEDHIERARCHY );
547                     if ( nHierarchy >= xHiers->getCount() )
548                         nHierarchy = 0;
549 
550                     uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
551                                                                      uno::UNO_QUERY);
552                     if ( xHierSupp.is() )
553                     {
554                         uno::Reference<container::XIndexAccess> xLevels =
555                                 new ScNameToIndexAccess( xHierSupp->getLevels() );
556                         tools::Long nLevCount = xLevels->getCount();
557                         for (tools::Long nLev=0; nLev<nLevCount; nLev++)
558                         {
559                             uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev),
560                                                                    uno::UNO_QUERY);
561                             uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY );
562                             uno::Reference<sheet::XDataPilotMemberResults> xLevRes(
563                                     xLevel, uno::UNO_QUERY );
564                             if ( xLevNam.is() && xLevRes.is() )
565                             {
566                                 OUString aName = xLevNam->getName();
567                                 Reference<XPropertySet> xPropSet(xLevel, UNO_QUERY);
568                                 // Caption equals the field name by default.
569                                 // #i108948# use ScUnoHelpFunctions::GetStringProperty, because
570                                 // LayoutName is new and may not be present in external implementation
571                                 OUString aCaption = ScUnoHelpFunctions::GetStringProperty( xPropSet,
572                                     SC_UNO_DP_LAYOUTNAME, aName );
573 
574                                 switch ( eDimOrient )
575                                 {
576                                     case sheet::DataPilotFieldOrientation_COLUMN:
577                                     {
578                                         uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
579                                         if (!lcl_MemberEmpty(aResult))
580                                         {
581                                             mpColFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
582                                                                      aCaption, bHasHiddenMember, bIsDataLayout, false);
583                                         }
584                                     }
585                                     break;
586                                     case sheet::DataPilotFieldOrientation_ROW:
587                                     {
588                                         uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
589                                         ++mnRowDims;
590                                         // We want only to remove the DATA column if it is empty
591                                         // and not any other empty columns (to still show the
592                                         // header columns)
593                                         bool bSkip = lcl_MemberEmpty(aResult) && bIsDataLayout;
594                                         if (!bSkip)
595                                         {
596                                             bool bFieldCompact = false;
597                                             try
598                                             {
599                                                 sheet::DataPilotFieldLayoutInfo aLayoutInfo;
600                                                 xPropSet->getPropertyValue( SC_UNO_DP_LAYOUT ) >>= aLayoutInfo;
601                                                 bFieldCompact = (aLayoutInfo.LayoutMode == sheet::DataPilotFieldLayoutMode::COMPACT_LAYOUT);
602                                             }
603                                             catch (uno::Exception&)
604                                             {
605                                             }
606                                             mpRowFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
607                                                                      aCaption, bHasHiddenMember, bIsDataLayout, false);
608                                             maRowCompactFlags.push_back(bFieldCompact);
609                                             mbHasCompactRowField |= bFieldCompact;
610                                         }
611 
612                                     }
613                                     break;
614                                     case sheet::DataPilotFieldOrientation_PAGE:
615                                     {
616                                         uno::Sequence<sheet::MemberResult> aResult = getVisiblePageMembersAsResults(xLevel);
617                                         // no check on results for page fields
618                                         mpPageFields.emplace_back(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
619                                                                   aCaption, bHasHiddenMember, false, true);
620                                     }
621                                     break;
622                                     default:
623                                     {
624                                         // added to avoid warnings
625                                     }
626                                 }
627 
628                                 // get number formats from data dimensions
629                                 if ( bIsDataLayout )
630                                 {
631                                     OSL_ENSURE( nLevCount == 1, "data layout: multiple levels?" );
632                                     if ( eDimOrient == sheet::DataPilotFieldOrientation_COLUMN )
633                                         lcl_FillNumberFormats(mpColNumberFormat, mnColFormatCount, xLevRes, xDims);
634                                     else if ( eDimOrient == sheet::DataPilotFieldOrientation_ROW )
635                                         lcl_FillNumberFormats(mpRowNumberFormat, mnRowFormatCount, xLevRes, xDims);
636                                 }
637                             }
638                         }
639                     }
640                 }
641                 else if ( bIsDataLayout )
642                 {
643                     // data layout dimension is hidden (allowed if there is only one data dimension)
644                     // -> use the number format from the first data dimension for all results
645 
646                     mnSingleNumberFormat = lcl_GetFirstNumberFormat( xDims );
647                 }
648             }
649         }
650         std::sort(mpColFields.begin(), mpColFields.end(), ScDPOutLevelDataComparator());
651         std::sort(mpRowFields.begin(), mpRowFields.end(), ScDPOutLevelDataComparator());
652         std::sort(mpPageFields.begin(), mpPageFields.end(), ScDPOutLevelDataComparator());
653 
654         //  get data results:
655 
656         try
657         {
658             maData = xResult->getResults();
659         }
660         catch (const uno::RuntimeException&)
661         {
662             mbResultsError = true;
663         }
664     }
665 
666     // get "DataDescription" property (may be missing in external sources)
667 
668     uno::Reference<beans::XPropertySet> xSrcProp(mxSource, uno::UNO_QUERY);
669     if ( !xSrcProp.is() )
670         return;
671 
672     try
673     {
674         uno::Any aAny = xSrcProp->getPropertyValue( SC_UNO_DP_DATADESC );
675         OUString aUStr;
676         aAny >>= aUStr;
677         maDataDescription = aUStr;
678     }
679     catch(const uno::Exception&)
680     {
681     }
682 }
683 
~ScDPOutput()684 ScDPOutput::~ScDPOutput()
685 {
686 }
687 
SetPosition(const ScAddress & rPosition)688 void ScDPOutput::SetPosition(const ScAddress& rPosition)
689 {
690     maStartPos = rPosition;
691     mbSizesValid = mbSizeOverflow = false;
692 }
693 
DataCell(SCCOL nCol,SCROW nRow,SCTAB nTab,const sheet::DataResult & rData)694 void ScDPOutput::DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::DataResult& rData )
695 {
696     tools::Long nFlags = rData.Flags;
697     if ( nFlags & sheet::DataResultFlags::ERROR )
698     {
699         mpDocument->SetError( nCol, nRow, nTab, FormulaError::NoValue );
700     }
701     else if ( nFlags & sheet::DataResultFlags::HASDATA )
702     {
703         mpDocument->SetValue( nCol, nRow, nTab, rData.Value );
704 
705         //  use number formats from source
706 
707         OSL_ENSURE(mbSizesValid, "DataCell: !bSizesValid");
708         sal_uInt32 nFormat = 0;
709         bool bApplyFormat = false;
710         if (mpColNumberFormat)
711         {
712             if (nCol >= mnDataStartCol)
713             {
714                 tools::Long nIndex = nCol - mnDataStartCol;
715                 if (nIndex < mnColFormatCount)
716                 {
717                     nFormat = mpColNumberFormat[nIndex];
718                     bApplyFormat = true;
719                 }
720             }
721         }
722         else if (mpRowNumberFormat)
723         {
724             if (nRow >= mnDataStartRow)
725             {
726                 tools::Long nIndex = nRow - mnDataStartRow;
727                 if (nIndex < mnRowFormatCount)
728                 {
729                     nFormat = mpRowNumberFormat[nIndex];
730                     bApplyFormat = true;
731                 }
732             }
733         }
734         else if (mnSingleNumberFormat != 0)
735         {
736             nFormat = mnSingleNumberFormat;        // single format is used everywhere
737             bApplyFormat = true;
738         }
739 
740         if (bApplyFormat)
741             mpDocument->ApplyAttr(nCol, nRow, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
742     }
743     //  SubTotal formatting is controlled by headers
744 }
745 
HeaderCell(SCCOL nCol,SCROW nRow,SCTAB nTab,const sheet::MemberResult & rData,bool bColHeader,tools::Long nLevel)746 void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab,
747                              const sheet::MemberResult& rData, bool bColHeader, tools::Long nLevel )
748 {
749     tools::Long nFlags = rData.Flags;
750 
751     if ( nFlags & sheet::MemberResultFlags::HASMEMBER )
752     {
753         bool bNumeric = (nFlags & sheet::MemberResultFlags::NUMERIC) != 0;
754         if (bNumeric && std::isfinite( rData.Value))
755         {
756             mpDocument->SetValue( nCol, nRow, nTab, rData.Value);
757         }
758         else
759         {
760             ScSetStringParam aParam;
761             if (bNumeric)
762                 aParam.setNumericInput();
763             else
764                 aParam.setTextInput();
765 
766             mpDocument->SetString(nCol, nRow, nTab, rData.Caption, &aParam);
767         }
768     }
769 
770     if ( !(nFlags & sheet::MemberResultFlags::SUBTOTAL) )
771         return;
772 
773     ScDPOutputImpl outputimp(mpDocument, nTab,
774         mnTabStartCol, mnTabStartRow,
775         mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow);
776     //TODO: limit frames to horizontal or vertical?
777     if (bColHeader)
778     {
779         outputimp.OutputBlockFrame(nCol, mnMemberStartRow+static_cast<SCROW>(nLevel), nCol, mnDataStartRow - 1);
780 
781         lcl_SetStyleById(mpDocument, nTab, nCol, mnMemberStartRow + static_cast<SCROW>(nLevel), nCol, mnDataStartRow - 1, STR_PIVOT_STYLENAME_TITLE);
782         lcl_SetStyleById(mpDocument, nTab, nCol, mnDataStartRow, nCol, mnTabEndRow, STR_PIVOT_STYLENAME_RESULT );
783     }
784     else
785     {
786         outputimp.OutputBlockFrame(mnMemberStartCol + static_cast<SCCOL>(nLevel), nRow, mnDataStartCol - 1, nRow);
787         lcl_SetStyleById(mpDocument, nTab, mnMemberStartCol + static_cast<SCCOL>(nLevel), nRow, mnDataStartCol - 1, nRow, STR_PIVOT_STYLENAME_TITLE);
788         lcl_SetStyleById(mpDocument, nTab, mnDataStartCol, nRow, mnTabEndCol, nRow, STR_PIVOT_STYLENAME_RESULT);
789     }
790 }
791 
MultiFieldCell(SCCOL nCol,SCROW nRow,SCTAB nTab,bool bRowField)792 void ScDPOutput::MultiFieldCell(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bRowField)
793 {
794     mpDocument->SetString(nCol, nRow, nTab, ScResId(bRowField ? STR_PIVOT_ROW_LABELS : STR_PIVOT_COL_LABELS));
795 
796     ScMF nMergeFlag = ScMF::Button;
797     for (auto& rData : mpRowFields)
798     {
799         if (rData.mbHasHiddenMember)
800         {
801             nMergeFlag |= ScMF::HiddenMember;
802             break;
803         }
804     }
805 
806     nMergeFlag |= ScMF::ButtonPopup2;
807 
808     mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag);
809     lcl_SetStyleById(mpDocument, nTab, nCol, nRow, nCol, nRow, STR_PIVOT_STYLENAME_FIELDNAME);
810 }
811 
FieldCell(SCCOL nCol,SCROW nRow,SCTAB nTab,const ScDPOutLevelData & rData,bool bInTable)812 void ScDPOutput::FieldCell(
813     SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool bInTable)
814 {
815     // Avoid unwanted automatic format detection.
816     ScSetStringParam aParam;
817     aParam.mbDetectNumberFormat = false;
818     aParam.meSetTextNumFormat = ScSetStringParam::Always;
819     aParam.mbHandleApostrophe = false;
820     mpDocument->SetString(nCol, nRow, nTab, rData.maCaption, &aParam);
821 
822     if (bInTable)
823         lcl_SetFrame(mpDocument, nTab, nCol,nRow, nCol,nRow, 20);
824 
825     // For field button drawing
826     ScMF nMergeFlag = ScMF::NONE;
827     if (rData.mbHasHiddenMember)
828         nMergeFlag |= ScMF::HiddenMember;
829 
830     if (rData.mbPageDim)
831     {
832         nMergeFlag |= ScMF::ButtonPopup;
833         mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
834         mpDocument->ApplyFlagsTab(nCol+1, nRow, nCol+1, nRow, nTab, nMergeFlag);
835     }
836     else
837     {
838         nMergeFlag |= ScMF::Button;
839         if (!rData.mbDataLayout)
840             nMergeFlag |= ScMF::ButtonPopup;
841         mpDocument->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag);
842     }
843 
844     lcl_SetStyleById(mpDocument, nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME);
845 }
846 
lcl_DoFilterButton(ScDocument * pDoc,SCCOL nCol,SCROW nRow,SCTAB nTab)847 static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab )
848 {
849     pDoc->SetString( nCol, nRow, nTab, ScResId(STR_CELL_FILTER) );
850     pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
851 }
852 
GetColumnsForRowFields() const853 SCCOL ScDPOutput::GetColumnsForRowFields() const
854 {
855     if (!mbHasCompactRowField)
856         return static_cast<SCCOL>(mpRowFields.size());
857 
858     SCCOL nNum = 0;
859     for (const auto bCompact: maRowCompactFlags)
860         if (!bCompact)
861             ++nNum;
862 
863     if (maRowCompactFlags.back())
864         ++nNum;
865 
866     return nNum;
867 }
868 
CalcSizes()869 void ScDPOutput::CalcSizes()
870 {
871     if (mbSizesValid)
872         return;
873 
874     //  get column size of data from first row
875     //TODO: allow different sizes (and clear following areas) ???
876 
877     mnRowCount = maData.getLength();
878     const uno::Sequence<sheet::DataResult>* pRowAry = maData.getConstArray();
879     mnColCount = mnRowCount ? ( pRowAry[0].getLength() ) : 0;
880 
881     mnHeaderSize = 1;
882     if (GetHeaderLayout() && mpColFields.empty())
883         // Insert an extra header row only when there is no column field.
884         mnHeaderSize = 2;
885 
886     //  calculate output positions and sizes
887 
888     tools::Long nPageSize = 0;     // use page fields!
889     if (mbDoFilter || !mpPageFields.empty())
890     {
891         nPageSize += mpPageFields.size() + 1;   // plus one empty row
892         if (mbDoFilter)
893             ++nPageSize;        //  filter button above the page fields
894     }
895 
896     if (maStartPos.Col() + static_cast<tools::Long>(mpRowFields.size()) + mnColCount - 1 > mpDocument->MaxCol() ||
897         maStartPos.Row() + nPageSize + mnHeaderSize + static_cast<tools::Long>(mpColFields.size()) + mnRowCount > mpDocument->MaxRow())
898     {
899         mbSizeOverflow = true;
900     }
901 
902     mnTabStartCol = maStartPos.Col();
903     mnTabStartRow = maStartPos.Row() + static_cast<SCROW>(nPageSize);          // below page fields
904     mnMemberStartCol = mnTabStartCol;
905     mnMemberStartRow = mnTabStartRow + static_cast<SCROW>(mnHeaderSize);
906     mnDataStartCol = mnMemberStartCol + GetColumnsForRowFields();
907     mnDataStartRow = mnMemberStartRow + static_cast<SCROW>(mpColFields.size());
908     if (mnColCount > 0)
909         mnTabEndCol = mnDataStartCol + static_cast<SCCOL>(mnColCount) - 1;
910     else
911         mnTabEndCol = mnDataStartCol;     // single column will remain empty
912     // if page fields are involved, include the page selection cells
913     if (!mpPageFields.empty() && mnTabEndCol < mnTabStartCol + 1)
914         mnTabEndCol = mnTabStartCol + 1;
915     if (mnRowCount > 0)
916         mnTabEndRow = mnDataStartRow + static_cast<SCROW>(mnRowCount) - 1;
917     else
918         mnTabEndRow = mnDataStartRow;     // single row will remain empty
919     mbSizesValid = true;
920 }
921 
GetPositionType(const ScAddress & rPos)922 sal_Int32 ScDPOutput::GetPositionType(const ScAddress& rPos)
923 {
924     using namespace ::com::sun::star::sheet;
925 
926     SCCOL nCol = rPos.Col();
927     SCROW nRow = rPos.Row();
928     SCTAB nTab = rPos.Tab();
929     if ( nTab != maStartPos.Tab() )
930         return DataPilotTablePositionType::NOT_IN_TABLE;
931 
932     CalcSizes();
933 
934     // Make sure the cursor is within the table.
935     if (nCol < mnTabStartCol || nRow < mnTabStartRow || nCol > mnTabEndCol || nRow > mnTabEndRow)
936         return DataPilotTablePositionType::NOT_IN_TABLE;
937 
938     // test for result data area.
939     if (nCol >= mnDataStartCol && nCol <= mnTabEndCol && nRow >= mnDataStartRow && nRow <= mnTabEndRow)
940         return DataPilotTablePositionType::RESULT;
941 
942     bool bInColHeader = (nRow >= mnTabStartRow && nRow < mnDataStartRow);
943     bool bInRowHeader = (nCol >= mnTabStartCol && nCol < mnDataStartCol);
944 
945     if (bInColHeader && bInRowHeader)
946         // probably in that ugly little box at the upper-left corner of the table.
947         return DataPilotTablePositionType::OTHER;
948 
949     if (bInColHeader)
950     {
951         if (nRow == mnTabStartRow)
952             // first row in the column header area is always used for column
953             // field buttons.
954             return DataPilotTablePositionType::OTHER;
955 
956         return DataPilotTablePositionType::COLUMN_HEADER;
957     }
958 
959     if (bInRowHeader)
960         return DataPilotTablePositionType::ROW_HEADER;
961 
962     return DataPilotTablePositionType::OTHER;
963 }
964 
outputPageFields(SCTAB nTab)965 void ScDPOutput::outputPageFields(SCTAB nTab)
966 {
967     for (size_t nField = 0; nField < mpPageFields.size(); ++nField)
968     {
969         SCCOL nHeaderCol = maStartPos.Col();
970         SCROW nHeaderRow = maStartPos.Row() + nField + (mbDoFilter ? 1 : 0);
971         // draw without frame for consistency with filter button:
972         FieldCell(nHeaderCol, nHeaderRow, nTab, mpPageFields[nField], false);
973         SCCOL nFieldCol = nHeaderCol + 1;
974 
975         OUString aPageValue = ScResId(SCSTR_ALL);
976         const uno::Sequence<sheet::MemberResult>& rRes = mpPageFields[nField].maResult;
977         sal_Int32 n = rRes.getLength();
978         if (n == 1)
979         {
980             if (rRes[0].Caption.isEmpty())
981                 aPageValue = ScResId(STR_EMPTYDATA);
982             else
983                 aPageValue = rRes[0].Caption;
984         }
985         else if (n > 1)
986         {
987             aPageValue = ScResId(SCSTR_MULTIPLE);
988         }
989 
990         ScSetStringParam aParam;
991         aParam.setTextInput();
992         mpDocument->SetString(nFieldCol, nHeaderRow, nTab, aPageValue, &aParam);
993 
994         lcl_SetFrame(mpDocument, nTab, nFieldCol, nHeaderRow, nFieldCol, nHeaderRow, 20);
995     }
996 }
997 
outputColumnHeaders(SCTAB nTab,ScDPOutputImpl & rOutputImpl)998 void ScDPOutput::outputColumnHeaders(SCTAB nTab, ScDPOutputImpl& rOutputImpl)
999 {
1000     size_t nNumColFields = mpColFields.size();
1001 
1002     for (size_t nField = 0; nField < nNumColFields; nField++)
1003     {
1004         SCCOL nHeaderCol = mnDataStartCol + SCCOL(nField); //TODO: check for overflow
1005 
1006         if (!mbHasCompactRowField || nNumColFields == 1)
1007             FieldCell(nHeaderCol, mnTabStartRow, nTab, mpColFields[nField], true);
1008         else if (!nField)
1009             MultiFieldCell(nHeaderCol, mnTabStartRow, nTab, false /* bRowField */);
1010 
1011         SCROW nRowPos = mnMemberStartRow + SCROW(nField); //TODO: check for overflow
1012         const uno::Sequence<sheet::MemberResult> rMemberSequence = mpColFields[nField].maResult;
1013         const sheet::MemberResult* pMemberArray = rMemberSequence.getConstArray();
1014         tools::Long nThisColCount = rMemberSequence.getLength();
1015         OSL_ENSURE(nThisColCount == mnColCount, "count mismatch"); //TODO: ???
1016 
1017         for (tools::Long nColumn = 0; nColumn < nThisColCount; nColumn++)
1018         {
1019             sheet::MemberResult const& rMember = rMemberSequence[nColumn];
1020 
1021             SCCOL nColPos = mnDataStartCol + SCCOL(nColumn); //TODO: check for overflow
1022 
1023             HeaderCell(nColPos, nRowPos, nTab, rMember, true, nField);
1024 
1025             if ((rMember.Flags & sheet::MemberResultFlags::HASMEMBER) &&
1026                !(rMember.Flags & sheet::MemberResultFlags::SUBTOTAL))
1027             {
1028                 // Check the number of columns this spreads
1029                 tools::Long nEnd = nColumn;
1030                 while (nEnd + 1 < nThisColCount && (pMemberArray[nEnd + 1].Flags & sheet::MemberResultFlags::CONTINUE))
1031                     ++nEnd;
1032 
1033                 SCCOL nEndColPos = mnDataStartCol + SCCOL(nEnd); //TODO: check for overflow
1034                 if (nField + 1 < mpColFields.size())
1035                 {
1036                     if (nField == mpColFields.size() - 2)
1037                     {
1038                         rOutputImpl.AddCol( nColPos );
1039                         if (nColPos + 1 == nEndColPos)
1040                             rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nEndColPos, nRowPos + 1, true);
1041                     }
1042                     else
1043                         rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nEndColPos, nRowPos);
1044 
1045                     lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, nEndColPos, mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY);
1046                 }
1047                 else
1048                 {
1049                     lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, nColPos, mnDataStartRow - 1, STR_PIVOT_STYLENAME_CATEGORY);
1050                 }
1051             }
1052             else if (rMember.Flags & sheet::MemberResultFlags::SUBTOTAL)
1053             {
1054                 rOutputImpl.AddCol(nColPos);
1055             }
1056 
1057             // Resolve formats
1058             maFormatOutput.insertFieldMember(nField, mpColFields[nField], nColumn, rMember, nColPos, nRowPos, sc::FormatResultDirection::COLUMN);
1059 
1060             // Apply the same number format as in data source.
1061             mpDocument->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, mpColFields[nField].mnSrcNumFmt));
1062         }
1063         if (nField == 0 && mpColFields.size() == 1)
1064             rOutputImpl.OutputBlockFrame(mnDataStartCol, mnTabStartRow, mnTabEndCol, nRowPos - 1);
1065     }
1066 }
1067 
outputRowHeader(SCTAB nTab,ScDPOutputImpl & rOutputImpl)1068 void ScDPOutput::outputRowHeader(SCTAB nTab, ScDPOutputImpl& rOutputImpl)
1069 {
1070     std::vector<bool> vbSetBorder;
1071     vbSetBorder.resize(mnTabEndRow - mnDataStartRow + 1, false);
1072     size_t nFieldColOffset = 0;
1073     size_t nFieldIndentLevel = 0; // To calculate indent level for fields packed in a column.
1074     size_t nNumRowFields = mpRowFields.size();
1075     for (size_t nField = 0; nField < nNumRowFields; nField++)
1076     {
1077         const bool bCompactField = maRowCompactFlags[nField];
1078         SCCOL nHdrCol = mnTabStartCol + SCCOL(nField); //TODO: check for overflow
1079         SCROW nHdrRow = mnDataStartRow - 1;
1080         if (!mbHasCompactRowField || nNumRowFields == 1)
1081             FieldCell(nHdrCol, nHdrRow, nTab, mpRowFields[nField], true);
1082         else if (!nField)
1083             MultiFieldCell(nHdrCol, nHdrRow, nTab, true /* bRowField */);
1084 
1085         SCCOL nColPos = mnMemberStartCol + SCCOL(nFieldColOffset); //TODO: check for overflow
1086         const uno::Sequence<sheet::MemberResult> rMemberSequence = mpRowFields[nField].maResult;
1087         const sheet::MemberResult* pMemberArray = rMemberSequence.getConstArray();
1088         sal_Int32 nThisRowCount = rMemberSequence.getLength();
1089         OSL_ENSURE(nThisRowCount == mnRowCount, "count mismatch");     //TODO: ???
1090         for (sal_Int32 nRow = 0; nRow < nThisRowCount; nRow++)
1091         {
1092             sheet::MemberResult const& rMember = rMemberSequence[nRow];
1093             const sheet::MemberResult& rData = rMember;
1094             const bool bHasMember = rData.Flags & sheet::MemberResultFlags::HASMEMBER;
1095             const bool bSubtotal = rData.Flags & sheet::MemberResultFlags::SUBTOTAL;
1096             SCROW nRowPos = mnDataStartRow + SCROW(nRow); //TODO: check for overflow
1097             HeaderCell( nColPos, nRowPos, nTab, rData, false, nFieldColOffset );
1098             if (bHasMember && !bSubtotal)
1099             {
1100                 if (nField + 1 < mpRowFields.size())
1101                 {
1102                     tools::Long nEnd = nRow;
1103                     while (nEnd + 1 < nThisRowCount && (pMemberArray[nEnd + 1].Flags & sheet::MemberResultFlags::CONTINUE))
1104                     {
1105                         ++nEnd;
1106                     }
1107                     SCROW nEndRowPos = mnDataStartRow + SCROW(nEnd); //TODO: check for overflow
1108                     rOutputImpl.AddRow(nRowPos);
1109                     if (!vbSetBorder[nRow] )
1110                     {
1111                         rOutputImpl.OutputBlockFrame(nColPos, nRowPos, mnTabEndCol, nEndRowPos);
1112                         vbSetBorder[nRow] = true;
1113                     }
1114                     rOutputImpl.OutputBlockFrame(nColPos, nRowPos, nColPos, nEndRowPos);
1115 
1116                     if (nField == mpRowFields.size() - 2)
1117                         rOutputImpl.OutputBlockFrame(nColPos + 1, nRowPos, nColPos + 1, nEndRowPos);
1118 
1119                     lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, mnDataStartCol - 1, nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY);
1120                 }
1121                 else
1122                 {
1123                     lcl_SetStyleById(mpDocument, nTab, nColPos, nRowPos, mnDataStartCol - 1, nRowPos, STR_PIVOT_STYLENAME_CATEGORY);
1124                 }
1125 
1126                 // Set flags for collapse/expand buttons and indent field header text
1127                 {
1128                     bool bLast = mnRowDims == (nField + 1);
1129                     size_t nMinIndentLevel = mbExpandCollapse ? 1 : 0;
1130                     tools::Long nIndent = o3tl::convert(13 * (bLast ? nFieldIndentLevel : nMinIndentLevel + nFieldIndentLevel), o3tl::Length::px, o3tl::Length::twip);
1131                     bool bHasContinue = !bLast && nRow + 1 < nThisRowCount && (pMemberArray[nRow + 1].Flags & sheet::MemberResultFlags::CONTINUE);
1132                     if (nIndent)
1133                         mpDocument->ApplyAttr(nColPos, nRowPos, nTab, ScIndentItem(nIndent));
1134                     if (mbExpandCollapse && !bLast)
1135                     {
1136                         mpDocument->ApplyFlagsTab(nColPos, nRowPos, nColPos, nRowPos, nTab,
1137                             bHasContinue ? ScMF::DpCollapse : ScMF::DpExpand);
1138                     }
1139                 }
1140             }
1141             else if (bSubtotal)
1142             {
1143                 rOutputImpl.AddRow(nRowPos);
1144             }
1145 
1146             // Resolve formats
1147             maFormatOutput.insertFieldMember(nField, mpRowFields[nField], nRow, rMember, nColPos, nRowPos, sc::FormatResultDirection::ROW);
1148 
1149             // Apply the same number format as in data source.
1150             mpDocument->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, mpRowFields[nField].mnSrcNumFmt));
1151         }
1152 
1153         if (!bCompactField)
1154         {
1155             // Next field should be placed in next column only if current field has a non-compact layout.
1156             ++nFieldColOffset;
1157             nFieldIndentLevel = 0; // Reset indent level.
1158         }
1159         else
1160         {
1161             ++nFieldIndentLevel;
1162         }
1163     }
1164 }
1165 
outputDataResults(SCTAB nTab)1166 void ScDPOutput::outputDataResults(SCTAB nTab)
1167 {
1168     const uno::Sequence<sheet::DataResult>* pRowAry = maData.getConstArray();
1169 
1170     for (sal_Int32 nRow = 0; nRow < mnRowCount; nRow++)
1171     {
1172         SCROW nRowPos = mnDataStartRow + SCROW(nRow); //TODO: check for overflow
1173         const sheet::DataResult* pColAry = pRowAry[nRow].getConstArray();
1174         sal_Int32 nThisColCount = pRowAry[nRow].getLength();
1175         OSL_ENSURE(nThisColCount == mnColCount, "count mismatch"); //TODO: ???
1176         for (sal_Int32 nCol = 0; nCol < nThisColCount; nCol++)
1177         {
1178             SCCOL nColPos = mnDataStartCol + SCCOL(nCol); //TODO: check for overflow
1179             DataCell(nColPos, nRowPos, nTab, pColAry[nCol]);
1180         }
1181     }
1182 
1183     maFormatOutput.apply(*mpDocument);
1184 }
1185 
Output()1186 void ScDPOutput::Output()
1187 {
1188     SCTAB nTab = maStartPos.Tab();
1189 
1190     //  calculate output positions and sizes
1191     CalcSizes();
1192 
1193     if (mbSizeOverflow || mbResultsError)   // does output area exceed sheet limits?
1194         return;                             // nothing
1195 
1196     // Prepare format output
1197     bool bColumnFieldIsDataOnly = mnColCount == 1 && mnRowCount > 0 && mpColFields.empty();
1198     maFormatOutput.prepare(nTab, mpColFields, mpRowFields, bColumnFieldIsDataOnly);
1199 
1200     //  clear whole (new) output area
1201     // when modifying table, clear old area !
1202     //TODO: include InsertDeleteFlags::OBJECTS ???
1203     mpDocument->DeleteAreaTab(maStartPos.Col(), maStartPos.Row(), mnTabEndCol, mnTabEndRow, nTab, InsertDeleteFlags::ALL );
1204 
1205     if (mbDoFilter)
1206         lcl_DoFilterButton(mpDocument, maStartPos.Col(), maStartPos.Row(), nTab);
1207 
1208     outputPageFields(nTab);
1209 
1210     //  data description
1211     //  (may get overwritten by first row field)
1212 
1213     if (maDataDescription.isEmpty())
1214     {
1215         //TODO: use default string ("result") ?
1216     }
1217     mpDocument->SetString(mnTabStartCol, mnTabStartRow, nTab, maDataDescription);
1218 
1219     //  set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten)
1220 
1221     if (mnDataStartRow > mnTabStartRow)
1222         lcl_SetStyleById(mpDocument, nTab, mnTabStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow - 1, STR_PIVOT_STYLENAME_TOP);
1223     lcl_SetStyleById(mpDocument, nTab, mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow, STR_PIVOT_STYLENAME_INNER);
1224 
1225     ScDPOutputImpl aOutputImpl(mpDocument, nTab, mnTabStartCol, mnTabStartRow,
1226                                mnDataStartCol, mnDataStartRow, mnTabEndCol, mnTabEndRow);
1227 
1228     outputColumnHeaders(nTab, aOutputImpl);
1229 
1230     outputRowHeader(nTab, aOutputImpl);
1231 
1232     if (bColumnFieldIsDataOnly)
1233     {
1234         // the table contains exactly one data field and no column fields.
1235         // Display data description at top right corner.
1236         ScSetStringParam aParam;
1237         aParam.setTextInput();
1238         SCCOL nCol = mnDataStartCol;
1239         SCCOL nRow = mnDataStartRow - 1;
1240         mpDocument->SetString(nCol, nRow, nTab, maDataDescription, &aParam);
1241         maFormatOutput.insertEmptyDataColumn(nCol, nRow);
1242     }
1243 
1244     outputDataResults(nTab);
1245 
1246     aOutputImpl.OutputDataArea();
1247 }
1248 
GetOutputRange(sal_Int32 nRegionType)1249 ScRange ScDPOutput::GetOutputRange( sal_Int32 nRegionType )
1250 {
1251     using namespace ::com::sun::star::sheet;
1252 
1253     CalcSizes();
1254 
1255     SCTAB nTab = maStartPos.Tab();
1256     switch (nRegionType)
1257     {
1258         case DataPilotOutputRangeType::RESULT:
1259             return ScRange(mnDataStartCol, mnDataStartRow, nTab, mnTabEndCol, mnTabEndRow, nTab);
1260         case DataPilotOutputRangeType::TABLE:
1261             return ScRange(maStartPos.Col(), mnTabStartRow, nTab, mnTabEndCol, mnTabEndRow, nTab);
1262         default:
1263             OSL_ENSURE(nRegionType == DataPilotOutputRangeType::WHOLE, "ScDPOutput::GetOutputRange: unknown region type");
1264         break;
1265     }
1266     return ScRange(maStartPos.Col(), maStartPos.Row(), nTab, mnTabEndCol, mnTabEndRow, nTab);
1267 }
1268 
HasError()1269 bool ScDPOutput::HasError()
1270 {
1271     CalcSizes();
1272 
1273     return mbSizeOverflow || mbResultsError;
1274 }
1275 
GetHeaderRows() const1276 sal_Int32 ScDPOutput::GetHeaderRows() const
1277 {
1278     return mpPageFields.size() + (mbDoFilter ? 1 : 0);
1279 }
1280 
1281 namespace
1282 {
insertNames(ScDPUniqueStringSet & rNames,const uno::Sequence<sheet::MemberResult> & rMemberResults)1283     void insertNames(ScDPUniqueStringSet& rNames, const uno::Sequence<sheet::MemberResult>& rMemberResults)
1284     {
1285         for (const sheet::MemberResult& rMemberResult : rMemberResults)
1286         {
1287             if (rMemberResult.Flags & sheet::MemberResultFlags::HASMEMBER)
1288                 rNames.insert(rMemberResult.Name);
1289         }
1290     }
1291 }
1292 
GetMemberResultNames(ScDPUniqueStringSet & rNames,tools::Long nDimension)1293 void ScDPOutput::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension)
1294 {
1295     //  Return the list of all member names in a dimension's MemberResults.
1296     //  Only the dimension has to be compared because this is only used with table data,
1297     //  where each dimension occurs only once.
1298 
1299     auto lFindDimension = [nDimension](const ScDPOutLevelData& rField) { return rField.mnDim == nDimension; };
1300 
1301     // look in column fields
1302     auto colit = std::find_if(mpColFields.begin(), mpColFields.end(), lFindDimension);
1303     if (colit != mpColFields.end())
1304     {
1305         // collect the member names
1306         insertNames(rNames, colit->maResult);
1307         return;
1308     }
1309 
1310     // look in row fields
1311     auto rowit = std::find_if(mpRowFields.begin(), mpRowFields.end(), lFindDimension);
1312     if (rowit != mpRowFields.end())
1313     {
1314         // collect the member names
1315         insertNames(rNames, rowit->maResult);
1316     }
1317 }
1318 
SetHeaderLayout(bool bUseGrid)1319 void ScDPOutput::SetHeaderLayout(bool bUseGrid)
1320 {
1321     mbHeaderLayout = bUseGrid;
1322     mbSizesValid = false;
1323 }
1324 
1325 namespace {
1326 
lcl_GetTableVars(sal_Int32 & rGrandTotalCols,sal_Int32 & rGrandTotalRows,sal_Int32 & rDataLayoutIndex,std::vector<OUString> & rDataNames,std::vector<OUString> & rGivenNames,sheet::DataPilotFieldOrientation & rDataOrient,const uno::Reference<sheet::XDimensionsSupplier> & xSource)1327 void lcl_GetTableVars( sal_Int32& rGrandTotalCols, sal_Int32& rGrandTotalRows, sal_Int32& rDataLayoutIndex,
1328                        std::vector<OUString>& rDataNames, std::vector<OUString>& rGivenNames,
1329                        sheet::DataPilotFieldOrientation& rDataOrient,
1330                        const uno::Reference<sheet::XDimensionsSupplier>& xSource )
1331 {
1332     rDataLayoutIndex = -1;  // invalid
1333     rGrandTotalCols = 0;
1334     rGrandTotalRows = 0;
1335     rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN;
1336 
1337     uno::Reference<beans::XPropertySet> xSrcProp( xSource, uno::UNO_QUERY );
1338     bool bColGrand = ScUnoHelpFunctions::GetBoolProperty(
1339         xSrcProp, SC_UNO_DP_COLGRAND);
1340     if ( bColGrand )
1341         rGrandTotalCols = 1;    // default if data layout not in columns
1342 
1343     bool bRowGrand = ScUnoHelpFunctions::GetBoolProperty(
1344         xSrcProp, SC_UNO_DP_ROWGRAND);
1345     if ( bRowGrand )
1346         rGrandTotalRows = 1;    // default if data layout not in rows
1347 
1348     if ( !xSource.is() )
1349         return;
1350 
1351     // find index and orientation of "data layout" dimension, count data dimensions
1352 
1353     sal_Int32 nDataCount = 0;
1354 
1355     uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xSource->getDimensions() );
1356     tools::Long nDimCount = xDims->getCount();
1357     for (tools::Long nDim=0; nDim<nDimCount; nDim++)
1358     {
1359         uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
1360         uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
1361         if ( xDimProp.is() )
1362         {
1363             sheet::DataPilotFieldOrientation eDimOrient =
1364                 ScUnoHelpFunctions::GetEnumProperty(
1365                     xDimProp, SC_UNO_DP_ORIENTATION,
1366                     sheet::DataPilotFieldOrientation_HIDDEN );
1367             if ( ScUnoHelpFunctions::GetBoolProperty( xDimProp,
1368                                      SC_UNO_DP_ISDATALAYOUT ) )
1369             {
1370                 rDataLayoutIndex = nDim;
1371                 rDataOrient = eDimOrient;
1372             }
1373             if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
1374             {
1375                 OUString aSourceName;
1376                 OUString aGivenName;
1377                 ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xDim );
1378                 try
1379                 {
1380                     uno::Any aValue = xDimProp->getPropertyValue( SC_UNO_DP_LAYOUTNAME );
1381 
1382                     if( aValue.hasValue() )
1383                     {
1384                         OUString strLayoutName;
1385 
1386                         if( ( aValue >>= strLayoutName ) && !strLayoutName.isEmpty() )
1387                             aGivenName = strLayoutName;
1388                     }
1389                 }
1390                 catch(const uno::Exception&)
1391                 {
1392                 }
1393                 rDataNames.push_back( aSourceName );
1394                 rGivenNames.push_back( aGivenName );
1395 
1396                 ++nDataCount;
1397             }
1398         }
1399     }
1400 
1401     if ( ( rDataOrient == sheet::DataPilotFieldOrientation_COLUMN ) && bColGrand )
1402         rGrandTotalCols = nDataCount;
1403     else if ( ( rDataOrient == sheet::DataPilotFieldOrientation_ROW ) && bRowGrand )
1404         rGrandTotalRows = nDataCount;
1405 }
1406 
1407 }
1408 
GetRowFieldRange(SCCOL nCol,sal_Int32 & nRowFieldStart,sal_Int32 & nRowFieldEnd) const1409 void ScDPOutput::GetRowFieldRange(SCCOL nCol, sal_Int32& nRowFieldStart, sal_Int32& nRowFieldEnd) const
1410 {
1411     if (!mbHasCompactRowField)
1412     {
1413         nRowFieldStart = nCol;
1414         nRowFieldEnd = nCol + 1;
1415         return;
1416     }
1417 
1418     if (nCol >= static_cast<SCCOL>(maRowCompactFlags.size()))
1419     {
1420         nRowFieldStart = nRowFieldEnd = 0;
1421         return;
1422     }
1423 
1424     nRowFieldStart = -1;
1425     nRowFieldEnd = -1;
1426     SCCOL nCurCol = 0;
1427     sal_Int32 nField = 0;
1428 
1429     for (const auto bCompact: maRowCompactFlags)
1430     {
1431         if (nCurCol == nCol && nRowFieldStart == -1)
1432             nRowFieldStart = nField;
1433 
1434         if (!bCompact)
1435             ++nCurCol;
1436 
1437         ++nField;
1438 
1439         if (nCurCol == (nCol + 1) && nRowFieldStart != -1 && nRowFieldEnd == -1)
1440         {
1441             nRowFieldEnd = nField;
1442             break;
1443         }
1444     }
1445 
1446     if (nRowFieldStart != -1 && nRowFieldEnd == -1 && nCurCol == nCol)
1447         nRowFieldEnd = static_cast<sal_Int32>(maRowCompactFlags.size());
1448 
1449     if (nRowFieldStart == -1 || nRowFieldEnd == -1)
1450     {
1451         SAL_WARN("sc.core", "ScDPOutput::GetRowFieldRange : unable to find field range for nCol = " << nCol);
1452         nRowFieldStart = nRowFieldEnd = 0;
1453     }
1454 }
1455 
GetRowFieldCompact(SCCOL nColQuery,SCROW nRowQuery) const1456 sal_Int32 ScDPOutput::GetRowFieldCompact(SCCOL nColQuery, SCROW nRowQuery) const
1457 {
1458     if (!mbHasCompactRowField)
1459         return nColQuery - mnTabStartCol;
1460 
1461     SCCOL nCol = nColQuery - mnTabStartCol;
1462     sal_Int32 nStartField = 0;
1463     sal_Int32 nEndField = 0;
1464     GetRowFieldRange(nCol, nStartField, nEndField);
1465 
1466     for (sal_Int32 nField = nEndField - 1; nField >= nStartField; --nField)
1467     {
1468         const uno::Sequence<sheet::MemberResult> rSequence = mpRowFields[nField].maResult;
1469         const sheet::MemberResult* pArray = rSequence.getConstArray();
1470         sal_Int32 nThisRowCount = rSequence.getLength();
1471         SCROW nRow = nRowQuery - mnDataStartRow;
1472         if (nRow >= 0 && nRow < nThisRowCount)
1473         {
1474             const sheet::MemberResult& rData = pArray[nRow];
1475             if ((rData.Flags & sheet::MemberResultFlags::HASMEMBER)
1476                 && !(rData.Flags & sheet::MemberResultFlags::SUBTOTAL))
1477             {
1478                 return nField;
1479             }
1480         }
1481     }
1482 
1483     return -1;
1484 }
1485 
GetPositionData(const ScAddress & rPos,DataPilotTablePositionData & rPosData)1486 void ScDPOutput::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData)
1487 {
1488     using namespace ::com::sun::star::sheet;
1489 
1490     SCCOL nCol = rPos.Col();
1491     SCROW nRow = rPos.Row();
1492     SCTAB nTab = rPos.Tab();
1493     if (nTab != maStartPos.Tab())
1494         return; // wrong sheet
1495 
1496     //  calculate output positions and sizes
1497 
1498     CalcSizes();
1499 
1500     rPosData.PositionType = GetPositionType(rPos);
1501     switch (rPosData.PositionType)
1502     {
1503         case DataPilotTablePositionType::RESULT:
1504         {
1505             vector<DataPilotFieldFilter> aFilters;
1506             GetDataResultPositionData(aFilters, rPos);
1507 
1508             DataPilotTableResultData aResData;
1509             aResData.FieldFilters = comphelper::containerToSequence(aFilters);
1510             aResData.DataFieldIndex = 0;
1511             Reference<beans::XPropertySet> xPropSet(mxSource, UNO_QUERY);
1512             if (xPropSet.is())
1513             {
1514                 sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
1515                                             SC_UNO_DP_DATAFIELDCOUNT );
1516                 if (nDataFieldCount > 0)
1517                     aResData.DataFieldIndex = (nRow - mnDataStartRow) % nDataFieldCount;
1518             }
1519 
1520             // Copy appropriate DataResult object from the cached sheet::DataResult table.
1521             if (maData.getLength() > nRow - mnDataStartRow &&
1522                 maData[nRow - mnDataStartRow].getLength() > nCol - mnDataStartCol)
1523                 aResData.Result = maData[nRow - mnDataStartRow][nCol - mnDataStartCol];
1524 
1525             rPosData.PositionData <<= aResData;
1526             return;
1527         }
1528         case DataPilotTablePositionType::COLUMN_HEADER:
1529         {
1530             tools::Long nField = nRow - mnTabStartRow - 1; // 1st line is used for the buttons
1531             if (nField < 0)
1532                 break;
1533 
1534             if (mpColFields.size() < o3tl::make_unsigned(nField) + 1 )
1535                 break;
1536             const uno::Sequence<sheet::MemberResult> rSequence = mpColFields[nField].maResult;
1537             if (!rSequence.hasElements())
1538                 break;
1539             const sheet::MemberResult* pArray = rSequence.getConstArray();
1540 
1541             tools::Long nItem = nCol - mnDataStartCol;
1542             //  get origin of "continue" fields
1543             while (nItem > 0 && ( pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
1544                 --nItem;
1545 
1546             if (nItem < 0)
1547                 break;
1548 
1549             DataPilotTableHeaderData aHeaderData;
1550             aHeaderData.MemberName = pArray[nItem].Name;
1551             aHeaderData.Flags = pArray[nItem].Flags;
1552             aHeaderData.Dimension = static_cast<sal_Int32>(mpColFields[nField].mnDim);
1553             aHeaderData.Hierarchy = static_cast<sal_Int32>(mpColFields[nField].mnHier);
1554             aHeaderData.Level     = static_cast<sal_Int32>(mpColFields[nField].mnLevel);
1555 
1556             rPosData.PositionData <<= aHeaderData;
1557             return;
1558         }
1559         case DataPilotTablePositionType::ROW_HEADER:
1560         {
1561             tools::Long nField = GetRowFieldCompact(nCol, nRow);
1562             if (nField < 0)
1563                 break;
1564 
1565             if (mpRowFields.size() < o3tl::make_unsigned(nField) + 1 )
1566                 break;
1567             const uno::Sequence<sheet::MemberResult> rSequence = mpRowFields[nField].maResult;
1568             if (!rSequence.hasElements())
1569                 break;
1570             const sheet::MemberResult* pArray = rSequence.getConstArray();
1571 
1572             tools::Long nItem = nRow - mnDataStartRow;
1573             //  get origin of "continue" fields
1574             while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
1575                 --nItem;
1576 
1577             if (nItem < 0)
1578                 break;
1579 
1580             DataPilotTableHeaderData aHeaderData;
1581             aHeaderData.MemberName = pArray[nItem].Name;
1582             aHeaderData.Flags = pArray[nItem].Flags;
1583             aHeaderData.Dimension = static_cast<sal_Int32>(mpRowFields[nField].mnDim);
1584             aHeaderData.Hierarchy = static_cast<sal_Int32>(mpRowFields[nField].mnHier);
1585             aHeaderData.Level     = static_cast<sal_Int32>(mpRowFields[nField].mnLevel);
1586 
1587             rPosData.PositionData <<= aHeaderData;
1588             return;
1589         }
1590     }
1591 }
1592 
GetDataResultPositionData(vector<sheet::DataPilotFieldFilter> & rFilters,const ScAddress & rPos)1593 bool ScDPOutput::GetDataResultPositionData(vector<sheet::DataPilotFieldFilter>& rFilters, const ScAddress& rPos)
1594 {
1595     // Check to make sure there is at least one data field.
1596     Reference<beans::XPropertySet> xPropSet(mxSource, UNO_QUERY);
1597     if (!xPropSet.is())
1598         return false;
1599 
1600     sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
1601                                 SC_UNO_DP_DATAFIELDCOUNT );
1602     if (nDataFieldCount == 0)
1603         // No data field is present in this datapilot table.
1604         return false;
1605 
1606     // #i111421# use lcl_GetTableVars for correct size of totals and data layout position
1607     sal_Int32 nGrandTotalCols;
1608     sal_Int32 nGrandTotalRows;
1609     sal_Int32 nDataLayoutIndex;
1610     std::vector<OUString> aDataNames;
1611     std::vector<OUString> aGivenNames;
1612     sheet::DataPilotFieldOrientation eDataOrient;
1613     lcl_GetTableVars( nGrandTotalCols, nGrandTotalRows, nDataLayoutIndex, aDataNames, aGivenNames, eDataOrient, mxSource);
1614 
1615     SCCOL nCol = rPos.Col();
1616     SCROW nRow = rPos.Row();
1617     SCTAB nTab = rPos.Tab();
1618     if (nTab != maStartPos.Tab())
1619         return false; // wrong sheet
1620 
1621     CalcSizes();
1622 
1623     // test for data area.
1624     if (nCol < mnDataStartCol || nCol > mnTabEndCol || nRow < mnDataStartRow || nRow > mnTabEndRow)
1625     {
1626         // Cell is outside the data field area.
1627         return false;
1628     }
1629 
1630     bool bFilterByCol = (nCol <= static_cast<SCCOL>(mnTabEndCol - nGrandTotalCols));
1631     bool bFilterByRow = (nRow <= static_cast<SCROW>(mnTabEndRow - nGrandTotalRows));
1632 
1633     // column fields
1634     for (size_t nColField = 0; nColField < mpColFields.size() && bFilterByCol; ++nColField)
1635     {
1636         if (mpColFields[nColField].mnDim == nDataLayoutIndex)
1637             // There is no sense including the data layout field for filtering.
1638             continue;
1639 
1640         sheet::DataPilotFieldFilter filter;
1641         filter.FieldName = mpColFields[nColField].maName;
1642 
1643         const uno::Sequence<sheet::MemberResult> rSequence = mpColFields[nColField].maResult;
1644         const sheet::MemberResult* pArray = rSequence.getConstArray();
1645 
1646         OSL_ENSURE(mnDataStartCol + rSequence.getLength() - 1 == mnTabEndCol, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
1647 
1648         tools::Long nItem = nCol - mnDataStartCol;
1649                 //  get origin of "continue" fields
1650         while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
1651             --nItem;
1652 
1653         filter.MatchValueName = pArray[nItem].Name;
1654         rFilters.push_back(filter);
1655     }
1656 
1657     // row fields
1658     for (size_t nRowField = 0; nRowField < mpRowFields.size() && bFilterByRow; ++nRowField)
1659     {
1660         if (mpRowFields[nRowField].mnDim == nDataLayoutIndex)
1661             // There is no sense including the data layout field for filtering.
1662             continue;
1663 
1664         sheet::DataPilotFieldFilter filter;
1665         filter.FieldName = mpRowFields[nRowField].maName;
1666 
1667         const uno::Sequence<sheet::MemberResult> rSequence = mpRowFields[nRowField].maResult;
1668         const sheet::MemberResult* pArray = rSequence.getConstArray();
1669 
1670         OSL_ENSURE(mnDataStartRow + rSequence.getLength() - 1 == mnTabEndRow, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
1671 
1672         tools::Long nItem = nRow - mnDataStartRow;
1673             //  get origin of "continue" fields
1674         while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
1675             --nItem;
1676 
1677         filter.MatchValueName = pArray[nItem].Name;
1678         rFilters.push_back(filter);
1679     }
1680 
1681     return true;
1682 }
1683 
1684 namespace {
1685 
lcl_GetDataFieldName(std::u16string_view rSourceName,sal_Int16 eFunc)1686 OUString lcl_GetDataFieldName( std::u16string_view rSourceName, sal_Int16 eFunc )
1687 {
1688     TranslateId pStrId;
1689     switch ( eFunc )
1690     {
1691         case sheet::GeneralFunction2::SUM:        pStrId = STR_FUN_TEXT_SUM;      break;
1692         case sheet::GeneralFunction2::COUNT:
1693         case sheet::GeneralFunction2::COUNTNUMS:  pStrId = STR_FUN_TEXT_COUNT;    break;
1694         case sheet::GeneralFunction2::AVERAGE:    pStrId = STR_FUN_TEXT_AVG;      break;
1695         case sheet::GeneralFunction2::MEDIAN:     pStrId = STR_FUN_TEXT_MEDIAN;   break;
1696         case sheet::GeneralFunction2::MAX:        pStrId = STR_FUN_TEXT_MAX;      break;
1697         case sheet::GeneralFunction2::MIN:        pStrId = STR_FUN_TEXT_MIN;      break;
1698         case sheet::GeneralFunction2::PRODUCT:    pStrId = STR_FUN_TEXT_PRODUCT;  break;
1699         case sheet::GeneralFunction2::STDEV:
1700         case sheet::GeneralFunction2::STDEVP:     pStrId = STR_FUN_TEXT_STDDEV;   break;
1701         case sheet::GeneralFunction2::VAR:
1702         case sheet::GeneralFunction2::VARP:       pStrId = STR_FUN_TEXT_VAR;      break;
1703         case sheet::GeneralFunction2::NONE:
1704         case sheet::GeneralFunction2::AUTO:                                       break;
1705         default:
1706         {
1707             assert(false);
1708         }
1709     }
1710     if (!pStrId)
1711         return OUString();
1712 
1713     return ScResId(pStrId) + " - " + rSourceName;
1714 }
1715 
1716 }
1717 
GetDataDimensionNames(OUString & rSourceName,OUString & rGivenName,const uno::Reference<uno::XInterface> & xDim)1718 void ScDPOutput::GetDataDimensionNames(
1719     OUString& rSourceName, OUString& rGivenName, const uno::Reference<uno::XInterface>& xDim )
1720 {
1721     uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
1722     uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
1723     if ( !(xDimProp.is() && xDimName.is()) )
1724         return;
1725 
1726     // Asterisks are added in ScDPSaveData::WriteToSource to create unique names.
1727     //TODO: preserve original name there?
1728     rSourceName = ScDPUtil::getSourceDimensionName(xDimName->getName());
1729 
1730     // Generate "given name" the same way as in dptabres.
1731     //TODO: Should use a stored name when available
1732 
1733     sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty(
1734                       xDimProp, SC_UNO_DP_FUNCTION2,
1735                       sheet::GeneralFunction2::NONE );
1736     rGivenName = lcl_GetDataFieldName( rSourceName, eFunc );
1737 }
1738 
IsFilterButton(const ScAddress & rPos)1739 bool ScDPOutput::IsFilterButton( const ScAddress& rPos )
1740 {
1741     SCCOL nCol = rPos.Col();
1742     SCROW nRow = rPos.Row();
1743     SCTAB nTab = rPos.Tab();
1744     if (nTab != maStartPos.Tab() || !mbDoFilter)
1745         return false;                               // wrong sheet or no button at all
1746 
1747     //  filter button is at top left
1748     return nCol == maStartPos.Col() && nRow == maStartPos.Row();
1749 }
1750 
GetHeaderDim(const ScAddress & rPos,sheet::DataPilotFieldOrientation & rOrient)1751 tools::Long ScDPOutput::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient )
1752 {
1753     SCCOL nCol = rPos.Col();
1754     SCROW nRow = rPos.Row();
1755     SCTAB nTab = rPos.Tab();
1756     if (nTab != maStartPos.Tab())
1757         return -1;                                      // wrong sheet
1758 
1759     //  calculate output positions and sizes
1760 
1761     CalcSizes();
1762 
1763     //  test for column header
1764 
1765     if ( nRow == mnTabStartRow && nCol >= mnDataStartCol && o3tl::make_unsigned(nCol) < mnDataStartCol + mpColFields.size())
1766     {
1767         rOrient = sheet::DataPilotFieldOrientation_COLUMN;
1768         tools::Long nField = nCol - mnDataStartCol;
1769         return mpColFields[nField].mnDim;
1770     }
1771 
1772     //  test for row header
1773 
1774     if ( nRow+1 == mnDataStartRow && nCol >= mnTabStartCol && o3tl::make_unsigned(nCol) < mnTabStartCol + mpRowFields.size() )
1775     {
1776         rOrient = sheet::DataPilotFieldOrientation_ROW;
1777         tools::Long nField = nCol - mnTabStartCol;
1778         return mpRowFields[nField].mnDim;
1779     }
1780 
1781     //  test for page field
1782 
1783     SCROW nPageStartRow = maStartPos.Row() + (mbDoFilter ? 1 : 0);
1784     if ( nCol == maStartPos.Col() && nRow >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + mpPageFields.size() )
1785     {
1786         rOrient = sheet::DataPilotFieldOrientation_PAGE;
1787         tools::Long nField = nRow - nPageStartRow;
1788         return mpPageFields[nField].mnDim;
1789     }
1790 
1791     //TODO: single data field (?)
1792 
1793     rOrient = sheet::DataPilotFieldOrientation_HIDDEN;
1794     return -1;      // invalid
1795 }
1796 
GetHeaderDrag(const ScAddress & rPos,bool bMouseLeft,bool bMouseTop,tools::Long nDragDim,tools::Rectangle & rPosRect,sheet::DataPilotFieldOrientation & rOrient,tools::Long & rDimPos)1797 bool ScDPOutput::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop,
1798                                 tools::Long nDragDim,
1799                                 tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos )
1800 {
1801     //  Rectangle instead of ScRange for rPosRect to allow for negative values
1802 
1803     SCCOL nCol = rPos.Col();
1804     SCROW nRow = rPos.Row();
1805     SCTAB nTab = rPos.Tab();
1806     if ( nTab != maStartPos.Tab() )
1807         return false;                                       // wrong sheet
1808 
1809     //  calculate output positions and sizes
1810 
1811     CalcSizes();
1812 
1813     //  test for column header
1814 
1815     if ( nCol >= mnDataStartCol && nCol <= mnTabEndCol &&
1816             nRow + 1 >= mnMemberStartRow && o3tl::make_unsigned(nRow) < mnMemberStartRow + mpColFields.size())
1817     {
1818         tools::Long nField = nRow - mnMemberStartRow;
1819         if (nField < 0)
1820         {
1821             nField = 0;
1822             bMouseTop = true;
1823         }
1824         //TODO: find start of dimension
1825 
1826         rPosRect = tools::Rectangle(mnDataStartCol, mnMemberStartRow + nField,
1827                                     mnTabEndCol, mnMemberStartRow + nField - 1);
1828 
1829         bool bFound = false;            // is this within the same orientation?
1830         bool bBeforeDrag = false;
1831         bool bAfterDrag = false;
1832         for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<mpColFields.size() && !bFound; nPos++)
1833         {
1834             if (mpColFields[nPos].mnDim == nDragDim)
1835             {
1836                 bFound = true;
1837                 if ( nField < nPos )
1838                     bBeforeDrag = true;
1839                 else if ( nField > nPos )
1840                     bAfterDrag = true;
1841             }
1842         }
1843 
1844         if ( bFound )
1845         {
1846             if (!bBeforeDrag)
1847             {
1848                 rPosRect.AdjustBottom( 1 );
1849                 if (bAfterDrag)
1850                     rPosRect.AdjustTop( 1 );
1851             }
1852         }
1853         else
1854         {
1855             if ( !bMouseTop )
1856             {
1857                 rPosRect.AdjustTop( 1 );
1858                 rPosRect.AdjustBottom( 1 );
1859                 ++nField;
1860             }
1861         }
1862 
1863         rOrient = sheet::DataPilotFieldOrientation_COLUMN;
1864         rDimPos = nField;                       //!...
1865         return true;
1866     }
1867 
1868     //  test for row header
1869 
1870     //  special case if no row fields
1871     bool bSpecial = ( nRow+1 >= mnDataStartRow && nRow <= mnTabEndRow &&
1872                         mpRowFields.empty() && nCol == mnTabStartCol && bMouseLeft );
1873 
1874     if ( bSpecial || ( nRow+1 >= mnDataStartRow && nRow <= mnTabEndRow &&
1875                         nCol + 1 >= mnTabStartCol && o3tl::make_unsigned(nCol) < mnTabStartCol + mpRowFields.size() ) )
1876     {
1877         tools::Long nField = nCol - mnTabStartCol;
1878         //TODO: find start of dimension
1879 
1880         rPosRect = tools::Rectangle(mnTabStartCol + nField, mnDataStartRow - 1,
1881                               mnTabStartCol + nField - 1, mnTabEndRow);
1882 
1883         bool bFound = false;            // is this within the same orientation?
1884         bool bBeforeDrag = false;
1885         bool bAfterDrag = false;
1886         for (tools::Long nPos = 0; o3tl::make_unsigned(nPos) < mpRowFields.size() && !bFound; nPos++)
1887         {
1888             if (mpRowFields[nPos].mnDim == nDragDim)
1889             {
1890                 bFound = true;
1891                 if ( nField < nPos )
1892                     bBeforeDrag = true;
1893                 else if ( nField > nPos )
1894                     bAfterDrag = true;
1895             }
1896         }
1897 
1898         if ( bFound )
1899         {
1900             if (!bBeforeDrag)
1901             {
1902                 rPosRect.AdjustRight( 1 );
1903                 if (bAfterDrag)
1904                     rPosRect.AdjustLeft( 1 );
1905             }
1906         }
1907         else
1908         {
1909             if ( !bMouseLeft )
1910             {
1911                 rPosRect.AdjustLeft( 1 );
1912                 rPosRect.AdjustRight( 1 );
1913                 ++nField;
1914             }
1915         }
1916 
1917         rOrient = sheet::DataPilotFieldOrientation_ROW;
1918         rDimPos = nField;                       //!...
1919         return true;
1920     }
1921 
1922     //  test for page fields
1923 
1924     SCROW nPageStartRow = maStartPos.Row() + (mbDoFilter ? 1 : 0);
1925     if (nCol >= maStartPos.Col() && nCol <= mnTabEndCol &&
1926             nRow + 1 >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + mpPageFields.size())
1927     {
1928         tools::Long nField = nRow - nPageStartRow;
1929         if (nField < 0)
1930         {
1931             nField = 0;
1932             bMouseTop = true;
1933         }
1934         //TODO: find start of dimension
1935 
1936         rPosRect = tools::Rectangle(maStartPos.Col(), nPageStartRow + nField,
1937                                     mnTabEndCol, nPageStartRow + nField - 1);
1938 
1939         bool bFound = false;            // is this within the same orientation?
1940         bool bBeforeDrag = false;
1941         bool bAfterDrag = false;
1942         for (tools::Long nPos = 0; o3tl::make_unsigned(nPos) < mpPageFields.size() && !bFound; nPos++)
1943         {
1944             if (mpPageFields[nPos].mnDim == nDragDim)
1945             {
1946                 bFound = true;
1947                 if ( nField < nPos )
1948                     bBeforeDrag = true;
1949                 else if ( nField > nPos )
1950                     bAfterDrag = true;
1951             }
1952         }
1953 
1954         if ( bFound )
1955         {
1956             if (!bBeforeDrag)
1957             {
1958                 rPosRect.AdjustBottom( 1 );
1959                 if (bAfterDrag)
1960                     rPosRect.AdjustTop( 1 );
1961             }
1962         }
1963         else
1964         {
1965             if ( !bMouseTop )
1966             {
1967                 rPosRect.AdjustTop( 1 );
1968                 rPosRect.AdjustBottom( 1 );
1969                 ++nField;
1970             }
1971         }
1972 
1973         rOrient = sheet::DataPilotFieldOrientation_PAGE;
1974         rDimPos = nField;                       //!...
1975         return true;
1976     }
1977 
1978     return false;
1979 }
1980 
1981 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1982