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