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