1796 lines
64 KiB
C++
1796 lines
64 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.aGroups[0].bActive && !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 = ScModule::get()->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 (SfxViewShell* pKitSomeViewForThisDoc = comphelper::LibreOfficeKit::isActive() ?
|
|
rDocShell.GetBestViewShell(false) : nullptr)
|
|
{
|
|
SfxViewShell* pViewShell = SfxViewShell::GetFirst();
|
|
while (pViewShell)
|
|
{
|
|
ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
|
|
if (pTabViewShell && pTabViewShell->GetDocId() == pKitSomeViewForThisDoc->GetDocId())
|
|
{
|
|
if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab))
|
|
pPosHelper->invalidateByIndex(nStartRow);
|
|
}
|
|
pViewShell = SfxViewShell::GetNext(*pViewShell);
|
|
}
|
|
|
|
ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
|
|
pKitSomeViewForThisDoc, 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.aGroups[0].bActive && !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,
|
|
sc::EditAction eAction = sc::EditAction::Unknown)
|
|
{
|
|
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, eAction);
|
|
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,
|
|
sc::EditAction eAction = sc::EditAction::Unknown)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (!rDoc.IsImportingXML())
|
|
{
|
|
ScEditableTester aTester(rDoc, rNewOut, eAction);
|
|
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(ScRange(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.
|
|
ScDocument& rDoc = rDocShell.GetDocument();
|
|
if (!rDoc.IsImportingXML() && !isEditable(rDocShell, ScRange(rDPObj.GetOutRange().aStart), bApi))
|
|
return false;
|
|
|
|
ScDocumentUniquePtr pNewUndoDoc;
|
|
|
|
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;
|
|
}
|
|
|
|
if (!rDoc.IsImportingXML())
|
|
{
|
|
ScEditableTester aTester(rDoc, aNewOut, sc::EditAction::Unknown);
|
|
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, sc::EditAction::UpdatePivotTable))
|
|
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, sc::EditAction::UpdatePivotTable))
|
|
{
|
|
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: */
|