/* -*- 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 .
*/
// #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();
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();
// 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 );
}
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);
// 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()); returnfalse;
}
// 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));
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); returnfalse;
}
// 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);
// 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);
}
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();
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
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;
// 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)
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())) returnfalse; // cell found
}
}
returntrue; // 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);
returnfalse;
}
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());
// 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);
returnfalse;
}
if (!rDoc.IsImportingXML())
{
ScEditableTester aTester(rDoc, rNewOut, eAction); if (!aTester.IsEditable())
{ // destination area isn't editable if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
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)) returnfalse;
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() );
// 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; returnfalse;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
pOldObj->Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
if (!isEditable(rDocShell, rDPObj.GetOutRange(), bApi)) returnfalse;
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);
// 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)) returnfalse;
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);
}
}
if (bOverflow)
{ if (!bApi)
rDocShell.ErrorMessage(STR_PIVOT_ERROR);
returnfalse;
}
if (!rDoc.IsImportingXML())
{
ScEditableTester aTester(rDoc, aNewOut, sc::EditAction::Unknown); if (!aTester.IsEditable())
{ // destination area isn't editable if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
returnfalse;
}
}
// 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) returnfalse;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
rDestObj.Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
// 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; returnfalse;
}
}
}
if (bRecord)
createUndoDoc(pNewUndoDoc, rDoc, aNewOut);
rDPObj.Output(aNewOut.aStart);
rDocShell.PostPaintGridAll(); //! only necessary parts
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
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.