Files
loongoffice/sc/source/ui/docshell/dbdocfun.cxx
Luboš Luňák 1e990a5ab3 fix up order of some ScDocument functions
For better or worse the usual order of arguments in Calc is
SCCOL, SCROW, SCTAB, so make this consistent.

Change-Id: Ie63c75f5ae92f82cb757c0873f7ff569f331e0df
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134229
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
2022-05-12 16:38:38 +02:00

1791 lines
63 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sfx2/app.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <svx/dataaccessdescriptor.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdoole2.hxx>
#include <com/sun/star/sdb/CommandType.hpp>
#include <unotools/charclass.hxx>
#include <comphelper/lok.hxx>
#include <osl/diagnose.h>
#include <dbdocfun.hxx>
#include <dbdata.hxx>
#include <undodat.hxx>
#include <docsh.hxx>
#include <docfunc.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <globalnames.hxx>
#include <tabvwsh.hxx>
#include <patattr.hxx>
#include <rangenam.hxx>
#include <olinetab.hxx>
#include <dpobject.hxx>
#include <dpsave.hxx>
#include <dociter.hxx>
#include <editable.hxx>
#include <attrib.hxx>
#include <drwlayer.hxx>
#include <dpshttab.hxx>
#include <hints.hxx>
#include <queryentry.hxx>
#include <markdata.hxx>
#include <progress.hxx>
#include <undosort.hxx>
#include <inputopt.hxx>
#include <scmod.hxx>
#include <chartlis.hxx>
#include <ChartTools.hxx>
#include <memory>
using namespace ::com::sun::star;
bool ScDBDocFunc::AddDBRange( const OUString& rName, const ScRange& rRange )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection* pDocColl = rDoc.GetDBCollection();
bool bUndo (rDoc.IsUndoEnabled());
std::unique_ptr<ScDBCollection> pUndoColl;
if (bUndo)
pUndoColl.reset( new ScDBCollection( *pDocColl ) );
std::unique_ptr<ScDBData> pNew(new ScDBData( rName, rRange.aStart.Tab(),
rRange.aStart.Col(), rRange.aStart.Row(),
rRange.aEnd.Col(), rRange.aEnd.Row() ));
// #i55926# While loading XML, formula cells only have a single string token,
// so CompileDBFormula would never find any name (index) tokens, and would
// unnecessarily loop through all cells.
bool bCompile = !rDoc.IsImportingXML();
bool bOk;
if ( bCompile )
rDoc.PreprocessDBDataUpdate();
if ( rName == STR_DB_LOCAL_NONAME )
{
rDoc.SetAnonymousDBData(rRange.aStart.Tab(), std::move(pNew));
bOk = true;
}
else
{
bOk = pDocColl->getNamedDBs().insert(std::move(pNew));
}
if ( bCompile )
rDoc.CompileHybridFormula();
if (!bOk)
{
return false;
}
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl),
std::make_unique<ScDBCollection>( *pDocColl ) ) );
}
aModificator.SetDocumentModified();
SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) );
return true;
}
bool ScDBDocFunc::DeleteDBRange(const OUString& rName)
{
bool bDone = false;
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection* pDocColl = rDoc.GetDBCollection();
bool bUndo = rDoc.IsUndoEnabled();
ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs();
auto const iter = rDBs.findByUpperName2(ScGlobal::getCharClass().uppercase(rName));
if (iter != rDBs.end())
{
ScDocShellModificator aModificator( rDocShell );
std::unique_ptr<ScDBCollection> pUndoColl;
if (bUndo)
pUndoColl.reset( new ScDBCollection( *pDocColl ) );
rDoc.PreprocessDBDataUpdate();
rDBs.erase(iter);
rDoc.CompileHybridFormula();
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl),
std::make_unique<ScDBCollection>( *pDocColl ) ) );
}
aModificator.SetDocumentModified();
SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) );
bDone = true;
}
return bDone;
}
bool ScDBDocFunc::RenameDBRange( const OUString& rOld, const OUString& rNew )
{
bool bDone = false;
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection* pDocColl = rDoc.GetDBCollection();
bool bUndo = rDoc.IsUndoEnabled();
ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs();
auto const iterOld = rDBs.findByUpperName2(ScGlobal::getCharClass().uppercase(rOld));
const ScDBData* pNew = rDBs.findByUpperName(ScGlobal::getCharClass().uppercase(rNew));
if (iterOld != rDBs.end() && !pNew)
{
ScDocShellModificator aModificator( rDocShell );
std::unique_ptr<ScDBData> pNewData(new ScDBData(rNew, **iterOld));
std::unique_ptr<ScDBCollection> pUndoColl( new ScDBCollection( *pDocColl ) );
rDoc.PreprocessDBDataUpdate();
rDBs.erase(iterOld);
bool bInserted = rDBs.insert(std::move(pNewData));
if (!bInserted) // error -> restore old state
{
rDoc.SetDBCollection(std::move(pUndoColl)); // belongs to the document then
}
rDoc.CompileHybridFormula();
if (bInserted) // insertion worked
{
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl),
std::make_unique<ScDBCollection>( *pDocColl ) ) );
}
else
pUndoColl.reset();
aModificator.SetDocumentModified();
SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) );
bDone = true;
}
}
return bDone;
}
void ScDBDocFunc::ModifyDBData( const ScDBData& rNewData )
{
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection* pDocColl = rDoc.GetDBCollection();
bool bUndo = rDoc.IsUndoEnabled();
ScDBData* pData = nullptr;
if (rNewData.GetName() == STR_DB_LOCAL_NONAME)
{
ScRange aRange;
rNewData.GetArea(aRange);
SCTAB nTab = aRange.aStart.Tab();
pData = rDoc.GetAnonymousDBData(nTab);
}
else
pData = pDocColl->getNamedDBs().findByUpperName(rNewData.GetUpperName());
if (!pData)
return;
ScDocShellModificator aModificator( rDocShell );
ScRange aOldRange, aNewRange;
pData->GetArea(aOldRange);
rNewData.GetArea(aNewRange);
bool bAreaChanged = ( aOldRange != aNewRange ); // then a recompilation is needed
std::unique_ptr<ScDBCollection> pUndoColl;
if (bUndo)
pUndoColl.reset( new ScDBCollection( *pDocColl ) );
*pData = rNewData;
if (bAreaChanged)
rDoc.CompileDBFormula();
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl),
std::make_unique<ScDBCollection>( *pDocColl ) ) );
}
aModificator.SetDocumentModified();
}
void ScDBDocFunc::ModifyAllDBData( const ScDBCollection& rNewColl, const std::vector<ScRange>& rDelAreaList )
{
ScDocShellModificator aModificator(rDocShell);
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection* pOldColl = rDoc.GetDBCollection();
std::unique_ptr<ScDBCollection> pUndoColl;
bool bRecord = rDoc.IsUndoEnabled();
for (const auto& rDelArea : rDelAreaList)
{
// unregistering target in SBA no longer necessary
const ScAddress& rStart = rDelArea.aStart;
const ScAddress& rEnd = rDelArea.aEnd;
rDocShell.DBAreaDeleted(
rStart.Tab(), rStart.Col(), rStart.Row(), rEnd.Col());
}
if (bRecord)
pUndoColl.reset( new ScDBCollection( *pOldColl ) );
// register target in SBA no longer necessary
rDoc.PreprocessDBDataUpdate();
rDoc.SetDBCollection( std::unique_ptr<ScDBCollection>(new ScDBCollection( rNewColl )) );
rDoc.CompileHybridFormula();
pOldColl = nullptr;
rDocShell.PostPaint(ScRange(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB), PaintPartFlags::Grid);
aModificator.SetDocumentModified();
SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) );
if (bRecord)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDBData>(&rDocShell, std::move(pUndoColl),
std::make_unique<ScDBCollection>(rNewColl)));
}
}
bool ScDBDocFunc::RepeatDB( const OUString& rDBName, bool bApi, bool bIsUnnamed, SCTAB aTab )
{
//! use also for ScDBFunc::RepeatDB !
bool bDone = false;
ScDocument& rDoc = rDocShell.GetDocument();
bool bRecord = true;
if (!rDoc.IsUndoEnabled())
bRecord = false;
ScDBData* pDBData = nullptr;
if (bIsUnnamed)
{
pDBData = rDoc.GetAnonymousDBData( aTab );
}
else
{
ScDBCollection* pColl = rDoc.GetDBCollection();
if (pColl)
pDBData = pColl->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName));
}
if ( pDBData )
{
ScQueryParam aQueryParam;
pDBData->GetQueryParam( aQueryParam );
bool bQuery = aQueryParam.GetEntry(0).bDoQuery;
ScSortParam aSortParam;
pDBData->GetSortParam( aSortParam );
bool bSort = aSortParam.maKeyState[0].bDoSort;
ScSubTotalParam aSubTotalParam;
pDBData->GetSubTotalParam( aSubTotalParam );
bool bSubTotal = aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly;
if ( bQuery || bSort || bSubTotal )
{
bool bQuerySize = false;
ScRange aOldQuery;
ScRange aNewQuery;
if (bQuery && !aQueryParam.bInplace)
{
ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow,
aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT );
if (pDest && pDest->IsDoSize())
{
pDest->GetArea( aOldQuery );
bQuerySize = true;
}
}
SCTAB nTab;
SCCOL nStartCol;
SCROW nStartRow;
SCCOL nEndCol;
SCROW nEndRow;
pDBData->GetArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow );
//! Undo needed data only ?
ScDocumentUniquePtr pUndoDoc;
std::unique_ptr<ScOutlineTable> pUndoTab;
std::unique_ptr<ScRangeName> pUndoRange;
std::unique_ptr<ScDBCollection> pUndoDB;
if (bRecord)
{
SCTAB nTabCount = rDoc.GetTableCount();
pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab );
if (pTable)
{
pUndoTab.reset(new ScOutlineTable( *pTable ));
// column/row state
SCCOLROW nOutStartCol, nOutEndCol;
SCCOLROW nOutStartRow, nOutEndRow;
pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol );
pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow );
pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true );
rDoc.CopyToDocument(static_cast<SCCOL>(nOutStartCol), 0,
nTab, static_cast<SCCOL>(nOutEndCol), rDoc.MaxRow(), nTab,
InsertDeleteFlags::NONE, false, *pUndoDoc);
rDoc.CopyToDocument(0, static_cast<SCROW>(nOutStartRow),
nTab, rDoc.MaxCol(), static_cast<SCROW>(nOutEndRow), nTab,
InsertDeleteFlags::NONE, false, *pUndoDoc);
}
else
pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true );
// secure data range - incl. filtering result
rDoc.CopyToDocument(0, nStartRow, nTab, rDoc.MaxCol(), nEndRow, nTab, InsertDeleteFlags::ALL, false, *pUndoDoc);
// all formulas because of references
rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), nTabCount-1, InsertDeleteFlags::FORMULA, false, *pUndoDoc);
// ranges of DB and other
ScRangeName* pDocRange = rDoc.GetRangeName();
if (!pDocRange->empty())
pUndoRange.reset(new ScRangeName( *pDocRange ));
ScDBCollection* pDocDB = rDoc.GetDBCollection();
if (!pDocDB->empty())
pUndoDB.reset(new ScDBCollection( *pDocDB ));
}
if (bSort && bSubTotal)
{
// sort without SubTotals
aSubTotalParam.bRemoveOnly = true; // will be reset again further down
DoSubTotals( nTab, aSubTotalParam, false, bApi );
}
if (bSort)
{
pDBData->GetSortParam( aSortParam ); // range may have changed
(void)Sort( nTab, aSortParam, false, false, bApi );
}
if (bQuery)
{
pDBData->GetQueryParam( aQueryParam ); // range may have changed
ScRange aAdvSource;
if (pDBData->GetAdvancedQuerySource(aAdvSource))
Query( nTab, aQueryParam, &aAdvSource, false, bApi );
else
Query( nTab, aQueryParam, nullptr, false, bApi );
// at not-inplace the table may have been converted
// if ( !aQueryParam.bInplace && aQueryParam.nDestTab != nTab )
// SetTabNo( nTab );
}
if (bSubTotal)
{
pDBData->GetSubTotalParam( aSubTotalParam ); // range may have changed
aSubTotalParam.bRemoveOnly = false;
DoSubTotals( nTab, aSubTotalParam, false, bApi );
}
if (bRecord)
{
SCTAB nDummyTab;
SCCOL nDummyCol;
SCROW nDummyRow;
SCROW nNewEndRow;
pDBData->GetArea( nDummyTab, nDummyCol,nDummyRow, nDummyCol,nNewEndRow );
const ScRange* pOld = nullptr;
const ScRange* pNew = nullptr;
if (bQuerySize)
{
ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow,
aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT );
if (pDest)
{
pDest->GetArea( aNewQuery );
pOld = &aOldQuery;
pNew = &aNewQuery;
}
}
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoRepeatDB>( &rDocShell, nTab,
nStartCol, nStartRow, nEndCol, nEndRow,
nNewEndRow,
//nCurX, nCurY,
nStartCol, nStartRow,
std::move(pUndoDoc), std::move(pUndoTab),
std::move(pUndoRange), std::move(pUndoDB),
pOld, pNew ) );
}
rDocShell.PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab),
PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size);
bDone = true;
}
else if (!bApi) // "Don't execute any operations"
rDocShell.ErrorMessage(STR_MSSG_REPEATDB_0);
}
return bDone;
}
bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam,
bool bRecord, bool bPaint, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rSortParam.nCol1, rSortParam.nRow1,
rSortParam.nCol2, rSortParam.nRow2 );
if (!pDBData)
{
OSL_FAIL( "Sort: no DBData" );
return false;
}
bool bCopy = !rSortParam.bInplace;
if ( bCopy && rSortParam.nDestCol == rSortParam.nCol1 &&
rSortParam.nDestRow == rSortParam.nRow1 && rSortParam.nDestTab == nTab )
bCopy = false;
ScSortParam aLocalParam( rSortParam );
if ( bCopy )
{
// Copy the data range to the destination then move the sort range to it.
ScRange aSrcRange(rSortParam.nCol1, rSortParam.nRow1, nTab, rSortParam.nCol2, rSortParam.nRow2, nTab);
ScAddress aDestPos(rSortParam.nDestCol,rSortParam.nDestRow,rSortParam.nDestTab);
ScDocFunc& rDocFunc = rDocShell.GetDocFunc();
bool bRet = rDocFunc.MoveBlock(aSrcRange, aDestPos, false, bRecord, bPaint, bApi);
if (!bRet)
return false;
aLocalParam.MoveToDest();
nTab = aLocalParam.nDestTab;
}
// tdf#119804: If there is a header row/column, it won't be affected by
// sorting; so we can exclude it from the test.
SCROW nStartingRowToEdit = aLocalParam.nRow1;
SCCOL nStartingColToEdit = aLocalParam.nCol1;
if ( aLocalParam.bHasHeader )
{
if ( aLocalParam.bByRow )
nStartingRowToEdit++;
else
nStartingColToEdit++;
}
ScEditableTester aTester( rDoc, nTab, nStartingColToEdit, nStartingRowToEdit,
aLocalParam.nCol2, aLocalParam.nRow2, true /*bNoMatrixAtAll*/ );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false;
}
const ScInputOptions aInputOption = SC_MOD()->GetInputOptions();
const bool bUpdateRefs = aInputOption.GetSortRefUpdate();
// Adjust aLocalParam cols/rows to used data area. Keep sticky top row or
// column (depending on direction) in any case, not just if it has headers,
// so empty leading cells will be sorted to the end.
// aLocalParam.nCol/Row will encompass data content only, extras in
// aLocalParam.aDataAreaExtras.
bool bShrunk = false;
aLocalParam.aDataAreaExtras.resetArea();
rDoc.ShrinkToUsedDataArea(bShrunk, nTab, aLocalParam.nCol1, aLocalParam.nRow1,
aLocalParam.nCol2, aLocalParam.nRow2, false, aLocalParam.bByRow,
!aLocalParam.bByRow,
(aLocalParam.aDataAreaExtras.anyExtrasWanted() ?
&aLocalParam.aDataAreaExtras : nullptr));
SCROW nStartRow = aLocalParam.nRow1;
if (aLocalParam.bByRow && aLocalParam.bHasHeader && nStartRow < aLocalParam.nRow2)
++nStartRow;
SCCOL nOverallCol1 = aLocalParam.nCol1;
SCROW nOverallRow1 = aLocalParam.nRow1;
SCCOL nOverallCol2 = aLocalParam.nCol2;
SCROW nOverallRow2 = aLocalParam.nRow2;
if (aLocalParam.aDataAreaExtras.anyExtrasWanted())
{
// Trailing empty excess columns/rows are excluded from being sorted,
// they stick at the end. Clip them.
const ScDataAreaExtras::Clip eClip = (aLocalParam.bByRow ?
ScDataAreaExtras::Clip::Row : ScDataAreaExtras::Clip::Col);
aLocalParam.aDataAreaExtras.GetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2, eClip);
// Make it permanent.
aLocalParam.aDataAreaExtras.SetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2);
if (bUpdateRefs)
{
// With update references the entire range needs to be handled as
// one entity for references pointing within to be moved along,
// even when there's no data content. For huge ranges we may be
// DOOMed then.
aLocalParam.nCol1 = nOverallCol1;
aLocalParam.nRow1 = nOverallRow1;
aLocalParam.nCol2 = nOverallCol2;
aLocalParam.nRow2 = nOverallRow2;
}
}
if (aLocalParam.aDataAreaExtras.mbCellFormats
&& rDoc.HasAttrib( nOverallCol1, nStartRow, nTab, nOverallCol2, nOverallRow2, nTab,
HasAttrFlags::Merged | HasAttrFlags::Overlapped))
{
// Merge attributes would be mixed up during sorting.
if (!bApi)
rDocShell.ErrorMessage(STR_SORT_ERR_MERGED);
return false;
}
// execute
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
// Calculate the script types for all cells in the sort range beforehand.
// This will speed up the row height adjustment that takes place after the
// sort.
rDoc.UpdateScriptTypes(
ScAddress(aLocalParam.nCol1,nStartRow,nTab),
aLocalParam.nCol2-aLocalParam.nCol1+1,
aLocalParam.nRow2-nStartRow+1);
// No point adjusting row heights after the sort when all rows have the same height.
bool bUniformRowHeight = rDoc.HasUniformRowHeight(nTab, nStartRow, nOverallRow2);
bool bRepeatQuery = false; // repeat existing filter?
ScQueryParam aQueryParam;
pDBData->GetQueryParam( aQueryParam );
if ( aQueryParam.GetEntry(0).bDoQuery )
bRepeatQuery = true;
sc::ReorderParam aUndoParam;
// don't call ScDocument::Sort with an empty SortParam (may be empty here if bCopy is set)
if (aLocalParam.GetSortKeyCount() && aLocalParam.maKeyState[0].bDoSort)
{
ScProgress aProgress(&rDocShell, ScResId(STR_PROGRESS_SORTING), 0, true);
if (!bRepeatQuery)
bRepeatQuery = rDoc.HasHiddenRows(aLocalParam.nRow1, aLocalParam.nRow2, nTab);
rDoc.Sort(nTab, aLocalParam, bRepeatQuery, bUpdateRefs, &aProgress, &aUndoParam);
}
if (bRecord)
{
// Set up an undo object.
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<sc::UndoSort>(&rDocShell, aUndoParam));
}
pDBData->SetSortParam(rSortParam);
// Remember additional settings on anonymous database ranges.
if (pDBData == rDoc.GetAnonymousDBData( nTab) || rDoc.GetDBCollection()->getAnonDBs().has( pDBData))
pDBData->UpdateFromSortParam( rSortParam);
if (comphelper::LibreOfficeKit::isActive())
{
SfxViewShell* pSomeViewForThisDoc = rDocShell.GetBestViewShell(false);
SfxViewShell* pViewShell = SfxViewShell::GetFirst();
while (pViewShell)
{
ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
if (pTabViewShell && pTabViewShell->GetDocId() == pSomeViewForThisDoc->GetDocId())
{
if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab))
pPosHelper->invalidateByIndex(nStartRow);
}
pViewShell = SfxViewShell::GetNext(*pViewShell);
}
ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
pSomeViewForThisDoc, false /* bColumns */, true /* bRows */, true /* bSizes*/,
true /* bHidden */, true /* bFiltered */, true /* bGroups */, nTab);
}
if (nStartRow <= aLocalParam.nRow2)
{
ScRange aDirtyRange(
aLocalParam.nCol1, nStartRow, nTab,
aLocalParam.nCol2, aLocalParam.nRow2, nTab);
rDoc.SetDirty( aDirtyRange, true );
}
if (bPaint)
{
PaintPartFlags nPaint = PaintPartFlags::Grid;
SCCOL nStartX = nOverallCol1;
SCROW nStartY = nOverallRow1;
SCCOL nEndX = nOverallCol2;
SCROW nEndY = nOverallRow2;
if ( bRepeatQuery )
{
nPaint |= PaintPartFlags::Left;
nStartX = 0;
nEndX = rDoc.MaxCol();
}
rDocShell.PostPaint(ScRange(nStartX, nStartY, nTab, nEndX, nEndY, nTab), nPaint);
}
if (!bUniformRowHeight && nStartRow <= nOverallRow2)
rDocShell.AdjustRowHeight(nStartRow, nOverallRow2, nTab);
aModificator.SetDocumentModified();
return true;
}
bool ScDBDocFunc::Query( SCTAB nTab, const ScQueryParam& rQueryParam,
const ScRange* pAdvSource, bool bRecord, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();
if (pViewSh && ScTabViewShell::isAnyEditViewInRange(pViewSh, /*bColumns*/ false, rQueryParam.nRow1, rQueryParam.nRow2))
{
return false;
}
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rQueryParam.nCol1, rQueryParam.nRow1,
rQueryParam.nCol2, rQueryParam.nRow2 );
if (!pDBData)
{
OSL_FAIL( "Query: no DBData" );
return false;
}
// Change from Inplace to non-Inplace, only then cancel Inplace:
// (only if "Persistent" is selected in the dialog)
if ( !rQueryParam.bInplace && pDBData->HasQueryParam() && rQueryParam.bDestPers )
{
ScQueryParam aOldQuery;
pDBData->GetQueryParam(aOldQuery);
if (aOldQuery.bInplace)
{
// cancel old filtering
SCSIZE nEC = aOldQuery.GetEntryCount();
for (SCSIZE i=0; i<nEC; i++)
aOldQuery.GetEntry(i).bDoQuery = false;
aOldQuery.bDuplicate = true;
Query( nTab, aOldQuery, nullptr, bRecord, bApi );
}
}
ScQueryParam aLocalParam( rQueryParam ); // for Paint / destination range
bool bCopy = !rQueryParam.bInplace; // copied in Table::Query
ScDBData* pDestData = nullptr; // range to be copied to
bool bDoSize = false; // adjust destination size (insert/delete)
SCCOL nFormulaCols = 0; // only at bDoSize
bool bKeepFmt = false;
ScRange aOldDest;
ScRange aDestTotal;
if ( bCopy && rQueryParam.nDestCol == rQueryParam.nCol1 &&
rQueryParam.nDestRow == rQueryParam.nRow1 && rQueryParam.nDestTab == nTab )
bCopy = false;
SCTAB nDestTab = nTab;
if ( bCopy )
{
aLocalParam.MoveToDest();
nDestTab = rQueryParam.nDestTab;
if ( !rDoc.ValidColRow( aLocalParam.nCol2, aLocalParam.nRow2 ) )
{
if (!bApi)
rDocShell.ErrorMessage(STR_PASTE_FULL);
return false;
}
ScEditableTester aTester( rDoc, nDestTab, aLocalParam.nCol1,aLocalParam.nRow1,
aLocalParam.nCol2,aLocalParam.nRow2);
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false;
}
pDestData = rDoc.GetDBAtCursor( rQueryParam.nDestCol, rQueryParam.nDestRow,
rQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT );
if (pDestData)
{
pDestData->GetArea( aOldDest );
aDestTotal=ScRange( rQueryParam.nDestCol,
rQueryParam.nDestRow,
nDestTab,
rQueryParam.nDestCol + rQueryParam.nCol2 - rQueryParam.nCol1,
rQueryParam.nDestRow + rQueryParam.nRow2 - rQueryParam.nRow1,
nDestTab );
bDoSize = pDestData->IsDoSize();
// test if formulas need to be filled in (nFormulaCols):
if ( bDoSize && aOldDest.aEnd.Col() == aDestTotal.aEnd.Col() )
{
SCCOL nTestCol = aOldDest.aEnd.Col() + 1; // next to the range
SCROW nTestRow = rQueryParam.nDestRow +
( aLocalParam.bHasHeader ? 1 : 0 );
while ( nTestCol <= rDoc.MaxCol() &&
rDoc.GetCellType(ScAddress( nTestCol, nTestRow, nTab )) == CELLTYPE_FORMULA )
{
++nTestCol;
++nFormulaCols;
}
}
bKeepFmt = pDestData->IsKeepFmt();
if ( bDoSize && !rDoc.CanFitBlock( aOldDest, aDestTotal ) )
{
if (!bApi)
rDocShell.ErrorMessage(STR_MSSG_DOSUBTOTALS_2); // cannot insert rows
return false;
}
}
}
// execute
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
bool bKeepSub = false; // repeat existing partial results?
if (rQueryParam.GetEntry(0).bDoQuery) // not at cancellation
{
ScSubTotalParam aSubTotalParam;
pDBData->GetSubTotalParam( aSubTotalParam ); // partial results exist?
if ( aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly )
bKeepSub = true;
}
ScDocumentUniquePtr pUndoDoc;
std::unique_ptr<ScDBCollection> pUndoDB;
const ScRange* pOld = nullptr;
if ( bRecord )
{
pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
if (bCopy)
{
pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab, false, true );
rDoc.CopyToDocument(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab,
aLocalParam.nCol2, aLocalParam.nRow2, nDestTab,
InsertDeleteFlags::ALL, false, *pUndoDoc);
// secure attributes in case they were copied along
if (pDestData)
{
rDoc.CopyToDocument(aOldDest, InsertDeleteFlags::ALL, false, *pUndoDoc);
pOld = &aOldDest;
}
}
else
{
pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true );
rDoc.CopyToDocument(0, rQueryParam.nRow1, nTab, rDoc.MaxCol(), rQueryParam.nRow2, nTab,
InsertDeleteFlags::NONE, false, *pUndoDoc);
}
ScDBCollection* pDocDB = rDoc.GetDBCollection();
if (!pDocDB->empty())
pUndoDB.reset(new ScDBCollection( *pDocDB ));
rDoc.BeginDrawUndo();
}
std::unique_ptr<ScDocument> pAttribDoc;
ScRange aAttribRange;
if (pDestData) // delete destination range
{
if ( bKeepFmt )
{
// smaller of the end columns, header+1 row
aAttribRange = aOldDest;
if ( aAttribRange.aEnd.Col() > aDestTotal.aEnd.Col() )
aAttribRange.aEnd.SetCol( aDestTotal.aEnd.Col() );
aAttribRange.aEnd.SetRow( aAttribRange.aStart.Row() +
( aLocalParam.bHasHeader ? 1 : 0 ) );
// also for filled-in formulas
aAttribRange.aEnd.SetCol( aAttribRange.aEnd.Col() + nFormulaCols );
pAttribDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
pAttribDoc->InitUndo( rDoc, nDestTab, nDestTab, false, true );
rDoc.CopyToDocument(aAttribRange, InsertDeleteFlags::ATTRIB, false, *pAttribDoc);
}
if ( bDoSize )
rDoc.FitBlock( aOldDest, aDestTotal );
else
rDoc.DeleteAreaTab(aOldDest, InsertDeleteFlags::ALL); // simply delete
}
// execute filtering on the document
SCSIZE nCount = rDoc.Query( nTab, rQueryParam, bKeepSub );
pDBData->CalcSaveFilteredCount( nCount );
if (bCopy)
{
aLocalParam.nRow2 = aLocalParam.nRow1 + nCount;
if (!aLocalParam.bHasHeader && nCount > 0)
--aLocalParam.nRow2;
if ( bDoSize )
{
// adjust to the real result range
// (this here is always a reduction)
ScRange aNewDest( aLocalParam.nCol1, aLocalParam.nRow1, nDestTab,
aLocalParam.nCol2, aLocalParam.nRow2, nDestTab );
rDoc.FitBlock( aDestTotal, aNewDest, false ); // sal_False - don't delete
if ( nFormulaCols > 0 )
{
// fill in formulas
//! Undo (Query and Repeat) !!!
ScRange aNewForm( aLocalParam.nCol2+1, aLocalParam.nRow1, nDestTab,
aLocalParam.nCol2+nFormulaCols, aLocalParam.nRow2, nDestTab );
ScRange aOldForm = aNewForm;
aOldForm.aEnd.SetRow( aOldDest.aEnd.Row() );
rDoc.FitBlock( aOldForm, aNewForm, false );
ScMarkData aMark(rDoc.GetSheetLimits());
aMark.SelectOneTable(nDestTab);
SCROW nFStartY = aLocalParam.nRow1 + ( aLocalParam.bHasHeader ? 1 : 0 );
sal_uLong nProgCount = nFormulaCols;
nProgCount *= aLocalParam.nRow2 - nFStartY;
ScProgress aProgress( rDoc.GetDocumentShell(),
ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true );
rDoc.Fill( aLocalParam.nCol2+1, nFStartY,
aLocalParam.nCol2+nFormulaCols, nFStartY, &aProgress, aMark,
aLocalParam.nRow2 - nFStartY,
FILL_TO_BOTTOM, FILL_SIMPLE );
}
}
if ( pAttribDoc ) // copy back the memorized attributes
{
// Header
if (aLocalParam.bHasHeader)
{
ScRange aHdrRange = aAttribRange;
aHdrRange.aEnd.SetRow( aHdrRange.aStart.Row() );
pAttribDoc->CopyToDocument(aHdrRange, InsertDeleteFlags::ATTRIB, false, rDoc);
}
// Data
SCCOL nAttrEndCol = aAttribRange.aEnd.Col();
SCROW nAttrRow = aAttribRange.aStart.Row() + ( aLocalParam.bHasHeader ? 1 : 0 );
for (SCCOL nCol = aAttribRange.aStart.Col(); nCol<=nAttrEndCol; nCol++)
{
const ScPatternAttr* pSrcPattern = pAttribDoc->GetPattern(
nCol, nAttrRow, nDestTab );
OSL_ENSURE(pSrcPattern,"Pattern is 0");
if (pSrcPattern)
{
rDoc.ApplyPatternAreaTab( nCol, nAttrRow, nCol, aLocalParam.nRow2,
nDestTab, *pSrcPattern );
const ScStyleSheet* pStyle = pSrcPattern->GetStyleSheet();
if (pStyle)
rDoc.ApplyStyleAreaTab( nCol, nAttrRow, nCol, aLocalParam.nRow2,
nDestTab, *pStyle );
}
}
}
}
// saving: Inplace always, otherwise depending on setting
// old Inplace-Filter may have already been removed
bool bSave = rQueryParam.bInplace || rQueryParam.bDestPers;
if (bSave) // memorize
{
pDBData->SetQueryParam( rQueryParam );
pDBData->SetHeader( rQueryParam.bHasHeader ); //! ???
pDBData->SetAdvancedQuerySource( pAdvSource ); // after SetQueryParam
}
if (bCopy) // memorize new DB range
{
// Selection is done afterwards from outside (dbfunc).
// Currently through the DB area at the destination position,
// so a range must be created there in any case.
ScDBData* pNewData;
if (pDestData)
pNewData = pDestData; // range exists -> adjust (always!)
else // create range
pNewData = rDocShell.GetDBData(
ScRange( aLocalParam.nCol1, aLocalParam.nRow1, nDestTab,
aLocalParam.nCol2, aLocalParam.nRow2, nDestTab ),
SC_DB_MAKE, ScGetDBSelection::ForceMark );
if (pNewData)
{
pNewData->SetArea( nDestTab, aLocalParam.nCol1, aLocalParam.nRow1,
aLocalParam.nCol2, aLocalParam.nRow2 );
// query parameter is no longer set at the destination, only leads to confusion
// and mistakes with the query parameter at the source range (#37187#)
}
else
{
OSL_FAIL("Target are not available");
}
}
if (!bCopy)
{
rDoc.InvalidatePageBreaks(nTab);
rDoc.UpdatePageBreaks( nTab );
}
// #i23299# Subtotal functions depend on cell's filtered states.
ScRange aDirtyRange(0 , aLocalParam.nRow1, nDestTab, rDoc.MaxCol(), aLocalParam.nRow2, nDestTab);
rDoc.SetSubTotalCellsDirty(aDirtyRange);
if ( bRecord )
{
// create undo action after executing, because of drawing layer undo
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoQuery>( &rDocShell, nTab, rQueryParam, std::move(pUndoDoc), std::move(pUndoDB),
pOld, bDoSize, pAdvSource ) );
}
if ( pViewSh )
{
// could there be horizontal autofilter ?
// maybe it would be better to set bColumns to !rQueryParam.bByRow ?
// anyway at the beginning the value of bByRow is 'false'
// then after the first sort action it becomes 'true'
pViewSh->OnLOKShowHideColRow(/*bColumns*/ false, rQueryParam.nRow1 - 1);
}
if (bCopy)
{
SCCOL nEndX = aLocalParam.nCol2;
SCROW nEndY = aLocalParam.nRow2;
if (pDestData)
{
if ( aOldDest.aEnd.Col() > nEndX )
nEndX = aOldDest.aEnd.Col();
if ( aOldDest.aEnd.Row() > nEndY )
nEndY = aOldDest.aEnd.Row();
}
if (bDoSize)
nEndY = rDoc.MaxRow();
// remove AutoFilter button flags
rDocShell.DBAreaDeleted(nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, aLocalParam.nCol2);
rDocShell.PostPaint(
ScRange(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, nEndX, nEndY, nDestTab),
PaintPartFlags::Grid);
}
else
rDocShell.PostPaint(
ScRange(0, rQueryParam.nRow1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab),
PaintPartFlags::Grid | PaintPartFlags::Left);
aModificator.SetDocumentModified();
return true;
}
void ScDBDocFunc::DoSubTotals( SCTAB nTab, const ScSubTotalParam& rParam,
bool bRecord, bool bApi )
{
//! use also for ScDBFunc::DoSubTotals !
// then stays outside:
// - mark new range (from DBData)
// - SelectionChanged (?)
bool bDo = !rParam.bRemoveOnly; // sal_False = only delete
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rParam.nCol1, rParam.nRow1,
rParam.nCol2, rParam.nRow2 );
if (!pDBData)
{
OSL_FAIL( "SubTotals: no DBData" );
return;
}
ScEditableTester aTester( rDoc, nTab, 0,rParam.nRow1+1, rDoc.MaxCol(),rDoc.MaxRow() );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return;
}
if (rDoc.HasAttrib( rParam.nCol1, rParam.nRow1+1, nTab,
rParam.nCol2, rParam.nRow2, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
{
if (!bApi)
rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0); // don't insert into merged
return;
}
bool bOk = true;
if (rParam.bReplace)
{
if (rDoc.TestRemoveSubTotals( nTab, rParam ))
{
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Question,
VclButtonsType::YesNo, ScResId(STR_MSSG_DOSUBTOTALS_1))); // "Delete Data?"
xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); // "StarCalc"
bOk = xBox->run() == RET_YES;
}
}
if (!bOk)
return;
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
ScDocShellModificator aModificator( rDocShell );
ScSubTotalParam aNewParam( rParam ); // end of range is being changed
ScDocumentUniquePtr pUndoDoc;
std::unique_ptr<ScOutlineTable> pUndoTab;
std::unique_ptr<ScRangeName> pUndoRange;
std::unique_ptr<ScDBCollection> pUndoDB;
if (bRecord) // secure old data
{
bool bOldFilter = bDo && rParam.bDoSort;
SCTAB nTabCount = rDoc.GetTableCount();
pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab );
if (pTable)
{
pUndoTab.reset(new ScOutlineTable( *pTable ));
// column/row state
SCCOLROW nOutStartCol, nOutEndCol;
SCCOLROW nOutStartRow, nOutEndRow;
pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol );
pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow );
pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true );
rDoc.CopyToDocument(static_cast<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(nOutEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc);
rDoc.CopyToDocument(0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc);
}
else
pUndoDoc->InitUndo( rDoc, nTab, nTab, false, bOldFilter );
// secure data range - incl. filtering result
rDoc.CopyToDocument(0, rParam.nRow1+1,nTab, rDoc.MaxCol(),rParam.nRow2,nTab,
InsertDeleteFlags::ALL, false, *pUndoDoc);
// all formulas because of references
rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1,
InsertDeleteFlags::FORMULA, false, *pUndoDoc);
// ranges of DB and other
ScRangeName* pDocRange = rDoc.GetRangeName();
if (!pDocRange->empty())
pUndoRange.reset(new ScRangeName( *pDocRange ));
ScDBCollection* pDocDB = rDoc.GetDBCollection();
if (!pDocDB->empty())
pUndoDB.reset(new ScDBCollection( *pDocDB ));
}
// rDoc.SetOutlineTable( nTab, NULL );
ScOutlineTable* pOut = rDoc.GetOutlineTable( nTab );
if (pOut)
pOut->GetRowArray().RemoveAll(); // only delete row outlines
if (rParam.bReplace)
rDoc.RemoveSubTotals( nTab, aNewParam );
bool bSuccess = true;
if (bDo)
{
// sort
if ( rParam.bDoSort )
{
pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 );
// set partial result field to before the sorting
// (Duplicates are omitted, so can be called again)
ScSortParam aOldSort;
pDBData->GetSortParam( aOldSort );
ScSortParam aSortParam( aNewParam, aOldSort );
Sort( nTab, aSortParam, false, false, bApi );
}
bSuccess = rDoc.DoSubTotals( nTab, aNewParam );
rDoc.SetDrawPageSize(nTab);
}
ScRange aDirtyRange( aNewParam.nCol1, aNewParam.nRow1, nTab,
aNewParam.nCol2, aNewParam.nRow2, nTab );
rDoc.SetDirty( aDirtyRange, true );
if (bRecord)
{
// ScDBData* pUndoDBData = pDBData ? new ScDBData( *pDBData ) : NULL;
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSubTotals>( &rDocShell, nTab,
rParam, aNewParam.nRow2,
std::move(pUndoDoc), std::move(pUndoTab), // pUndoDBData,
std::move(pUndoRange), std::move(pUndoDB) ) );
}
if (!bSuccess)
{
// "Cannot insert rows"
if (!bApi)
rDocShell.ErrorMessage(STR_MSSG_DOSUBTOTALS_2);
}
// memorize
pDBData->SetSubTotalParam( aNewParam );
pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 );
rDoc.CompileDBFormula();
rDocShell.PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab),
PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size);
aModificator.SetDocumentModified();
}
namespace {
bool lcl_EmptyExcept( ScDocument& rDoc, const ScRange& rRange, const ScRange& rExcept )
{
ScCellIterator aIter( rDoc, rRange );
for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next())
{
if (!aIter.isEmpty()) // real content?
{
if (!rExcept.Contains(aIter.GetPos()))
return false; // cell found
}
}
return true; // nothing found - empty
}
bool isEditable(ScDocShell& rDocShell, const ScRangeList& rRanges, bool bApi)
{
ScDocument& rDoc = rDocShell.GetDocument();
if (!rDocShell.IsEditable() || rDoc.GetChangeTrack())
{
// not recorded -> disallow
if (!bApi)
rDocShell.ErrorMessage(STR_PROTECTIONERR);
return false;
}
for (size_t i = 0, n = rRanges.size(); i < n; ++i)
{
const ScRange & r = rRanges[i];
ScEditableTester aTester(rDoc, r);
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false;
}
}
return true;
}
void createUndoDoc(ScDocumentUniquePtr& pUndoDoc, ScDocument& rDoc, const ScRange& rRange)
{
SCTAB nTab = rRange.aStart.Tab();
pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
pUndoDoc->InitUndo(rDoc, nTab, nTab);
rDoc.CopyToDocument(rRange, InsertDeleteFlags::ALL, false, *pUndoDoc);
}
bool checkNewOutputRange(ScDPObject& rDPObj, ScDocShell& rDocShell, ScRange& rNewOut, bool bApi)
{
ScDocument& rDoc = rDocShell.GetDocument();
bool bOverflow = false;
rNewOut = rDPObj.GetNewOutputRange(bOverflow);
// Test for overlap with source data range.
// TODO: Check with other pivot tables as well.
const ScSheetSourceDesc* pSheetDesc = rDPObj.GetSheetDesc();
if (pSheetDesc && pSheetDesc->GetSourceRange().Intersects(rNewOut))
{
// New output range intersteps with the source data. Move it up to
// where the old range is and see if that works.
ScRange aOldRange = rDPObj.GetOutRange();
SCROW nDiff = aOldRange.aStart.Row() - rNewOut.aStart.Row();
rNewOut.aStart.SetRow(aOldRange.aStart.Row());
rNewOut.aEnd.IncRow(nDiff);
if (!rDoc.ValidRow(rNewOut.aStart.Row()) || !rDoc.ValidRow(rNewOut.aEnd.Row()))
bOverflow = true;
}
if (bOverflow)
{
if (!bApi)
rDocShell.ErrorMessage(STR_PIVOT_ERROR);
return false;
}
ScEditableTester aTester(rDoc, rNewOut);
if (!aTester.IsEditable())
{
// destination area isn't editable
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false;
}
return true;
}
}
bool ScDBDocFunc::DataPilotUpdate( ScDPObject* pOldObj, const ScDPObject* pNewObj,
bool bRecord, bool bApi, bool bAllowMove )
{
if (!pOldObj)
{
if (!pNewObj)
return false;
return CreatePivotTable(*pNewObj, bRecord, bApi);
}
if (!pNewObj)
return RemovePivotTable(*pOldObj, bRecord, bApi);
if (pOldObj == pNewObj)
return UpdatePivotTable(*pOldObj, bRecord, bApi);
OSL_ASSERT(pOldObj && pNewObj && pOldObj != pNewObj);
ScDocShellModificator aModificator( rDocShell );
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
ScRangeList aRanges;
aRanges.push_back(pOldObj->GetOutRange());
aRanges.push_back(pNewObj->GetOutRange().aStart); // at least one cell in the output position must be editable.
if (!isEditable(rDocShell, aRanges, bApi))
return false;
ScDocumentUniquePtr pOldUndoDoc;
ScDocumentUniquePtr pNewUndoDoc;
ScDPObject aUndoDPObj(*pOldObj); // for undo or revert on failure
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
if (bRecord)
createUndoDoc(pOldUndoDoc, rDoc, pOldObj->GetOutRange());
pNewObj->WriteSourceDataTo(*pOldObj); // copy source data
ScDPSaveData* pData = pNewObj->GetSaveData();
OSL_ENSURE( pData, "no SaveData from living DPObject" );
if (pData)
pOldObj->SetSaveData(*pData); // copy SaveData
pOldObj->SetAllowMove(bAllowMove);
pOldObj->ReloadGroupTableData();
pOldObj->SyncAllDimensionMembers();
pOldObj->InvalidateData(); // before getting the new output area
// make sure the table has a name (not set by dialog)
if (pOldObj->GetName().isEmpty())
pOldObj->SetName( rDoc.GetDPCollection()->CreateNewName() );
ScRange aNewOut;
if (!checkNewOutputRange(*pOldObj, rDocShell, aNewOut, bApi))
{
*pOldObj = aUndoDPObj;
return false;
}
// test if new output area is empty except for old area
if (!bApi)
{
// OutRange of pOldObj (pDestObj) is still old area
if (!lcl_EmptyExcept(rDoc, aNewOut, pOldObj->GetOutRange()))
{
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Question, VclButtonsType::YesNo,
ScResId(STR_PIVOT_NOTEMPTY)));
xQueryBox->set_default_response(RET_YES);
if (xQueryBox->run() == RET_NO)
{
//! like above (not editable)
*pOldObj = aUndoDPObj;
return false;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
pOldObj->Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
if (bRecord)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDataPilot>(
&rDocShell, std::move(pOldUndoDoc), std::move(pNewUndoDoc), &aUndoDPObj, pOldObj, bAllowMove));
}
// notify API objects
rDoc.BroadcastUno( ScDataPilotModifiedHint(pOldObj->GetName()) );
aModificator.SetDocumentModified();
return true;
}
bool ScDBDocFunc::RemovePivotTable(const ScDPObject& rDPObj, bool bRecord, bool bApi)
{
ScDocShellModificator aModificator(rDocShell);
weld::WaitObject aWait(ScDocShell::GetActiveDialogParent());
if (!isEditable(rDocShell, rDPObj.GetOutRange(), bApi))
return false;
ScDocument& rDoc = rDocShell.GetDocument();
if (!bApi)
{
// If we come from GUI - ask to delete the associated pivot charts too...
std::vector<SdrOle2Obj*> aListOfObjects =
sc::tools::getAllPivotChartsConnectedTo(rDPObj.GetName(), &rDocShell);
ScDrawLayer* pModel = rDoc.GetDrawLayer();
if (pModel && !aListOfObjects.empty())
{
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Question, VclButtonsType::YesNo,
ScResId(STR_PIVOT_REMOVE_PIVOTCHART)));
xQueryBox->set_default_response(RET_YES);
if (xQueryBox->run() == RET_NO)
{
return false;
}
else
{
for (SdrOle2Obj* pChartObject : aListOfObjects)
{
rDoc.GetChartListenerCollection()->removeByName(pChartObject->GetName());
pModel->AddUndo(std::make_unique<SdrUndoDelObj>(*pChartObject));
pChartObject->getSdrPageFromSdrObject()->RemoveObject(pChartObject->GetOrdNum());
}
}
}
}
ScDocumentUniquePtr pOldUndoDoc;
std::unique_ptr<ScDPObject> pUndoDPObj;
if (bRecord)
pUndoDPObj.reset(new ScDPObject(rDPObj)); // copy old settings for undo
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
// delete table
ScRange aRange = rDPObj.GetOutRange();
SCTAB nTab = aRange.aStart.Tab();
if (bRecord)
createUndoDoc(pOldUndoDoc, rDoc, aRange);
rDoc.DeleteAreaTab( aRange.aStart.Col(), aRange.aStart.Row(),
aRange.aEnd.Col(), aRange.aEnd.Row(),
nTab, InsertDeleteFlags::ALL );
rDoc.RemoveFlagsTab( aRange.aStart.Col(), aRange.aStart.Row(),
aRange.aEnd.Col(), aRange.aEnd.Row(),
nTab, ScMF::Auto );
rDoc.GetDPCollection()->FreeTable(&rDPObj); // object is deleted here
rDocShell.PostPaintGridAll(); //! only necessary parts
rDocShell.PostPaint(aRange, PaintPartFlags::Grid);
if (bRecord)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDataPilot>(
&rDocShell, std::move(pOldUndoDoc), nullptr, pUndoDPObj.get(), nullptr, false));
// pUndoDPObj is copied
}
aModificator.SetDocumentModified();
return true;
}
bool ScDBDocFunc::CreatePivotTable(const ScDPObject& rDPObj, bool bRecord, bool bApi)
{
ScDocShellModificator aModificator(rDocShell);
weld::WaitObject aWait(ScDocShell::GetActiveDialogParent());
// At least one cell in the output range should be editable. Check in advance.
if (!isEditable(rDocShell, ScRange(rDPObj.GetOutRange().aStart), bApi))
return false;
ScDocumentUniquePtr pNewUndoDoc;
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
// output range must be set at pNewObj
std::unique_ptr<ScDPObject> pDestObj(new ScDPObject(rDPObj));
ScDPObject& rDestObj = *pDestObj;
// #i94570# When changing the output position in the dialog, a new table is created
// with the settings from the old table, including the name.
// So we have to check for duplicate names here (before inserting).
if (rDoc.GetDPCollection()->GetByName(rDestObj.GetName()))
rDestObj.SetName(OUString()); // ignore the invalid name, create a new name below
// Synchronize groups between linked tables
{
const ScDPDimensionSaveData* pGroups = nullptr;
bool bRefFound = rDoc.GetDPCollection()->GetReferenceGroups(rDestObj, &pGroups);
if (bRefFound)
{
ScDPSaveData* pSaveData = rDestObj.GetSaveData();
if (pSaveData)
pSaveData->SetDimensionData(pGroups);
}
}
rDoc.GetDPCollection()->InsertNewTable(std::move(pDestObj));
rDestObj.ReloadGroupTableData();
rDestObj.SyncAllDimensionMembers();
rDestObj.InvalidateData(); // before getting the new output area
// make sure the table has a name (not set by dialog)
if (rDestObj.GetName().isEmpty())
rDestObj.SetName(rDoc.GetDPCollection()->CreateNewName());
bool bOverflow = false;
ScRange aNewOut = rDestObj.GetNewOutputRange(bOverflow);
if (bOverflow)
{
if (!bApi)
rDocShell.ErrorMessage(STR_PIVOT_ERROR);
return false;
}
{
ScEditableTester aTester(rDoc, aNewOut);
if (!aTester.IsEditable())
{
// destination area isn't editable
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false;
}
}
// test if new output area is empty except for old area
if (!bApi)
{
bool bEmpty = rDoc.IsBlockEmpty(
aNewOut.aStart.Col(), aNewOut.aStart.Row(),
aNewOut.aEnd.Col(), aNewOut.aEnd.Row(), aNewOut.aStart.Tab() );
if (!bEmpty)
{
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Question, VclButtonsType::YesNo,
ScResId(STR_PIVOT_NOTEMPTY)));
xQueryBox->set_default_response(RET_YES);
if (xQueryBox->run() == RET_NO)
{
//! like above (not editable)
return false;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
rDestObj.Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
if (bRecord)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDataPilot>(&rDocShell, nullptr, std::move(pNewUndoDoc), nullptr, &rDestObj, false));
}
// notify API objects
rDoc.BroadcastUno(ScDataPilotModifiedHint(rDestObj.GetName()));
aModificator.SetDocumentModified();
return true;
}
bool ScDBDocFunc::UpdatePivotTable(ScDPObject& rDPObj, bool bRecord, bool bApi)
{
ScDocShellModificator aModificator( rDocShell );
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
if (!isEditable(rDocShell, rDPObj.GetOutRange(), bApi))
return false;
ScDocumentUniquePtr pOldUndoDoc;
ScDocumentUniquePtr pNewUndoDoc;
ScDPObject aUndoDPObj(rDPObj); // For undo or revert on failure.
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false;
if (bRecord)
createUndoDoc(pOldUndoDoc, rDoc, rDPObj.GetOutRange());
rDPObj.SetAllowMove(false);
rDPObj.ReloadGroupTableData();
if (!rDPObj.SyncAllDimensionMembers())
return false;
rDPObj.InvalidateData(); // before getting the new output area
// make sure the table has a name (not set by dialog)
if (rDPObj.GetName().isEmpty())
rDPObj.SetName( rDoc.GetDPCollection()->CreateNewName() );
ScRange aNewOut;
if (!checkNewOutputRange(rDPObj, rDocShell, aNewOut, bApi))
{
rDPObj = aUndoDPObj;
return false;
}
// test if new output area is empty except for old area
if (!bApi)
{
if (!lcl_EmptyExcept(rDoc, aNewOut, rDPObj.GetOutRange()))
{
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Question, VclButtonsType::YesNo,
ScResId(STR_PIVOT_NOTEMPTY)));
xQueryBox->set_default_response(RET_YES);
if (xQueryBox->run() == RET_NO)
{
rDPObj = aUndoDPObj;
return false;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
rDPObj.Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
if (bRecord)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDataPilot>(
&rDocShell, std::move(pOldUndoDoc), std::move(pNewUndoDoc), &aUndoDPObj, &rDPObj, false));
}
// notify API objects
rDoc.BroadcastUno( ScDataPilotModifiedHint(rDPObj.GetName()) );
aModificator.SetDocumentModified();
return true;
}
void ScDBDocFunc::RefreshPivotTables(const ScDPObject* pDPObj, bool bApi)
{
ScDPCollection* pDPs = rDocShell.GetDocument().GetDPCollection();
if (!pDPs)
return;
o3tl::sorted_vector<ScDPObject*> aRefs;
TranslateId pErrId = pDPs->ReloadCache(pDPObj, aRefs);
if (pErrId)
return;
for (ScDPObject* pObj : aRefs)
{
// This action is intentionally not undoable since it modifies cache.
UpdatePivotTable(*pObj, false, bApi);
}
}
void ScDBDocFunc::RefreshPivotTableGroups(ScDPObject* pDPObj)
{
if (!pDPObj)
return;
ScDPCollection* pDPs = rDocShell.GetDocument().GetDPCollection();
if (!pDPs)
return;
ScDPSaveData* pSaveData = pDPObj->GetSaveData();
if (!pSaveData)
return;
if (!pDPs->HasTable(pDPObj))
{
// This table is under construction so no need for a whole update (UpdatePivotTable()).
pDPObj->ReloadGroupTableData();
return;
}
// Update all linked tables, if this table is part of the cache (ScDPCollection)
o3tl::sorted_vector<ScDPObject*> aRefs;
if (!pDPs->ReloadGroupsInCache(pDPObj, aRefs))
return;
// We allow pDimData being NULL.
const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData();
for (ScDPObject* pObj : aRefs)
{
if (pObj != pDPObj)
{
pSaveData = pObj->GetSaveData();
if (pSaveData)
pSaveData->SetDimensionData(pDimData);
}
// This action is intentionally not undoable since it modifies cache.
UpdatePivotTable(*pObj, false, false);
}
}
// database import
void ScDBDocFunc::UpdateImport( const OUString& rTarget, const svx::ODataAccessDescriptor& rDescriptor )
{
// rTarget is the name of a database range
ScDocument& rDoc = rDocShell.GetDocument();
ScDBCollection& rDBColl = *rDoc.GetDBCollection();
const ScDBData* pData = rDBColl.getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rTarget));
if (!pData)
{
std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
VclMessageType::Info, VclButtonsType::Ok,
ScResId(STR_TARGETNOTFOUND)));
xInfoBox->run();
return;
}
SCTAB nTab;
SCCOL nDummyCol;
SCROW nDummyRow;
pData->GetArea( nTab, nDummyCol,nDummyRow,nDummyCol,nDummyRow );
ScImportParam aImportParam;
pData->GetImportParam( aImportParam );
OUString sDBName;
OUString sDBTable;
sal_Int32 nCommandType = 0;
sDBName = rDescriptor.getDataSource();
rDescriptor[svx::DataAccessDescriptorProperty::Command] >>= sDBTable;
rDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= nCommandType;
aImportParam.aDBName = sDBName;
aImportParam.bSql = ( nCommandType == sdb::CommandType::COMMAND );
aImportParam.aStatement = sDBTable;
aImportParam.bNative = false;
aImportParam.nType = static_cast<sal_uInt8>( ( nCommandType == sdb::CommandType::QUERY ) ? ScDbQuery : ScDbTable );
aImportParam.bImport = true;
bool bContinue = DoImport( nTab, aImportParam, &rDescriptor );
// repeat DB operations
ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();
if (!pViewSh)
return;
ScRange aRange;
pData->GetArea(aRange);
pViewSh->MarkRange(aRange); // select
if ( bContinue ) // error at import -> abort
{
// internal operations, if some are saved
if ( pData->HasQueryParam() || pData->HasSortParam() || pData->HasSubTotalParam() )
pViewSh->RepeatDB();
// pivot tables which have the range as source data
rDocShell.RefreshPivotTables(aRange);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */