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 <dbfunc.hxx>
21 #include <scitems.hxx>
22 #include <vcl/svapp.hxx>
23 #include <vcl/weld.hxx>
24 #include <svl/zforlist.hxx>
25 #include <sfx2/app.hxx>
26 #include <unotools/collatorwrapper.hxx>
27 #include <com/sun/star/beans/XPropertySet.hpp>
28 #include <com/sun/star/container/XNameAccess.hpp>
29 #include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
30 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
31 #include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
32 #include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
33 #include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
34 #include <com/sun/star/sheet/MemberResultFlags.hpp>
35 #include <com/sun/star/sheet/XDimensionsSupplier.hpp>
36 #include <com/sun/star/sheet/XDrillDownDataSupplier.hpp>
37 
38 #include <global.hxx>
39 #include <scresid.hxx>
40 #include <globstr.hrc>
41 #include <undotab.hxx>
42 #include <undodat.hxx>
43 #include <dbdata.hxx>
44 #include <rangenam.hxx>
45 #include <docsh.hxx>
46 #include <olinetab.hxx>
47 #include <olinefun.hxx>
48 #include <dpobject.hxx>
49 #include <dpsave.hxx>
50 #include <dpdimsave.hxx>
51 #include <dbdocfun.hxx>
52 #include <dpoutput.hxx>
53 #include <editable.hxx>
54 #include <docpool.hxx>
55 #include <patattr.hxx>
56 #include <unonames.hxx>
57 #include <userlist.hxx>
58 #include <queryentry.hxx>
59 #include <markdata.hxx>
60 #include <tabvwsh.hxx>
61 #include <generalfunction.hxx>
62 #include <sortparam.hxx>
63 
64 #include <comphelper/lok.hxx>
65 
66 #include <memory>
67 #include <unordered_set>
68 #include <unordered_map>
69 #include <vector>
70 #include <set>
71 #include <algorithm>
72 
73 using namespace com::sun::star;
74 using ::com::sun::star::uno::Any;
75 using ::com::sun::star::uno::Sequence;
76 using ::com::sun::star::uno::Reference;
77 using ::com::sun::star::uno::UNO_QUERY;
78 using ::com::sun::star::beans::XPropertySet;
79 using ::com::sun::star::container::XNameAccess;
80 using ::com::sun::star::sheet::XDimensionsSupplier;
81 using ::std::vector;
82 
83 //          outliner
84 
85 // create outline grouping
86 
MakeOutline(bool bColumns,bool bRecord)87 void ScDBFunc::MakeOutline( bool bColumns, bool bRecord )
88 {
89     ScRange aRange;
90     if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
91     {
92         ScDocShell* pDocSh = GetViewData().GetDocShell();
93         ScOutlineDocFunc aFunc(*pDocSh);
94         aFunc.MakeOutline( aRange, bColumns, bRecord, false );
95 
96         ScTabViewShell::notifyAllViewsHeaderInvalidation(bColumns, GetViewData().GetTabNo());
97     }
98     else
99         ErrorMessage(STR_NOMULTISELECT);
100 }
101 
102 // delete outline grouping
103 
RemoveOutline(bool bColumns,bool bRecord)104 void ScDBFunc::RemoveOutline( bool bColumns, bool bRecord )
105 {
106     ScRange aRange;
107     if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
108     {
109         ScDocShell* pDocSh = GetViewData().GetDocShell();
110         ScOutlineDocFunc aFunc(*pDocSh);
111         aFunc.RemoveOutline( aRange, bColumns, bRecord, false );
112 
113         ScTabViewShell::notifyAllViewsHeaderInvalidation(bColumns, GetViewData().GetTabNo());
114     }
115     else
116         ErrorMessage(STR_NOMULTISELECT);
117 }
118 
119 //  menu status: delete outlines
120 
TestRemoveOutline(bool & rCol,bool & rRow)121 void ScDBFunc::TestRemoveOutline( bool& rCol, bool& rRow )
122 {
123     bool bColFound = false;
124     bool bRowFound = false;
125 
126     SCCOL nStartCol, nEndCol;
127     SCROW nStartRow, nEndRow;
128     SCTAB nStartTab, nEndTab;
129     if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE)
130     {
131         SCTAB nTab = nStartTab;
132         ScDocument* pDoc = GetViewData().GetDocument();
133         ScOutlineTable* pTable = pDoc->GetOutlineTable( nTab );
134         if (pTable)
135         {
136             ScOutlineEntry* pEntry;
137             SCCOLROW nStart;
138             SCCOLROW nEnd;
139             bool bColMarked = ( nStartRow == 0 && nEndRow == pDoc->MaxRow() );
140             bool bRowMarked = ( nStartCol == 0 && nEndCol == pDoc->MaxCol() );
141 
142             // columns
143 
144             if ( !bRowMarked || bColMarked )        // not when entire rows are marked
145             {
146                 ScOutlineArray& rArray = pTable->GetColArray();
147                 ScSubOutlineIterator aColIter( &rArray );
148                 while ((pEntry=aColIter.GetNext()) != nullptr && !bColFound)
149                 {
150                     nStart = pEntry->GetStart();
151                     nEnd   = pEntry->GetEnd();
152                     if ( nStartCol<=static_cast<SCCOL>(nEnd) && nEndCol>=static_cast<SCCOL>(nStart) )
153                         bColFound = true;
154                 }
155             }
156 
157             // rows
158 
159             if ( !bColMarked || bRowMarked )        // not when entire columns are marked
160             {
161                 ScOutlineArray& rArray = pTable->GetRowArray();
162                 ScSubOutlineIterator aRowIter( &rArray );
163                 while ((pEntry=aRowIter.GetNext()) != nullptr && !bRowFound)
164                 {
165                     nStart = pEntry->GetStart();
166                     nEnd   = pEntry->GetEnd();
167                     if ( nStartRow<=nEnd && nEndRow>=nStart )
168                         bRowFound = true;
169                 }
170             }
171         }
172     }
173 
174     rCol = bColFound;
175     rRow = bRowFound;
176 }
177 
RemoveAllOutlines(bool bRecord)178 void ScDBFunc::RemoveAllOutlines( bool bRecord )
179 {
180     SCTAB nTab = GetViewData().GetTabNo();
181     ScDocShell* pDocSh = GetViewData().GetDocShell();
182     ScOutlineDocFunc aFunc(*pDocSh);
183 
184     bool bOk = aFunc.RemoveAllOutlines( nTab, bRecord );
185 
186     if (bOk)
187     {
188         UpdateScrollBars(BOTH_HEADERS);
189     }
190 }
191 
192 // auto outlines
193 
AutoOutline()194 void ScDBFunc::AutoOutline( )
195 {
196     ScDocument* pDoc = GetViewData().GetDocument();
197     SCTAB nTab = GetViewData().GetTabNo();
198     ScRange aRange( 0,0,nTab, pDoc->MaxCol(),pDoc->MaxRow(),nTab );     // the complete sheet, if nothing is marked
199     ScMarkData& rMark = GetViewData().GetMarkData();
200     if ( rMark.IsMarked() || rMark.IsMultiMarked() )
201     {
202         rMark.MarkToMulti();
203         rMark.GetMultiMarkArea( aRange );
204     }
205 
206     ScDocShell* pDocSh = GetViewData().GetDocShell();
207     ScOutlineDocFunc aFunc(*pDocSh);
208     aFunc.AutoOutline( aRange, true );
209 }
210 
211 // select outline level
212 
SelectLevel(bool bColumns,sal_uInt16 nLevel,bool bRecord)213 void ScDBFunc::SelectLevel( bool bColumns, sal_uInt16 nLevel, bool bRecord )
214 {
215     SCTAB nTab = GetViewData().GetTabNo();
216     ScDocShell* pDocSh = GetViewData().GetDocShell();
217     ScOutlineDocFunc aFunc(*pDocSh);
218 
219     bool bOk = aFunc.SelectLevel( nTab, bColumns, nLevel, bRecord, true/*bPaint*/ );
220 
221     if (bOk)
222         UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER);
223 }
224 
225 // show individual outline groups
226 
SetOutlineState(bool bColumns,sal_uInt16 nLevel,sal_uInt16 nEntry,bool bHidden)227 void ScDBFunc::SetOutlineState( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bHidden)
228 {
229     const sal_uInt16 nHeadEntry = static_cast< sal_uInt16 >( -1 );
230     if ( nEntry ==  nHeadEntry)
231         SelectLevel( bColumns, sal::static_int_cast<sal_uInt16>(nLevel) );
232     else
233     {
234         if ( !bHidden )
235             ShowOutline( bColumns, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) );
236         else
237             HideOutline( bColumns, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) );
238     }
239 }
240 
ShowOutline(bool bColumns,sal_uInt16 nLevel,sal_uInt16 nEntry,bool bRecord,bool bPaint)241 void ScDBFunc::ShowOutline( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bRecord, bool bPaint )
242 {
243     SCTAB nTab = GetViewData().GetTabNo();
244     ScDocShell* pDocSh = GetViewData().GetDocShell();
245     ScOutlineDocFunc aFunc(*pDocSh);
246 
247     aFunc.ShowOutline( nTab, bColumns, nLevel, nEntry, bRecord, bPaint );
248 
249     if ( bPaint )
250         UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER);
251 }
252 
253 // hide individual outline groups
254 
HideOutline(bool bColumns,sal_uInt16 nLevel,sal_uInt16 nEntry,bool bRecord,bool bPaint)255 void ScDBFunc::HideOutline( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bRecord, bool bPaint )
256 {
257     SCTAB nTab = GetViewData().GetTabNo();
258     ScDocShell* pDocSh = GetViewData().GetDocShell();
259     ScOutlineDocFunc aFunc(*pDocSh);
260 
261     bool bOk = aFunc.HideOutline( nTab, bColumns, nLevel, nEntry, bRecord, bPaint );
262 
263     if ( bOk && bPaint )
264         UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER);
265 }
266 
267 // menu status: show/hide marked range
268 
OutlinePossible(bool bHide)269 bool ScDBFunc::OutlinePossible(bool bHide)
270 {
271     bool bEnable = false;
272 
273     SCCOL nStartCol;
274     SCROW nStartRow;
275     SCTAB nStartTab;
276     SCCOL nEndCol;
277     SCROW nEndRow;
278     SCTAB nEndTab;
279 
280     if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE)
281     {
282         ScDocument* pDoc = GetViewData().GetDocument();
283         SCTAB nTab = GetViewData().GetTabNo();
284         ScOutlineTable* pTable = pDoc->GetOutlineTable( nTab );
285         if (pTable)
286         {
287             ScOutlineEntry* pEntry;
288             SCCOLROW nStart;
289             SCCOLROW nEnd;
290 
291             // columns
292 
293             ScOutlineArray& rColArray = pTable->GetColArray();
294             ScSubOutlineIterator aColIter( &rColArray );
295             while ((pEntry=aColIter.GetNext()) != nullptr && !bEnable)
296             {
297                 nStart = pEntry->GetStart();
298                 nEnd   = pEntry->GetEnd();
299                 if ( bHide )
300                 {
301                     if ( nStartCol<=static_cast<SCCOL>(nEnd) && nEndCol>=static_cast<SCCOL>(nStart) )
302                         if (!pEntry->IsHidden())
303                             bEnable = true;
304                 }
305                 else
306                 {
307                     if ( nStart>=nStartCol && nEnd<=nEndCol )
308                         if (pEntry->IsHidden())
309                             bEnable = true;
310                 }
311             }
312 
313             // rows
314 
315             ScOutlineArray& rRowArray = pTable->GetRowArray();
316             ScSubOutlineIterator aRowIter( &rRowArray );
317             while ((pEntry=aRowIter.GetNext()) != nullptr)
318             {
319                 nStart = pEntry->GetStart();
320                 nEnd   = pEntry->GetEnd();
321                 if ( bHide )
322                 {
323                     if ( nStartRow<=nEnd && nEndRow>=nStart )
324                         if (!pEntry->IsHidden())
325                             bEnable = true;
326                 }
327                 else
328                 {
329                     if ( nStart>=nStartRow && nEnd<=nEndRow )
330                         if (pEntry->IsHidden())
331                             bEnable = true;
332                 }
333             }
334         }
335     }
336 
337     return bEnable;
338 }
339 
340 // show marked range
341 
ShowMarkedOutlines(bool bRecord)342 void ScDBFunc::ShowMarkedOutlines( bool bRecord )
343 {
344     ScRange aRange;
345     if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
346     {
347         ScDocShell* pDocSh = GetViewData().GetDocShell();
348         ScOutlineDocFunc aFunc(*pDocSh);
349         bool bDone = aFunc.ShowMarkedOutlines( aRange, bRecord );
350         if (bDone)
351             UpdateScrollBars();
352     }
353     else
354         ErrorMessage(STR_NOMULTISELECT);
355 }
356 
357 // hide marked range
358 
HideMarkedOutlines(bool bRecord)359 void ScDBFunc::HideMarkedOutlines( bool bRecord )
360 {
361     ScRange aRange;
362     if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
363     {
364         ScDocShell* pDocSh = GetViewData().GetDocShell();
365         ScOutlineDocFunc aFunc(*pDocSh);
366         bool bDone = aFunc.HideMarkedOutlines( aRange, bRecord );
367         if (bDone)
368             UpdateScrollBars();
369     }
370     else
371         ErrorMessage(STR_NOMULTISELECT);
372 }
373 
374 // sub totals
375 
DoSubTotals(const ScSubTotalParam & rParam,bool bRecord,const ScSortParam * pForceNewSort)376 void ScDBFunc::DoSubTotals( const ScSubTotalParam& rParam, bool bRecord,
377                             const ScSortParam* pForceNewSort )
378 {
379     bool bDo = !rParam.bRemoveOnly;                         // sal_False = only delete
380 
381     ScDocShell* pDocSh = GetViewData().GetDocShell();
382     ScDocument& rDoc = pDocSh->GetDocument();
383     ScMarkData& rMark = GetViewData().GetMarkData();
384     SCTAB nTab = GetViewData().GetTabNo();
385     if (bRecord && !rDoc.IsUndoEnabled())
386         bRecord = false;
387 
388     ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rParam.nCol1, rParam.nRow1,
389                                                 rParam.nCol2, rParam.nRow2 );
390     if (!pDBData)
391     {
392         OSL_FAIL( "SubTotals: no DBData" );
393         return;
394     }
395 
396     ScEditableTester aTester( &rDoc, nTab, 0,rParam.nRow1+1, rDoc.MaxCol(),rDoc.MaxRow() );
397     if (!aTester.IsEditable())
398     {
399         ErrorMessage(aTester.GetMessageId());
400         return;
401     }
402 
403     if (rDoc.HasAttrib( rParam.nCol1, rParam.nRow1+1, nTab,
404                          rParam.nCol2, rParam.nRow2, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
405     {
406         ErrorMessage(STR_MSSG_INSERTCELLS_0);   // do not insert into merged
407         return;
408     }
409 
410     weld::WaitObject aWait(GetViewData().GetDialogParent());
411     bool bOk = true;
412     if (rParam.bReplace)
413     {
414         if (rDoc.TestRemoveSubTotals( nTab, rParam ))
415         {
416             std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(),
417                                                       VclMessageType::Question, VclButtonsType::YesNo,
418                                                       ScResId(STR_MSSG_DOSUBTOTALS_1))); // "delete data?"
419             xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); // "StarCalc"
420             xBox->set_default_response(RET_YES);
421             bOk = xBox->run() == RET_YES;
422         }
423     }
424 
425     if (bOk)
426     {
427         ScDocShellModificator aModificator( *pDocSh );
428 
429         ScSubTotalParam aNewParam( rParam );        // change end of range
430         ScDocumentUniquePtr pUndoDoc;
431         std::unique_ptr<ScOutlineTable> pUndoTab;
432         std::unique_ptr<ScRangeName> pUndoRange;
433         std::unique_ptr<ScDBCollection> pUndoDB;
434 
435         if (bRecord)                                        // record old data
436         {
437             bool bOldFilter = bDo && rParam.bDoSort;
438             SCTAB nTabCount = rDoc.GetTableCount();
439             pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
440             ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab );
441             if (pTable)
442             {
443                 pUndoTab.reset(new ScOutlineTable( *pTable ));
444 
445                 SCCOLROW nOutStartCol;                          // row/column status
446                 SCCOLROW nOutStartRow;
447                 SCCOLROW nOutEndCol;
448                 SCCOLROW nOutEndRow;
449                 pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol );
450                 pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow );
451 
452                 pUndoDoc->InitUndo( &rDoc, nTab, nTab, true, true );
453                 rDoc.CopyToDocument( static_cast<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(nOutEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
454                 rDoc.CopyToDocument( 0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
455             }
456             else
457                 pUndoDoc->InitUndo( &rDoc, nTab, nTab, false, bOldFilter );
458 
459             // record data range - including filter results
460             rDoc.CopyToDocument( 0,rParam.nRow1+1,nTab, rDoc.MaxCol(),rParam.nRow2,nTab,
461                                     InsertDeleteFlags::ALL, false, *pUndoDoc );
462 
463             // all formulas for reference
464             rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1,
465                                         InsertDeleteFlags::FORMULA, false, *pUndoDoc );
466 
467             // database and other ranges
468             ScRangeName* pDocRange = rDoc.GetRangeName();
469             if (!pDocRange->empty())
470                 pUndoRange.reset(new ScRangeName( *pDocRange ));
471             ScDBCollection* pDocDB = rDoc.GetDBCollection();
472             if (!pDocDB->empty())
473                 pUndoDB.reset(new ScDBCollection( *pDocDB ));
474         }
475 
476         ScOutlineTable* pOut = rDoc.GetOutlineTable( nTab );
477         if (pOut)
478         {
479             // Remove all existing outlines in the specified range.
480             ScOutlineArray& rRowArray = pOut->GetRowArray();
481             sal_uInt16 nDepth = rRowArray.GetDepth();
482             for (sal_uInt16 i = 0; i < nDepth; ++i)
483             {
484                 bool bSize;
485                 rRowArray.Remove(aNewParam.nRow1, aNewParam.nRow2, bSize);
486             }
487         }
488 
489         if (rParam.bReplace)
490             rDoc.RemoveSubTotals( nTab, aNewParam );
491         bool bSuccess = true;
492         if (bDo)
493         {
494             // Sort
495             if ( rParam.bDoSort || pForceNewSort )
496             {
497                 pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 );
498 
499                 // set subtotal fields before sorting
500                 // (duplicate values are dropped, so that they can be called again)
501 
502                 ScSortParam aOldSort;
503                 pDBData->GetSortParam( aOldSort );
504                 ScSortParam aSortParam( aNewParam, pForceNewSort ? *pForceNewSort : aOldSort );
505                 Sort( aSortParam, false, false );
506             }
507 
508             bSuccess = rDoc.DoSubTotals( nTab, aNewParam );
509         }
510         ScRange aDirtyRange( aNewParam.nCol1, aNewParam.nRow1, nTab,
511             aNewParam.nCol2, aNewParam.nRow2, nTab );
512         rDoc.SetDirty( aDirtyRange, true );
513 
514         if (bRecord)
515         {
516             pDocSh->GetUndoManager()->AddUndoAction(
517                 std::make_unique<ScUndoSubTotals>( pDocSh, nTab,
518                                         rParam, aNewParam.nRow2,
519                                         std::move(pUndoDoc), std::move(pUndoTab), // pUndoDBData,
520                                         std::move(pUndoRange), std::move(pUndoDB) ) );
521         }
522 
523         if (!bSuccess)
524         {
525             // "Can not insert any rows"
526             ErrorMessage(STR_MSSG_DOSUBTOTALS_2);
527         }
528 
529                                                     // store
530         pDBData->SetSubTotalParam( aNewParam );
531         pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 );
532         rDoc.CompileDBFormula();
533 
534         DoneBlockMode();
535         InitOwnBlockMode();
536         rMark.SetMarkArea( ScRange( aNewParam.nCol1,aNewParam.nRow1,nTab,
537                                     aNewParam.nCol2,aNewParam.nRow2,nTab ) );
538         MarkDataChanged();
539 
540         pDocSh->PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab),
541                           PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size);
542 
543         aModificator.SetDocumentModified();
544 
545         SelectionChanged();
546     }
547 }
548 
549 // consolidate
550 
Consolidate(const ScConsolidateParam & rParam)551 void ScDBFunc::Consolidate( const ScConsolidateParam& rParam )
552 {
553     ScDocShell* pDocShell = GetViewData().GetDocShell();
554     pDocShell->DoConsolidate( rParam );
555     SetTabNo( rParam.nTab, true );
556 }
557 
558 // pivot
559 
lcl_MakePivotTabName(const OUString & rPrefix,SCTAB nNumber)560 static OUString lcl_MakePivotTabName( const OUString& rPrefix, SCTAB nNumber )
561 {
562     OUString aName = rPrefix + OUString::number( nNumber );
563     return aName;
564 }
565 
MakePivotTable(const ScDPSaveData & rData,const ScRange & rDest,bool bNewTable,const ScDPObject & rSource)566 bool ScDBFunc::MakePivotTable(
567     const ScDPSaveData& rData, const ScRange& rDest, bool bNewTable,
568     const ScDPObject& rSource )
569 {
570     //  error message if no fields are set
571     //  this must be removed when drag&drop of fields from a toolbox is available
572 
573     if ( rData.IsEmpty() )
574     {
575         ErrorMessage(STR_PIVOT_NODATA);
576         return false;
577     }
578 
579     ScDocShell* pDocSh  = GetViewData().GetDocShell();
580     ScDocument* pDoc    = GetViewData().GetDocument();
581     bool bUndo = pDoc->IsUndoEnabled();
582 
583     ScRange aDestRange = rDest;
584     if ( bNewTable )
585     {
586         SCTAB nSrcTab = GetViewData().GetTabNo();
587 
588         OUString aName( ScResId(STR_PIVOT_TABLE) );
589         OUString aStr;
590 
591         pDoc->GetName( nSrcTab, aStr );
592         aName += "_" + aStr + "_";
593 
594         SCTAB nNewTab = nSrcTab+1;
595 
596         SCTAB i=1;
597         while ( !pDoc->InsertTab( nNewTab, lcl_MakePivotTabName( aName, i ) ) && i <= MAXTAB )
598             i++;
599 
600         bool bAppend = ( nNewTab+1 == pDoc->GetTableCount() );
601         if (bUndo)
602         {
603             pDocSh->GetUndoManager()->AddUndoAction(
604                         std::make_unique<ScUndoInsertTab>( pDocSh, nNewTab, bAppend, lcl_MakePivotTabName( aName, i ) ));
605         }
606 
607         GetViewData().InsertTab( nNewTab );
608         SetTabNo(nNewTab, true);
609 
610         aDestRange = ScRange( 0, 0, nNewTab );
611     }
612 
613     ScDPObject* pDPObj = pDoc->GetDPAtCursor(
614                             aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aStart.Tab() );
615 
616     ScDPObject aObj( rSource );
617     aObj.SetOutRange( aDestRange );
618     if ( pDPObj && !rData.GetExistingDimensionData() )
619     {
620         // copy dimension data from old object - lost in the dialog
621         //! change the dialog to keep the dimension data
622 
623         ScDPSaveData aNewData( rData );
624         const ScDPSaveData* pOldData = pDPObj->GetSaveData();
625         if ( pOldData )
626         {
627             const ScDPDimensionSaveData* pDimSave = pOldData->GetExistingDimensionData();
628             aNewData.SetDimensionData( pDimSave );
629         }
630         aObj.SetSaveData( aNewData );
631     }
632     else
633         aObj.SetSaveData( rData );
634 
635     bool bAllowMove = (pDPObj != nullptr);   // allow re-positioning when editing existing table
636 
637     ScDBDocFunc aFunc( *pDocSh );
638     bool bSuccess = aFunc.DataPilotUpdate(pDPObj, &aObj, true, false, bAllowMove);
639 
640     CursorPosChanged();     // shells may be switched
641 
642     if ( bNewTable )
643     {
644         pDocSh->PostPaintExtras();
645         SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
646     }
647 
648     return bSuccess;
649 }
650 
DeletePivotTable()651 void ScDBFunc::DeletePivotTable()
652 {
653     ScDocShell* pDocSh    = GetViewData().GetDocShell();
654     ScDocument& rDoc      = pDocSh->GetDocument();
655     ScDPObject* pDPObj    = rDoc.GetDPAtCursor( GetViewData().GetCurX(),
656                                                   GetViewData().GetCurY(),
657                                                   GetViewData().GetTabNo() );
658     if ( pDPObj )
659     {
660         ScDBDocFunc aFunc( *pDocSh );
661         aFunc.RemovePivotTable(*pDPObj, true, false);
662         CursorPosChanged();     // shells may be switched
663     }
664     else
665         ErrorMessage(STR_PIVOT_NOTFOUND);
666 }
667 
RecalcPivotTable()668 void ScDBFunc::RecalcPivotTable()
669 {
670     ScDocShell* pDocSh  = GetViewData().GetDocShell();
671     ScDocument* pDoc    = GetViewData().GetDocument();
672 
673     ScDPObject* pDPObj  = pDoc->GetDPAtCursor( GetViewData().GetCurX(),
674                                                   GetViewData().GetCurY(),
675                                                   GetViewData().GetTabNo() );
676     if (pDPObj)
677     {
678         // Remove existing data cache for the data that this datapilot uses,
679         // to force re-build data cache.
680         ScDBDocFunc aFunc(*pDocSh);
681         aFunc.RefreshPivotTables(pDPObj, false);
682 
683         CursorPosChanged();     // shells may be switched
684     }
685     else
686         ErrorMessage(STR_PIVOT_NOTFOUND);
687 }
688 
GetSelectedMemberList(ScDPUniqueStringSet & rEntries,long & rDimension)689 void ScDBFunc::GetSelectedMemberList(ScDPUniqueStringSet& rEntries, long& rDimension)
690 {
691     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
692                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
693     if ( !pDPObj )
694         return;
695 
696     long nStartDimension = -1;
697     long nStartHierarchy = -1;
698     long nStartLevel     = -1;
699 
700     ScRangeListRef xRanges;
701     GetViewData().GetMultiArea( xRanges );         // incl. cursor if nothing is selected
702     size_t nRangeCount = xRanges->size();
703     bool bContinue = true;
704 
705     for (size_t nRangePos=0; nRangePos < nRangeCount && bContinue; nRangePos++)
706     {
707         ScRange const & rRange = (*xRanges)[nRangePos];
708         SCCOL nStartCol = rRange.aStart.Col();
709         SCROW nStartRow = rRange.aStart.Row();
710         SCCOL nEndCol = rRange.aEnd.Col();
711         SCROW nEndRow = rRange.aEnd.Row();
712         SCTAB nTab = rRange.aStart.Tab();
713 
714         for (SCROW nRow=nStartRow; nRow<=nEndRow && bContinue; nRow++)
715             for (SCCOL nCol=nStartCol; nCol<=nEndCol && bContinue; nCol++)
716             {
717                 sheet::DataPilotTableHeaderData aData;
718                 pDPObj->GetHeaderPositionData(ScAddress(nCol, nRow, nTab), aData);
719                 if ( aData.Dimension < 0 )
720                     bContinue = false;              // not part of any dimension
721                 else
722                 {
723                     if ( nStartDimension < 0 )      // first member?
724                     {
725                         nStartDimension = aData.Dimension;
726                         nStartHierarchy = aData.Hierarchy;
727                         nStartLevel     = aData.Level;
728                     }
729                     if ( aData.Dimension != nStartDimension ||
730                          aData.Hierarchy != nStartHierarchy ||
731                          aData.Level     != nStartLevel )
732                     {
733                         bContinue = false;          // cannot mix dimensions
734                     }
735                 }
736                 if ( bContinue )
737                 {
738                     // accept any part of a member description, also subtotals,
739                     // but don't stop if empty parts are contained
740                     if ( aData.Flags & sheet::MemberResultFlags::HASMEMBER )
741                         rEntries.insert(aData.MemberName);
742                 }
743             }
744     }
745 
746     rDimension = nStartDimension;   // dimension from which the found members came
747     if (!bContinue)
748         rEntries.clear();         // remove all if not valid
749 }
750 
HasSelectionForDateGroup(ScDPNumGroupInfo & rOldInfo,sal_Int32 & rParts)751 bool ScDBFunc::HasSelectionForDateGroup( ScDPNumGroupInfo& rOldInfo, sal_Int32& rParts )
752 {
753     // determine if the date group dialog has to be shown for the current selection
754 
755     bool bFound = false;
756 
757     SCCOL nCurX = GetViewData().GetCurX();
758     SCROW nCurY = GetViewData().GetCurY();
759     SCTAB nTab = GetViewData().GetTabNo();
760     ScDocument* pDoc = GetViewData().GetDocument();
761 
762     ScDPObject* pDPObj = pDoc->GetDPAtCursor( nCurX, nCurY, nTab );
763     if ( pDPObj )
764     {
765         ScDPUniqueStringSet aEntries;
766         long nSelectDimension = -1;
767         GetSelectedMemberList( aEntries, nSelectDimension );
768 
769         if (!aEntries.empty())
770         {
771             bool bIsDataLayout;
772             OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
773             OUString aBaseDimName( aDimName );
774 
775             bool bInGroupDim = false;
776             bool bFoundParts = false;
777 
778             ScDPDimensionSaveData* pDimData =
779                 const_cast<ScDPDimensionSaveData*>( pDPObj->GetSaveData()->GetExistingDimensionData() );
780             if ( pDimData )
781             {
782                 const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName );
783                 const ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDim( aDimName );
784                 if ( pNumGroupDim )
785                 {
786                     //  existing num group dimension
787 
788                     if ( pNumGroupDim->GetDatePart() != 0 )
789                     {
790                         //  dimension has date info -> edit settings of this dimension
791                         //  (parts are collected below)
792 
793                         rOldInfo = pNumGroupDim->GetDateInfo();
794                         bFound = true;
795                     }
796                     else if ( pNumGroupDim->GetInfo().mbDateValues )
797                     {
798                         //  Numerical grouping with DateValues flag is used for grouping
799                         //  of days with a "Number of days" value.
800 
801                         rOldInfo = pNumGroupDim->GetInfo();
802                         rParts = css::sheet::DataPilotFieldGroupBy::DAYS;               // not found in CollectDateParts
803                         bFoundParts = true;
804                         bFound = true;
805                     }
806                     bInGroupDim = true;
807                 }
808                 else if ( pGroupDim )
809                 {
810                     //  existing additional group dimension
811 
812                     if ( pGroupDim->GetDatePart() != 0 )
813                     {
814                         //  dimension has date info -> edit settings of this dimension
815                         //  (parts are collected below)
816 
817                         rOldInfo = pGroupDim->GetDateInfo();
818                         aBaseDimName = pGroupDim->GetSourceDimName();
819                         bFound = true;
820                     }
821                     bInGroupDim = true;
822                 }
823             }
824             if ( bFound && !bFoundParts )
825             {
826                 // collect date parts from all group dimensions
827                 rParts = pDimData->CollectDateParts( aBaseDimName );
828             }
829             if ( !bFound && !bInGroupDim )
830             {
831                 // create new date group dimensions if the selection is a single cell
832                 // in a normal dimension with date content
833 
834                 ScRange aSelRange;
835                 if ( (GetViewData().GetSimpleArea( aSelRange ) == SC_MARK_SIMPLE) &&
836                         aSelRange.aStart == aSelRange.aEnd )
837                 {
838                     SCCOL nSelCol = aSelRange.aStart.Col();
839                     SCROW nSelRow = aSelRange.aStart.Row();
840                     SCTAB nSelTab = aSelRange.aStart.Tab();
841                     if ( pDoc->HasValueData( nSelCol, nSelRow, nSelTab ) )
842                     {
843                         sal_uLong nIndex = pDoc->GetAttr(
844                                         nSelCol, nSelRow, nSelTab, ATTR_VALUE_FORMAT)->GetValue();
845                         SvNumFormatType nType = pDoc->GetFormatTable()->GetType(nIndex);
846                         if ( nType == SvNumFormatType::DATE || nType == SvNumFormatType::TIME || nType == SvNumFormatType::DATETIME )
847                         {
848                             bFound = true;
849                             // use currently selected value for automatic limits
850                             if( rOldInfo.mbAutoStart )
851                                 rOldInfo.mfStart = pDoc->GetValue( aSelRange.aStart );
852                             if( rOldInfo.mbAutoEnd )
853                                 rOldInfo.mfEnd = pDoc->GetValue( aSelRange.aStart );
854                         }
855                     }
856                 }
857             }
858         }
859     }
860 
861     return bFound;
862 }
863 
HasSelectionForNumGroup(ScDPNumGroupInfo & rOldInfo)864 bool ScDBFunc::HasSelectionForNumGroup( ScDPNumGroupInfo& rOldInfo )
865 {
866     // determine if the numeric group dialog has to be shown for the current selection
867 
868     bool bFound = false;
869 
870     SCCOL nCurX = GetViewData().GetCurX();
871     SCROW nCurY = GetViewData().GetCurY();
872     SCTAB nTab = GetViewData().GetTabNo();
873     ScDocument* pDoc = GetViewData().GetDocument();
874 
875     ScDPObject* pDPObj = pDoc->GetDPAtCursor( nCurX, nCurY, nTab );
876     if ( pDPObj )
877     {
878         ScDPUniqueStringSet aEntries;
879         long nSelectDimension = -1;
880         GetSelectedMemberList( aEntries, nSelectDimension );
881 
882         if (!aEntries.empty())
883         {
884             bool bIsDataLayout;
885             OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
886 
887             bool bInGroupDim = false;
888 
889             ScDPDimensionSaveData* pDimData =
890                 const_cast<ScDPDimensionSaveData*>( pDPObj->GetSaveData()->GetExistingDimensionData() );
891             if ( pDimData )
892             {
893                 const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName );
894                 if ( pNumGroupDim )
895                 {
896                     //  existing num group dimension
897                     //  -> edit settings of this dimension
898 
899                     rOldInfo = pNumGroupDim->GetInfo();
900                     bFound = true;
901                 }
902                 else if ( pDimData->GetNamedGroupDim( aDimName ) )
903                     bInGroupDim = true;                                    // in a group dimension
904             }
905             if ( !bFound && !bInGroupDim )
906             {
907                 // create a new num group dimension if the selection is a single cell
908                 // in a normal dimension with numeric content
909 
910                 ScRange aSelRange;
911                 if ( (GetViewData().GetSimpleArea( aSelRange ) == SC_MARK_SIMPLE) &&
912                         aSelRange.aStart == aSelRange.aEnd )
913                 {
914                     if ( pDoc->HasValueData( aSelRange.aStart.Col(), aSelRange.aStart.Row(),
915                                              aSelRange.aStart.Tab() ) )
916                     {
917                         bFound = true;
918                         // use currently selected value for automatic limits
919                         if( rOldInfo.mbAutoStart )
920                             rOldInfo.mfStart = pDoc->GetValue( aSelRange.aStart );
921                         if( rOldInfo.mbAutoEnd )
922                             rOldInfo.mfEnd = pDoc->GetValue( aSelRange.aStart );
923                     }
924                 }
925             }
926         }
927     }
928 
929     return bFound;
930 }
931 
DateGroupDataPilot(const ScDPNumGroupInfo & rInfo,sal_Int32 nParts)932 void ScDBFunc::DateGroupDataPilot( const ScDPNumGroupInfo& rInfo, sal_Int32 nParts )
933 {
934     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
935                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
936     if (!pDPObj)
937         return;
938 
939     ScDPUniqueStringSet aEntries;
940     long nSelectDimension = -1;
941     GetSelectedMemberList( aEntries, nSelectDimension );
942 
943     if (aEntries.empty())
944         return;
945 
946     std::vector<OUString> aDeletedNames;
947     bool bIsDataLayout;
948     OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
949 
950     ScDPSaveData aData( *pDPObj->GetSaveData() );
951     ScDPDimensionSaveData* pDimData = aData.GetDimensionData();     // created if not there
952 
953     // find the source dimension name.
954     OUString aBaseDimName = aDimName;
955     if( const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName ) )
956         aBaseDimName = pBaseGroupDim->GetSourceDimName();
957 
958     // Remove all group dimensions associated with this source dimension. For
959     // date grouping, we need to remove all existing groups for the affected
960     // source dimension and build new one(s) from scratch.  Keep the deleted
961     // names so that they can be reused during re-construction.
962     aData.RemoveAllGroupDimensions(aBaseDimName, &aDeletedNames);
963 
964     if ( nParts )
965     {
966         // create date group dimensions
967 
968         bool bFirst = true;
969         sal_Int32 nMask = 1;
970         for (sal_uInt16 nBit=0; nBit<32; nBit++)
971         {
972             if ( nParts & nMask )
973             {
974                 if ( bFirst )
975                 {
976                     // innermost part: create NumGroupDimension (replacing original values)
977                     // Dimension name is left unchanged
978 
979                     if ( (nParts == sheet::DataPilotFieldGroupBy::DAYS) && (rInfo.mfStep >= 1.0) )
980                     {
981                         // only days, and a step value specified: use numerical grouping
982                         // with DateValues flag, not date grouping
983 
984                         ScDPNumGroupInfo aNumInfo( rInfo );
985                         aNumInfo.mbDateValues = true;
986 
987                         ScDPSaveNumGroupDimension aNumGroupDim( aBaseDimName, aNumInfo );
988                         pDimData->AddNumGroupDimension( aNumGroupDim );
989                     }
990                     else
991                     {
992                         ScDPSaveNumGroupDimension aNumGroupDim( aBaseDimName, rInfo, nMask );
993                         pDimData->AddNumGroupDimension( aNumGroupDim );
994                     }
995 
996                     bFirst = false;
997                 }
998                 else
999                 {
1000                     // additional parts: create GroupDimension (shown as additional dimensions)
1001                     OUString aGroupDimName =
1002                         pDimData->CreateDateGroupDimName(nMask, *pDPObj, true, &aDeletedNames);
1003                     ScDPSaveGroupDimension aGroupDim( aBaseDimName, aGroupDimName );
1004                     aGroupDim.SetDateInfo( rInfo, nMask );
1005                     pDimData->AddGroupDimension( aGroupDim );
1006 
1007                     // set orientation
1008                     ScDPSaveDimension* pSaveDimension = aData.GetDimensionByName( aGroupDimName );
1009                     if ( pSaveDimension->GetOrientation() == sheet::DataPilotFieldOrientation_HIDDEN )
1010                     {
1011                         ScDPSaveDimension* pOldDimension = aData.GetDimensionByName( aBaseDimName );
1012                         pSaveDimension->SetOrientation( pOldDimension->GetOrientation() );
1013                         aData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base
1014                     }
1015                 }
1016             }
1017             nMask *= 2;
1018         }
1019     }
1020 
1021     // apply changes
1022     ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1023     pDPObj->SetSaveData( aData );
1024     aFunc.RefreshPivotTableGroups(pDPObj);
1025 
1026     // unmark cell selection
1027     Unmark();
1028 }
1029 
NumGroupDataPilot(const ScDPNumGroupInfo & rInfo)1030 void ScDBFunc::NumGroupDataPilot( const ScDPNumGroupInfo& rInfo )
1031 {
1032     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
1033                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
1034     if (!pDPObj)
1035         return;
1036 
1037     ScDPUniqueStringSet aEntries;
1038     long nSelectDimension = -1;
1039     GetSelectedMemberList( aEntries, nSelectDimension );
1040 
1041     if (aEntries.empty())
1042         return;
1043 
1044     bool bIsDataLayout;
1045     OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
1046 
1047     ScDPSaveData aData( *pDPObj->GetSaveData() );
1048     ScDPDimensionSaveData* pDimData = aData.GetDimensionData();     // created if not there
1049 
1050     ScDPSaveNumGroupDimension* pExisting = pDimData->GetNumGroupDimAcc( aDimName );
1051     if ( pExisting )
1052     {
1053         // modify existing group dimension
1054         pExisting->SetGroupInfo( rInfo );
1055     }
1056     else
1057     {
1058         // create new group dimension
1059         ScDPSaveNumGroupDimension aNumGroupDim( aDimName, rInfo );
1060         pDimData->AddNumGroupDimension( aNumGroupDim );
1061     }
1062 
1063     // apply changes
1064     ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1065     pDPObj->SetSaveData( aData );
1066     aFunc.RefreshPivotTableGroups(pDPObj);
1067 
1068     // unmark cell selection
1069     Unmark();
1070 }
1071 
GroupDataPilot()1072 void ScDBFunc::GroupDataPilot()
1073 {
1074     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
1075                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
1076     if (!pDPObj)
1077         return;
1078 
1079     ScDPUniqueStringSet aEntries;
1080     long nSelectDimension = -1;
1081     GetSelectedMemberList( aEntries, nSelectDimension );
1082 
1083     if (aEntries.empty())
1084         return;
1085 
1086     bool bIsDataLayout;
1087     OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
1088 
1089     ScDPSaveData aData( *pDPObj->GetSaveData() );
1090     ScDPDimensionSaveData* pDimData = aData.GetDimensionData();     // created if not there
1091 
1092     // find original base
1093     OUString aBaseDimName = aDimName;
1094     const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName );
1095     if ( pBaseGroupDim )
1096     {
1097         // any entry's SourceDimName is the original base
1098         aBaseDimName = pBaseGroupDim->GetSourceDimName();
1099     }
1100 
1101     // find existing group dimension
1102     // (using the selected dim, can be intermediate group dim)
1103     ScDPSaveGroupDimension* pGroupDimension = pDimData->GetGroupDimAccForBase( aDimName );
1104 
1105     // remove the selected items from their groups
1106     // (empty groups are removed, too)
1107     if ( pGroupDimension )
1108     {
1109         for (const OUString& aEntryName : aEntries)
1110         {
1111             if ( pBaseGroupDim )
1112             {
1113                 // for each selected (intermediate) group, remove all its items
1114                 // (same logic as for adding, below)
1115                 const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName );
1116                 if ( pBaseGroup )
1117                     pBaseGroup->RemoveElementsFromGroups( *pGroupDimension );   // remove all elements
1118                 else
1119                     pGroupDimension->RemoveFromGroups( aEntryName );
1120             }
1121             else
1122                 pGroupDimension->RemoveFromGroups( aEntryName );
1123         }
1124     }
1125 
1126     std::unique_ptr<ScDPSaveGroupDimension> pNewGroupDim;
1127     if ( !pGroupDimension )
1128     {
1129         // create a new group dimension
1130         OUString aGroupDimName =
1131             pDimData->CreateGroupDimName(aBaseDimName, *pDPObj, false, nullptr);
1132         pNewGroupDim.reset(new ScDPSaveGroupDimension( aBaseDimName, aGroupDimName ));
1133 
1134         pGroupDimension = pNewGroupDim.get();     // make changes to the new dim if none existed
1135 
1136         if ( pBaseGroupDim )
1137         {
1138             // If it's a higher-order group dimension, pre-allocate groups for all
1139             // non-selected original groups, so the individual base members aren't
1140             // used for automatic groups (this would make the original groups hard
1141             // to find).
1142             //! Also do this when removing groups?
1143             //! Handle this case dynamically with automatic groups?
1144 
1145             long nGroupCount = pBaseGroupDim->GetGroupCount();
1146             for ( long nGroup = 0; nGroup < nGroupCount; nGroup++ )
1147             {
1148                 const ScDPSaveGroupItem& rBaseGroup = pBaseGroupDim->GetGroupByIndex( nGroup );
1149 
1150                 if (!aEntries.count(rBaseGroup.GetGroupName()))
1151                 {
1152                     // add an additional group for each item that is not in the selection
1153                     ScDPSaveGroupItem aGroup( rBaseGroup.GetGroupName() );
1154                     aGroup.AddElementsFromGroup( rBaseGroup );
1155                     pGroupDimension->AddGroupItem( aGroup );
1156                 }
1157             }
1158         }
1159     }
1160     OUString aGroupDimName = pGroupDimension->GetGroupDimName();
1161 
1162     OUString aGroupName = pGroupDimension->CreateGroupName(ScResId(STR_PIVOT_GROUP));
1163     ScDPSaveGroupItem aGroup( aGroupName );
1164     for (const OUString& aEntryName : aEntries)
1165     {
1166         if ( pBaseGroupDim )
1167         {
1168             // for each selected (intermediate) group, add all its items
1169             const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName );
1170             if ( pBaseGroup )
1171                 aGroup.AddElementsFromGroup( *pBaseGroup );
1172             else
1173                 aGroup.AddElement( aEntryName );    // no group found -> automatic group, add the item itself
1174         }
1175         else
1176             aGroup.AddElement( aEntryName );        // no group dimension, add all items directly
1177     }
1178 
1179     pGroupDimension->AddGroupItem( aGroup );
1180 
1181     if ( pNewGroupDim )
1182     {
1183         pDimData->AddGroupDimension( *pNewGroupDim );
1184         pNewGroupDim.reset();        // AddGroupDimension copies the object
1185         // don't access pGroupDimension after here
1186     }
1187     pGroupDimension = nullptr;
1188 
1189     // set orientation
1190     ScDPSaveDimension* pSaveDimension = aData.GetDimensionByName( aGroupDimName );
1191     if ( pSaveDimension->GetOrientation() == sheet::DataPilotFieldOrientation_HIDDEN )
1192     {
1193         ScDPSaveDimension* pOldDimension = aData.GetDimensionByName( aDimName );
1194         pSaveDimension->SetOrientation( pOldDimension->GetOrientation() );
1195         aData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base
1196     }
1197 
1198     // apply changes
1199     ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1200     pDPObj->SetSaveData( aData );
1201     aFunc.RefreshPivotTableGroups(pDPObj);
1202 
1203     // unmark cell selection
1204     Unmark();
1205 }
1206 
UngroupDataPilot()1207 void ScDBFunc::UngroupDataPilot()
1208 {
1209     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
1210                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
1211     if (!pDPObj)
1212         return;
1213 
1214     ScDPUniqueStringSet aEntries;
1215     long nSelectDimension = -1;
1216     GetSelectedMemberList( aEntries, nSelectDimension );
1217 
1218     if (aEntries.empty())
1219         return;
1220 
1221     bool bIsDataLayout;
1222     OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
1223 
1224     ScDPSaveData aData( *pDPObj->GetSaveData() );
1225     if (!aData.GetExistingDimensionData())
1226         // There is nothing to ungroup.
1227         return;
1228 
1229     ScDPDimensionSaveData* pDimData = aData.GetDimensionData();
1230 
1231     ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName );
1232     const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName );
1233     if ( ( pGroupDim && pGroupDim->GetDatePart() != 0 ) ||
1234          ( pNumGroupDim && pNumGroupDim->GetDatePart() != 0 ) )
1235     {
1236         // Date grouping: need to remove all affected group dimensions.
1237         // This is done using DateGroupDataPilot with nParts=0.
1238 
1239         DateGroupDataPilot( ScDPNumGroupInfo(), 0 );
1240         return;
1241     }
1242 
1243     if ( pGroupDim )
1244     {
1245         for (const auto& rEntry : aEntries)
1246             pGroupDim->RemoveGroup(rEntry);
1247 
1248         // remove group dimension if empty
1249         bool bEmptyDim = pGroupDim->IsEmpty();
1250         if ( !bEmptyDim )
1251         {
1252             // If all remaining groups in the dimension aren't shown, remove
1253             // the dimension too, as if it was completely empty.
1254             ScDPUniqueStringSet aVisibleEntries;
1255             pDPObj->GetMemberResultNames( aVisibleEntries, nSelectDimension );
1256             bEmptyDim = pGroupDim->HasOnlyHidden( aVisibleEntries );
1257         }
1258         if ( bEmptyDim )
1259         {
1260             pDimData->RemoveGroupDimension( aDimName );     // pGroupDim is deleted
1261 
1262             // also remove SaveData settings for the dimension that no longer exists
1263             aData.RemoveDimensionByName( aDimName );
1264         }
1265     }
1266     else if ( pNumGroupDim )
1267     {
1268         // remove the numerical grouping
1269         pDimData->RemoveNumGroupDimension( aDimName );
1270         // SaveData settings can remain unchanged - the same dimension still exists
1271     }
1272 
1273     // apply changes
1274     ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1275     pDPObj->SetSaveData( aData );
1276     aFunc.RefreshPivotTableGroups(pDPObj);
1277 
1278     // unmark cell selection
1279     Unmark();
1280 }
1281 
lcl_replaceMemberNameInSubtotal(const OUString & rSubtotal,const OUString & rMemberName)1282 static OUString lcl_replaceMemberNameInSubtotal(const OUString& rSubtotal, const OUString& rMemberName)
1283 {
1284     sal_Int32 n = rSubtotal.getLength();
1285     const sal_Unicode* p = rSubtotal.getStr();
1286     OUStringBuffer aBuf, aWordBuf;
1287     for (sal_Int32 i = 0; i < n; ++i)
1288     {
1289         sal_Unicode c = p[i];
1290         if (c == ' ')
1291         {
1292             OUString aWord = aWordBuf.makeStringAndClear();
1293             if (aWord == rMemberName)
1294                 aBuf.append('?');
1295             else
1296                 aBuf.append(aWord);
1297             aBuf.append(c);
1298         }
1299         else if (c == '\\')
1300         {
1301             // Escape a backslash character.
1302             aWordBuf.append(c);
1303             aWordBuf.append(c);
1304         }
1305         else if (c == '?')
1306         {
1307             // A literal '?' must be escaped with a backslash ('\');
1308             aWordBuf.append('\\');
1309             aWordBuf.append(c);
1310         }
1311         else
1312             aWordBuf.append(c);
1313     }
1314 
1315     if (!aWordBuf.isEmpty())
1316     {
1317         OUString aWord = aWordBuf.makeStringAndClear();
1318         if (aWord == rMemberName)
1319             aBuf.append('?');
1320         else
1321             aBuf.append(aWord);
1322     }
1323 
1324     return aBuf.makeStringAndClear();
1325 }
1326 
DataPilotInput(const ScAddress & rPos,const OUString & rString)1327 void ScDBFunc::DataPilotInput( const ScAddress& rPos, const OUString& rString )
1328 {
1329     using namespace ::com::sun::star::sheet;
1330 
1331     ScDocument* pDoc = GetViewData().GetDocument();
1332     ScDPObject* pDPObj = pDoc->GetDPAtCursor( rPos.Col(), rPos.Row(), rPos.Tab() );
1333     if (!pDPObj)
1334         return;
1335 
1336     OUString aOldText = pDoc->GetString(rPos.Col(), rPos.Row(), rPos.Tab());
1337 
1338     if ( aOldText == rString )
1339     {
1340         // nothing to do: silently exit
1341         return;
1342     }
1343 
1344     const char* pErrorId = nullptr;
1345 
1346     pDPObj->BuildAllDimensionMembers();
1347     ScDPSaveData aData( *pDPObj->GetSaveData() );
1348     bool bChange = false;
1349     bool bNeedReloadGroups = false;
1350 
1351     DataPilotFieldOrientation nOrient = DataPilotFieldOrientation_HIDDEN;
1352     long nField = pDPObj->GetHeaderDim( rPos, nOrient );
1353     if ( nField >= 0 )
1354     {
1355         // changing a field title
1356         if ( aData.GetExistingDimensionData() )
1357         {
1358             // only group dimensions can be renamed
1359 
1360             ScDPDimensionSaveData* pDimData = aData.GetDimensionData();
1361             ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aOldText );
1362             if ( pGroupDim )
1363             {
1364                 // valid name: not empty, no existing dimension (group or other)
1365                 if (!rString.isEmpty() && !pDPObj->IsDimNameInUse(rString))
1366                 {
1367                     pGroupDim->Rename( rString );
1368 
1369                     // also rename in SaveData to preserve the field settings
1370                     ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aOldText );
1371                     pSaveDim->SetName( rString );
1372 
1373                     bChange = true;
1374                 }
1375                 else
1376                     pErrorId = STR_INVALIDNAME;
1377             }
1378         }
1379         else if (nOrient == DataPilotFieldOrientation_COLUMN || nOrient == DataPilotFieldOrientation_ROW)
1380         {
1381             bool bDataLayout = false;
1382             OUString aDimName = pDPObj->GetDimName(nField, bDataLayout);
1383             ScDPSaveDimension* pDim = bDataLayout ? aData.GetDataLayoutDimension() : aData.GetDimensionByName(aDimName);
1384             if (pDim)
1385             {
1386                 if (!rString.isEmpty())
1387                 {
1388                     if (rString.equalsIgnoreAsciiCase(aDimName))
1389                     {
1390                         pDim->RemoveLayoutName();
1391                         bChange = true;
1392                     }
1393                     else if (!pDPObj->IsDimNameInUse(rString))
1394                     {
1395                         pDim->SetLayoutName(rString);
1396                         bChange = true;
1397                     }
1398                     else
1399                         pErrorId = STR_INVALIDNAME;
1400                 }
1401                 else
1402                     pErrorId = STR_INVALIDNAME;
1403             }
1404         }
1405     }
1406     else if (pDPObj->IsDataDescriptionCell(rPos))
1407     {
1408         // There is only one data dimension.
1409         ScDPSaveDimension* pDim = aData.GetFirstDimension(sheet::DataPilotFieldOrientation_DATA);
1410         if (pDim)
1411         {
1412             if (!rString.isEmpty())
1413             {
1414                 if (pDim->GetName().equalsIgnoreAsciiCase(rString))
1415                 {
1416                     pDim->RemoveLayoutName();
1417                     bChange = true;
1418                 }
1419                 else if (!pDPObj->IsDimNameInUse(rString))
1420                 {
1421                     pDim->SetLayoutName(rString);
1422                     bChange = true;
1423                 }
1424                 else
1425                     pErrorId = STR_INVALIDNAME;
1426             }
1427             else
1428                 pErrorId = STR_INVALIDNAME;
1429         }
1430     }
1431     else
1432     {
1433         // This is not a field header.
1434         sheet::DataPilotTableHeaderData aPosData;
1435         pDPObj->GetHeaderPositionData(rPos, aPosData);
1436 
1437         if ((aPosData.Flags & MemberResultFlags::HASMEMBER) && !aOldText.isEmpty())
1438         {
1439             if ( aData.GetExistingDimensionData() && !(aPosData.Flags & MemberResultFlags::SUBTOTAL))
1440             {
1441                 bool bIsDataLayout;
1442                 OUString aDimName = pDPObj->GetDimName( aPosData.Dimension, bIsDataLayout );
1443 
1444                 ScDPDimensionSaveData* pDimData = aData.GetDimensionData();
1445                 ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName );
1446                 if ( pGroupDim )
1447                 {
1448                     // valid name: not empty, no existing group in this dimension
1449                     //! ignore case?
1450                     if (!rString.isEmpty() && !pGroupDim->GetNamedGroup(rString))
1451                     {
1452                         ScDPSaveGroupItem* pGroup = pGroupDim->GetNamedGroupAcc( aOldText );
1453                         if ( pGroup )
1454                             pGroup->Rename( rString );     // rename the existing group
1455                         else
1456                         {
1457                             // create a new group to replace the automatic group
1458                             ScDPSaveGroupItem aGroup( rString );
1459                             aGroup.AddElement( aOldText );
1460                             pGroupDim->AddGroupItem( aGroup );
1461                         }
1462 
1463                         // in both cases also adjust savedata, to preserve member settings (show details)
1464                         ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aDimName );
1465                         ScDPSaveMember* pSaveMember = pSaveDim->GetExistingMemberByName( aOldText );
1466                         if ( pSaveMember )
1467                             pSaveMember->SetName( rString );
1468 
1469                         bChange = true;
1470                         bNeedReloadGroups = true;
1471                     }
1472                     else
1473                         pErrorId = STR_INVALIDNAME;
1474                  }
1475             }
1476             else if (aPosData.Flags & MemberResultFlags::GRANDTOTAL)
1477             {
1478                 aData.SetGrandTotalName(rString);
1479                 bChange = true;
1480             }
1481             else if (aPosData.Dimension >= 0 && !aPosData.MemberName.isEmpty())
1482             {
1483                 bool bDataLayout = false;
1484                 OUString aDimName = pDPObj->GetDimName(static_cast<long>(aPosData.Dimension), bDataLayout);
1485                 if (bDataLayout)
1486                 {
1487                     // data dimension
1488                     do
1489                     {
1490                         if (aPosData.Flags & MemberResultFlags::SUBTOTAL)
1491                             break;
1492 
1493                         ScDPSaveDimension* pDim = aData.GetDimensionByName(aPosData.MemberName);
1494                         if (!pDim)
1495                             break;
1496 
1497                         if (rString.isEmpty())
1498                         {
1499                             pErrorId = STR_INVALIDNAME;
1500                             break;
1501                         }
1502 
1503                         if (aPosData.MemberName.equalsIgnoreAsciiCase(rString))
1504                         {
1505                             pDim->RemoveLayoutName();
1506                             bChange = true;
1507                         }
1508                         else if (!pDPObj->IsDimNameInUse(rString))
1509                         {
1510                             pDim->SetLayoutName(rString);
1511                             bChange = true;
1512                         }
1513                         else
1514                             pErrorId = STR_INVALIDNAME;
1515                     }
1516                     while (false);
1517                 }
1518                 else
1519                 {
1520                     // field member
1521                     do
1522                     {
1523                         ScDPSaveDimension* pDim = aData.GetDimensionByName(aDimName);
1524                         if (!pDim)
1525                             break;
1526 
1527                         ScDPSaveMember* pMem = pDim->GetExistingMemberByName(aPosData.MemberName);
1528                         if (!pMem)
1529                             break;
1530 
1531                         if (aPosData.Flags & MemberResultFlags::SUBTOTAL)
1532                         {
1533                             // Change subtotal only when the table has one data dimension.
1534                             if (aData.GetDataDimensionCount() > 1)
1535                                 break;
1536 
1537                             // display name for subtotal is allowed only if the subtotal type is 'Automatic'.
1538                             if (pDim->GetSubTotalsCount() != 1)
1539                                 break;
1540 
1541                             if (pDim->GetSubTotalFunc(0) != ScGeneralFunction::AUTO)
1542                                 break;
1543 
1544                             const boost::optional<OUString> & pLayoutName = pMem->GetLayoutName();
1545                             OUString aMemberName;
1546                             if (pLayoutName)
1547                                 aMemberName = *pLayoutName;
1548                             else
1549                                 aMemberName = aPosData.MemberName;
1550 
1551                             OUString aNew = lcl_replaceMemberNameInSubtotal(rString, aMemberName);
1552                             pDim->SetSubtotalName(aNew);
1553                             bChange = true;
1554                         }
1555                         else
1556                         {
1557                             // Check to make sure the member name isn't
1558                             // already used.
1559                             if (!rString.isEmpty())
1560                             {
1561                                 if (rString.equalsIgnoreAsciiCase(pMem->GetName()))
1562                                 {
1563                                     pMem->RemoveLayoutName();
1564                                     bChange = true;
1565                                 }
1566                                 else if (!pDim->IsMemberNameInUse(rString))
1567                                 {
1568                                     pMem->SetLayoutName(rString);
1569                                     bChange = true;
1570                                 }
1571                                 else
1572                                     pErrorId = STR_INVALIDNAME;
1573                             }
1574                             else
1575                                 pErrorId = STR_INVALIDNAME;
1576                         }
1577                     }
1578                     while (false);
1579                 }
1580             }
1581         }
1582     }
1583 
1584     if ( bChange )
1585     {
1586         // apply changes
1587         ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1588         pDPObj->SetSaveData( aData );
1589         if (bNeedReloadGroups)
1590         {
1591             ScDPCollection* pDPs = pDoc->GetDPCollection();
1592             if (pDPs)
1593             {
1594                 std::set<ScDPObject*> aRefs;
1595                 // tdf#111305: Reload groups in cache after modifications.
1596                 pDPs->ReloadGroupsInCache(pDPObj, aRefs);
1597             } // pDPs
1598         } // bNeedReloadGroups
1599         aFunc.UpdatePivotTable(*pDPObj, true, false);
1600     }
1601     else
1602     {
1603         if (!pErrorId)
1604             pErrorId = STR_ERR_DATAPILOT_INPUT;
1605         ErrorMessage(pErrorId);
1606     }
1607 }
1608 
lcl_MoveToEnd(ScDPSaveDimension & rDim,const OUString & rItemName)1609 static void lcl_MoveToEnd( ScDPSaveDimension& rDim, const OUString& rItemName )
1610 {
1611     std::unique_ptr<ScDPSaveMember> pNewMember;
1612     const ScDPSaveMember* pOldMember = rDim.GetExistingMemberByName( rItemName );
1613     if ( pOldMember )
1614         pNewMember.reset(new ScDPSaveMember( *pOldMember ));
1615     else
1616         pNewMember.reset(new ScDPSaveMember( rItemName ));
1617     rDim.AddMember( std::move(pNewMember) );
1618     // AddMember takes ownership of the new pointer,
1619     // puts it to the end of the list even if it was in the list before.
1620 }
1621 
1622 struct ScOUStringCollate
1623 {
1624     CollatorWrapper* const mpCollator;
1625 
ScOUStringCollateScOUStringCollate1626     explicit ScOUStringCollate(CollatorWrapper* pColl) : mpCollator(pColl) {}
1627 
operator ()ScOUStringCollate1628     bool operator()(const OUString& rStr1, const OUString& rStr2) const
1629     {
1630         return ( mpCollator->compareString(rStr1, rStr2) < 0 );
1631     }
1632 };
1633 
DataPilotSort(ScDPObject * pDPObj,long nDimIndex,bool bAscending,const sal_uInt16 * pUserListId)1634 void ScDBFunc::DataPilotSort(ScDPObject* pDPObj, long nDimIndex, bool bAscending, const sal_uInt16* pUserListId)
1635 {
1636     if (!pDPObj)
1637         return;
1638 
1639     // We need to run this to get all members later.
1640     if ( pUserListId )
1641         pDPObj->BuildAllDimensionMembers();
1642 
1643     if (nDimIndex < 0)
1644         // Invalid dimension index.  Bail out.
1645         return;
1646 
1647     ScDPSaveData* pSaveData = pDPObj->GetSaveData();
1648     if (!pSaveData)
1649         return;
1650 
1651     ScDPSaveData aNewSaveData(*pSaveData);
1652     bool bDataLayout;
1653     OUString aDimName = pDPObj->GetDimName(nDimIndex, bDataLayout);
1654     ScDPSaveDimension* pSaveDim = aNewSaveData.GetDimensionByName(aDimName);
1655     if (!pSaveDim)
1656         return;
1657 
1658     // manual evaluation of sort order is only needed if a user list id is given
1659     if ( pUserListId )
1660     {
1661         typedef ScDPSaveDimension::MemberList MemList;
1662         const MemList& rDimMembers = pSaveDim->GetMembers();
1663         vector<OUString> aMembers;
1664         std::unordered_set<OUString> aMemberSet;
1665         size_t nMemberCount = 0;
1666         for (ScDPSaveMember* pMem : rDimMembers)
1667         {
1668             aMembers.push_back(pMem->GetName());
1669             aMemberSet.insert(pMem->GetName());
1670             ++nMemberCount;
1671         }
1672 
1673         // Sort the member list in ascending order.
1674         ScOUStringCollate aCollate( ScGlobal::GetCollator() );
1675         std::stable_sort(aMembers.begin(), aMembers.end(), aCollate);
1676 
1677         // Collect and rank those custom sort strings that also exist in the member name list.
1678 
1679         typedef std::unordered_map<OUString, sal_uInt16> UserSortMap;
1680         UserSortMap aSubStrs;
1681         sal_uInt16 nSubCount = 0;
1682         ScUserList* pUserList = ScGlobal::GetUserList();
1683         if (!pUserList)
1684             return;
1685 
1686         {
1687             size_t n = pUserList->size();
1688             if (!n || *pUserListId >= static_cast<sal_uInt16>(n))
1689                 return;
1690         }
1691 
1692         const ScUserListData& rData = (*pUserList)[*pUserListId];
1693         sal_uInt16 n = rData.GetSubCount();
1694         for (sal_uInt16 i = 0; i < n; ++i)
1695         {
1696             OUString aSub = rData.GetSubStr(i);
1697             if (!aMemberSet.count(aSub))
1698                 // This string doesn't exist in the member name set.  Don't add this.
1699                 continue;
1700 
1701             aSubStrs.emplace(aSub, nSubCount++);
1702         }
1703 
1704         // Rank all members.
1705 
1706         vector<OUString> aRankedNames(nMemberCount);
1707         sal_uInt16 nCurStrId = 0;
1708         for (auto const& aMemberName : aMembers)
1709         {
1710             sal_uInt16 nRank = 0;
1711             UserSortMap::const_iterator itrSub = aSubStrs.find(aMemberName);
1712             if (itrSub == aSubStrs.end())
1713                 nRank = nSubCount + nCurStrId++;
1714             else
1715                 nRank = itrSub->second;
1716 
1717             if (!bAscending)
1718                 nRank = static_cast< sal_uInt16 >( nMemberCount - nRank - 1 );
1719 
1720             aRankedNames[nRank] = aMemberName;
1721         }
1722 
1723         // Re-order ScDPSaveMember instances with the new ranks.
1724         for (auto const& aRankedName : aRankedNames)
1725         {
1726             const ScDPSaveMember* pOldMem = pSaveDim->GetExistingMemberByName(aRankedName);
1727             if (!pOldMem)
1728                 // All members are supposed to be present.
1729                 continue;
1730 
1731             pSaveDim->AddMember(std::unique_ptr<ScDPSaveMember>(new ScDPSaveMember(*pOldMem)));
1732         }
1733 
1734         // Set the sorting mode to manual for now.  We may introduce a new sorting
1735         // mode later on.
1736 
1737         sheet::DataPilotFieldSortInfo aSortInfo;
1738         aSortInfo.Mode = sheet::DataPilotFieldSortMode::MANUAL;
1739         pSaveDim->SetSortInfo(&aSortInfo);
1740     }
1741     else
1742     {
1743         // without user list id, just apply sorting mode
1744 
1745         sheet::DataPilotFieldSortInfo aSortInfo;
1746         aSortInfo.Mode = sheet::DataPilotFieldSortMode::NAME;
1747         aSortInfo.IsAscending = bAscending;
1748         pSaveDim->SetSortInfo(&aSortInfo);
1749     }
1750 
1751     // Update the datapilot with the newly sorted field members.
1752 
1753     std::unique_ptr<ScDPObject> pNewObj(new ScDPObject(*pDPObj));
1754     pNewObj->SetSaveData(aNewSaveData);
1755     ScDBDocFunc aFunc(*GetViewData().GetDocShell());
1756 
1757     aFunc.DataPilotUpdate(pDPObj, pNewObj.get(), true, false);
1758 }
1759 
DataPilotMove(const ScRange & rSource,const ScAddress & rDest)1760 bool ScDBFunc::DataPilotMove( const ScRange& rSource, const ScAddress& rDest )
1761 {
1762     bool bRet = false;
1763     ScDocument* pDoc = GetViewData().GetDocument();
1764     ScDPObject* pDPObj = pDoc->GetDPAtCursor( rSource.aStart.Col(), rSource.aStart.Row(), rSource.aStart.Tab() );
1765     if ( pDPObj && pDPObj == pDoc->GetDPAtCursor( rDest.Col(), rDest.Row(), rDest.Tab() ) )
1766     {
1767         sheet::DataPilotTableHeaderData aDestData;
1768         pDPObj->GetHeaderPositionData( rDest, aDestData );
1769         bool bValid = ( aDestData.Dimension >= 0 );        // dropping onto a field
1770 
1771         // look through the source range
1772         std::unordered_set< OUString > aMembersSet;   // for lookup
1773         std::vector< OUString > aMembersVector;  // members in original order, for inserting
1774         aMembersVector.reserve( std::max( static_cast<SCSIZE>( rSource.aEnd.Col() - rSource.aStart.Col() + 1 ),
1775                                           static_cast<SCSIZE>( rSource.aEnd.Row() - rSource.aStart.Row() + 1 ) ) );
1776         for (SCROW nRow = rSource.aStart.Row(); bValid && nRow <= rSource.aEnd.Row(); ++nRow )
1777             for (SCCOL nCol = rSource.aStart.Col(); bValid && nCol <= rSource.aEnd.Col(); ++nCol )
1778             {
1779                 sheet::DataPilotTableHeaderData aSourceData;
1780                 pDPObj->GetHeaderPositionData( ScAddress( nCol, nRow, rSource.aStart.Tab() ), aSourceData );
1781                 if ( aSourceData.Dimension == aDestData.Dimension && !aSourceData.MemberName.isEmpty() )
1782                 {
1783                     if ( aMembersSet.insert( aSourceData.MemberName ).second )
1784                     {
1785                         aMembersVector.push_back( aSourceData.MemberName );
1786                     }
1787                     // duplicates are ignored
1788                 }
1789                 else
1790                     bValid = false;     // empty (subtotal) or different field
1791             }
1792 
1793         if ( bValid )
1794         {
1795             bool bIsDataLayout;
1796             OUString aDimName = pDPObj->GetDimName( aDestData.Dimension, bIsDataLayout );
1797             if ( !bIsDataLayout )
1798             {
1799                 ScDPSaveData aData( *pDPObj->GetSaveData() );
1800                 ScDPSaveDimension* pDim = aData.GetDimensionByName( aDimName );
1801 
1802                 // get all member names in source order
1803                 uno::Sequence<OUString> aMemberNames;
1804                 pDPObj->GetMemberNames( aDestData.Dimension, aMemberNames );
1805 
1806                 bool bInserted = false;
1807 
1808                 for (const OUString& aMemberStr : std::as_const(aMemberNames))
1809                 {
1810                     if ( !bInserted && aMemberStr == aDestData.MemberName )
1811                     {
1812                         // insert dragged items before this item
1813                         for ( const auto& rMember : aMembersVector )
1814                             lcl_MoveToEnd( *pDim, rMember );
1815                         bInserted = true;
1816                     }
1817 
1818                     if ( aMembersSet.find( aMemberStr ) == aMembersSet.end() )  // skip dragged items
1819                         lcl_MoveToEnd( *pDim, aMemberStr );
1820                 }
1821                 // insert dragged item at end if dest wasn't found (for example, empty)
1822                 if ( !bInserted )
1823                     for ( const auto& rMember : aMembersVector )
1824                         lcl_MoveToEnd( *pDim, rMember );
1825 
1826                 // Items that were in SaveData, but not in the source, end up at the start of the list.
1827 
1828                 // set flag for manual sorting
1829                 sheet::DataPilotFieldSortInfo aSortInfo;
1830                 aSortInfo.Mode = sheet::DataPilotFieldSortMode::MANUAL;
1831                 pDim->SetSortInfo( &aSortInfo );
1832 
1833                 // apply changes
1834                 ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1835                 std::unique_ptr<ScDPObject> pNewObj(new ScDPObject( *pDPObj ));
1836                 pNewObj->SetSaveData( aData );
1837                 aFunc.DataPilotUpdate( pDPObj, pNewObj.get(), true, false );      //! bApi for drag&drop?
1838                 pNewObj.reset();
1839 
1840                 Unmark();       // entry was moved - no use in leaving the old cell selected
1841 
1842                 bRet = true;
1843             }
1844         }
1845     }
1846 
1847     return bRet;
1848 }
1849 
HasSelectionForDrillDown(css::sheet::DataPilotFieldOrientation & rOrientation)1850 bool ScDBFunc::HasSelectionForDrillDown( css::sheet::DataPilotFieldOrientation& rOrientation )
1851 {
1852     bool bRet = false;
1853 
1854     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
1855                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
1856     if ( pDPObj )
1857     {
1858         ScDPUniqueStringSet aEntries;
1859         long nSelectDimension = -1;
1860         GetSelectedMemberList( aEntries, nSelectDimension );
1861 
1862         if (!aEntries.empty())
1863         {
1864             bool bIsDataLayout;
1865             OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
1866             if ( !bIsDataLayout )
1867             {
1868                 ScDPSaveData* pSaveData = pDPObj->GetSaveData();
1869                 ScDPSaveDimension* pDim = pSaveData->GetExistingDimensionByName( aDimName );
1870                 if ( pDim )
1871                 {
1872                     css::sheet::DataPilotFieldOrientation nDimOrient = pDim->GetOrientation();
1873                     ScDPSaveDimension* pInner = pSaveData->GetInnermostDimension( nDimOrient );
1874                     if ( pDim == pInner )
1875                     {
1876                         rOrientation = nDimOrient;
1877                         bRet = true;
1878                     }
1879                 }
1880             }
1881         }
1882     }
1883 
1884     return bRet;
1885 }
1886 
SetDataPilotDetails(bool bShow,const OUString * pNewDimensionName)1887 void ScDBFunc::SetDataPilotDetails(bool bShow, const OUString* pNewDimensionName)
1888 {
1889     ScDPObject* pDPObj = GetViewData().GetDocument()->GetDPAtCursor( GetViewData().GetCurX(),
1890                                         GetViewData().GetCurY(), GetViewData().GetTabNo() );
1891     if ( pDPObj )
1892     {
1893         ScDPUniqueStringSet aEntries;
1894         long nSelectDimension = -1;
1895         GetSelectedMemberList( aEntries, nSelectDimension );
1896 
1897         if (!aEntries.empty())
1898         {
1899             bool bIsDataLayout;
1900             OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout );
1901             if ( !bIsDataLayout )
1902             {
1903                 ScDPSaveData aData( *pDPObj->GetSaveData() );
1904                 ScDPSaveDimension* pDim = aData.GetDimensionByName( aDimName );
1905 
1906                 if ( bShow && pNewDimensionName )
1907                 {
1908                     //  add the new dimension with the same orientation, at the end
1909 
1910                     ScDPSaveDimension* pNewDim = aData.GetDimensionByName( *pNewDimensionName );
1911                     ScDPSaveDimension* pDuplicated = nullptr;
1912                     if ( pNewDim->GetOrientation() == sheet::DataPilotFieldOrientation_DATA )
1913                     {
1914                         // Need to duplicate the dimension, create column/row in addition to data:
1915                         // The duplicated dimension inherits the existing settings, pNewDim is modified below.
1916                         pDuplicated = aData.DuplicateDimension( *pNewDimensionName );
1917                     }
1918 
1919                     css::sheet::DataPilotFieldOrientation nOrientation = pDim->GetOrientation();
1920                     pNewDim->SetOrientation( nOrientation );
1921 
1922                     long nPosition = LONG_MAX;
1923                     aData.SetPosition( pNewDim, nPosition );
1924 
1925                     ScDPSaveDimension* pDataLayout = aData.GetDataLayoutDimension();
1926                     if ( pDataLayout->GetOrientation() == nOrientation &&
1927                          aData.GetDataDimensionCount() <= 1 )
1928                     {
1929                         // If there is only one data dimension, the data layout dimension
1930                         // must still be the last one in its orientation.
1931                         aData.SetPosition( pDataLayout, nPosition );
1932                     }
1933 
1934                     if ( pDuplicated )
1935                     {
1936                         // The duplicated (data) dimension needs to be behind the original dimension
1937                         aData.SetPosition( pDuplicated, nPosition );
1938                     }
1939 
1940                     //  Hide details for all visible members (selected are changed below).
1941                     //! Use all members from source level instead (including non-visible)?
1942 
1943                     ScDPUniqueStringSet aVisibleEntries;
1944                     pDPObj->GetMemberResultNames( aVisibleEntries, nSelectDimension );
1945 
1946                     for (const OUString& aVisName : aVisibleEntries)
1947                     {
1948                         ScDPSaveMember* pMember = pDim->GetMemberByName( aVisName );
1949                         pMember->SetShowDetails( false );
1950                     }
1951                 }
1952 
1953                 for (const auto& rEntry : aEntries)
1954                 {
1955                     ScDPSaveMember* pMember = pDim->GetMemberByName(rEntry);
1956                     pMember->SetShowDetails( bShow );
1957                 }
1958 
1959                 // apply changes
1960                 ScDBDocFunc aFunc( *GetViewData().GetDocShell() );
1961                 std::unique_ptr<ScDPObject> pNewObj(new ScDPObject( *pDPObj ));
1962                 pNewObj->SetSaveData( aData );
1963                 aFunc.DataPilotUpdate( pDPObj, pNewObj.get(), true, false );
1964                 pNewObj.reset();
1965 
1966                 // unmark cell selection
1967                 Unmark();
1968             }
1969         }
1970     }
1971 }
1972 
ShowDataPilotSourceData(ScDPObject & rDPObj,const Sequence<sheet::DataPilotFieldFilter> & rFilters)1973 void ScDBFunc::ShowDataPilotSourceData( ScDPObject& rDPObj, const Sequence<sheet::DataPilotFieldFilter>& rFilters )
1974 {
1975     ScDocument* pDoc = GetViewData().GetDocument();
1976     if (pDoc->GetDocumentShell()->IsReadOnly())
1977     {
1978         ErrorMessage(STR_READONLYERR);
1979         return;
1980     }
1981 
1982     Reference<sheet::XDimensionsSupplier> xDimSupplier = rDPObj.GetSource();
1983     Reference<container::XNameAccess> xDims = xDimSupplier->getDimensions();
1984     Reference<sheet::XDrillDownDataSupplier> xDDSupplier(xDimSupplier, UNO_QUERY);
1985     if (!xDDSupplier.is())
1986         return;
1987 
1988     Sequence< Sequence<Any> > aTabData = xDDSupplier->getDrillDownData(rFilters);
1989     sal_Int32 nRowSize = aTabData.getLength();
1990     if (nRowSize <= 1)
1991         // There is no data to show.  Bail out.
1992         return;
1993 
1994     SCCOL nColSize = aTabData[0].getLength();
1995 
1996     SCTAB nNewTab = GetViewData().GetTabNo();
1997 
1998     ScDocumentUniquePtr pInsDoc(new ScDocument(SCDOCMODE_CLIP));
1999     pInsDoc->ResetClip( pDoc, nNewTab );
2000     for (SCROW nRow = 0; nRow < nRowSize; ++nRow)
2001     {
2002         for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
2003         {
2004             const Any& rAny = aTabData[nRow][nCol];
2005             OUString aStr;
2006             double fVal;
2007             if (rAny >>= aStr)
2008             {
2009                 pInsDoc->SetString(ScAddress(nCol,nRow,nNewTab), aStr);
2010             }
2011             else if (rAny >>= fVal)
2012                 pInsDoc->SetValue(nCol, nRow, nNewTab, fVal);
2013         }
2014     }
2015 
2016     // set number format (important for dates)
2017     for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
2018     {
2019         OUString aStr;
2020         if (!(aTabData[0][nCol] >>= aStr))
2021             continue;
2022 
2023         Reference<XPropertySet> xPropSet(xDims->getByName(aStr), UNO_QUERY);
2024         if (!xPropSet.is())
2025             continue;
2026 
2027         Any any = xPropSet->getPropertyValue( SC_UNO_DP_NUMBERFO );
2028         sal_Int32 nNumFmt = 0;
2029         if (!(any >>= nNumFmt))
2030             continue;
2031 
2032         ScPatternAttr aPattern( pInsDoc->GetPool() );
2033         aPattern.GetItemSet().Put( SfxUInt32Item(ATTR_VALUE_FORMAT, static_cast<sal_uInt32>(nNumFmt)) );
2034         pInsDoc->ApplyPatternAreaTab(nCol, 1, nCol, nRowSize-1, nNewTab, aPattern);
2035     }
2036 
2037     SCCOL nEndCol = 0;
2038     SCROW nEndRow = 0;
2039     pInsDoc->GetCellArea( nNewTab, nEndCol, nEndRow );
2040     pInsDoc->SetClipArea( ScRange( 0, 0, nNewTab, nEndCol, nEndRow, nNewTab ) );
2041 
2042     SfxUndoManager* pMgr = GetViewData().GetDocShell()->GetUndoManager();
2043     OUString aUndo = ScResId( STR_UNDO_DOOUTLINE );
2044     pMgr->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() );
2045 
2046     OUString aNewTabName;
2047     pDoc->CreateValidTabName(aNewTabName);
2048     if ( InsertTable(aNewTabName, nNewTab) )
2049         PasteFromClip( InsertDeleteFlags::ALL, pInsDoc.get() );
2050 
2051     pMgr->LeaveListAction();
2052 }
2053 
2054 // repeat data base operations (sorting, filtering, subtotals)
2055 
RepeatDB(bool bRecord)2056 void ScDBFunc::RepeatDB( bool bRecord )
2057 {
2058     SCCOL nCurX = GetViewData().GetCurX();
2059     SCROW nCurY = GetViewData().GetCurY();
2060     SCTAB nTab = GetViewData().GetTabNo();
2061     ScDocument* pDoc = GetViewData().GetDocument();
2062     ScDBData* pDBData = GetDBData();
2063     if (bRecord && !pDoc->IsUndoEnabled())
2064         bRecord = false;
2065 
2066     ScQueryParam aQueryParam;
2067     pDBData->GetQueryParam( aQueryParam );
2068     bool bQuery = aQueryParam.GetEntry(0).bDoQuery;
2069 
2070     ScSortParam aSortParam;
2071     pDBData->GetSortParam( aSortParam );
2072     bool bSort = aSortParam.maKeyState[0].bDoSort;
2073 
2074     ScSubTotalParam aSubTotalParam;
2075     pDBData->GetSubTotalParam( aSubTotalParam );
2076     bool bSubTotal = aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly;
2077 
2078     if ( bQuery || bSort || bSubTotal )
2079     {
2080         bool bQuerySize = false;
2081         ScRange aOldQuery;
2082         ScRange aNewQuery;
2083         if (bQuery && !aQueryParam.bInplace)
2084         {
2085             ScDBData* pDest = pDoc->GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow,
2086                                                     aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT );
2087             if (pDest && pDest->IsDoSize())
2088             {
2089                 pDest->GetArea( aOldQuery );
2090                 bQuerySize = true;
2091             }
2092         }
2093 
2094         SCTAB nDummy;
2095         SCCOL nStartCol;
2096         SCROW nStartRow;
2097         SCCOL nEndCol;
2098         SCROW nEndRow;
2099         pDBData->GetArea( nDummy, nStartCol, nStartRow, nEndCol, nEndRow );
2100 
2101         //! undo only needed data ?
2102 
2103         ScDocumentUniquePtr pUndoDoc;
2104         std::unique_ptr<ScOutlineTable> pUndoTab;
2105         std::unique_ptr<ScRangeName> pUndoRange;
2106         std::unique_ptr<ScDBCollection> pUndoDB;
2107 
2108         if (bRecord)
2109         {
2110             SCTAB nTabCount = pDoc->GetTableCount();
2111             pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
2112             ScOutlineTable* pTable = pDoc->GetOutlineTable( nTab );
2113             if (pTable)
2114             {
2115                 pUndoTab.reset(new ScOutlineTable( *pTable ));
2116 
2117                 SCCOLROW nOutStartCol;                          // row/column status
2118                 SCCOLROW nOutStartRow;
2119                 SCCOLROW nOutEndCol;
2120                 SCCOLROW nOutEndRow;
2121                 pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol );
2122                 pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow );
2123 
2124                 pUndoDoc->InitUndo( pDoc, nTab, nTab, true, true );
2125                 pDoc->CopyToDocument( static_cast<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(nOutEndCol), pDoc->MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
2126                 pDoc->CopyToDocument( 0, nOutStartRow, nTab, pDoc->MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
2127             }
2128             else
2129                 pUndoDoc->InitUndo( pDoc, nTab, nTab, false, true );
2130 
2131             // Record data range - including filter results
2132             pDoc->CopyToDocument( 0,nStartRow,nTab, pDoc->MaxCol(),nEndRow,nTab, InsertDeleteFlags::ALL, false, *pUndoDoc );
2133 
2134             // all formulas for reference
2135             pDoc->CopyToDocument( 0,0,0, pDoc->MaxCol(),pDoc->MaxRow(),nTabCount-1, InsertDeleteFlags::FORMULA, false, *pUndoDoc );
2136 
2137             // data base and other ranges
2138             ScRangeName* pDocRange = pDoc->GetRangeName();
2139             if (!pDocRange->empty())
2140                 pUndoRange.reset(new ScRangeName( *pDocRange ));
2141             ScDBCollection* pDocDB = pDoc->GetDBCollection();
2142             if (!pDocDB->empty())
2143                 pUndoDB.reset(new ScDBCollection( *pDocDB ));
2144         }
2145 
2146         if (bSort && bSubTotal)
2147         {
2148             // sort without subtotals
2149 
2150             aSubTotalParam.bRemoveOnly = true;      // is reset below
2151             DoSubTotals( aSubTotalParam, false );
2152         }
2153 
2154         if (bSort)
2155         {
2156             pDBData->GetSortParam( aSortParam );            // range may have changed
2157             Sort( aSortParam, false, false);
2158         }
2159         if (bQuery)
2160         {
2161             pDBData->GetQueryParam( aQueryParam );          // range may have changed
2162             ScRange aAdvSource;
2163             if (pDBData->GetAdvancedQuerySource(aAdvSource))
2164             {
2165                 pDoc->CreateQueryParam(aAdvSource, aQueryParam);
2166                 Query( aQueryParam, &aAdvSource, false );
2167             }
2168             else
2169                 Query( aQueryParam, nullptr, false );
2170 
2171             // if not inplace the sheet may have changed
2172             if ( !aQueryParam.bInplace && aQueryParam.nDestTab != nTab )
2173                 SetTabNo( nTab );
2174         }
2175         if (bSubTotal)
2176         {
2177             pDBData->GetSubTotalParam( aSubTotalParam );    // range may have changed
2178             aSubTotalParam.bRemoveOnly = false;
2179             DoSubTotals( aSubTotalParam, false );
2180         }
2181 
2182         if (bRecord)
2183         {
2184             SCTAB nDummyTab;
2185             SCCOL nDummyCol;
2186             SCROW nDummyRow, nNewEndRow;
2187             pDBData->GetArea( nDummyTab, nDummyCol,nDummyRow, nDummyCol,nNewEndRow );
2188 
2189             const ScRange* pOld = nullptr;
2190             const ScRange* pNew = nullptr;
2191             if (bQuerySize)
2192             {
2193                 ScDBData* pDest = pDoc->GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow,
2194                                                         aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT );
2195                 if (pDest)
2196                 {
2197                     pDest->GetArea( aNewQuery );
2198                     pOld = &aOldQuery;
2199                     pNew = &aNewQuery;
2200                 }
2201             }
2202 
2203             GetViewData().GetDocShell()->GetUndoManager()->AddUndoAction(
2204                 std::make_unique<ScUndoRepeatDB>( GetViewData().GetDocShell(), nTab,
2205                                         nStartCol, nStartRow, nEndCol, nEndRow,
2206                                         nNewEndRow,
2207                                         nCurX, nCurY,
2208                                         std::move(pUndoDoc), std::move(pUndoTab),
2209                                         std::move(pUndoRange), std::move(pUndoDB),
2210                                         pOld, pNew ) );
2211         }
2212 
2213         GetViewData().GetDocShell()->PostPaint(
2214             ScRange(0, 0, nTab, pDoc->MaxCol(), pDoc->MaxRow(), nTab),
2215             PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size);
2216     }
2217     else        // "no not execute any operations"
2218         ErrorMessage(STR_MSSG_REPEATDB_0);
2219 }
2220 
OnLOKShowHideColRow(bool bColumns,SCCOLROW nStart)2221 void ScDBFunc::OnLOKShowHideColRow(bool bColumns, SCCOLROW nStart)
2222 {
2223     if (!comphelper::LibreOfficeKit::isActive())
2224         return;
2225 
2226     SCTAB nCurrentTabIndex = GetViewData().GetTabNo();
2227     SfxViewShell* pViewShell = SfxViewShell::GetFirst();
2228     while (pViewShell)
2229     {
2230         ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
2231         if (pTabViewShell)
2232         {
2233             if (bColumns)
2234                 pTabViewShell->GetViewData().GetLOKWidthHelper(nCurrentTabIndex)->invalidateByIndex(nStart);
2235             else
2236                 pTabViewShell->GetViewData().GetLOKHeightHelper(nCurrentTabIndex)->invalidateByIndex(nStart);
2237 
2238             if (pTabViewShell->getPart() == nCurrentTabIndex)
2239             {
2240                 pTabViewShell->ShowCursor();
2241                 pTabViewShell->MarkDataChanged();
2242             }
2243         }
2244         pViewShell = SfxViewShell::GetNext(*pViewShell);
2245     }
2246 }
2247 
2248 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2249