/* -*- 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 .
*/
usingnamespace com::sun::star; using ::com::sun::star::uno::Any; using ::com::sun::star::uno::Sequence; using ::com::sun::star::uno::Reference; using ::com::sun::star::uno::UNO_QUERY; using ::com::sun::star::beans::XPropertySet; using ::com::sun::star::container::XNameAccess; using ::com::sun::star::sheet::XDimensionsSupplier; using ::std::vector;
// record data range - including filter results
rDoc.CopyToDocument( 0,rParam.nRow1+1,nTab, rDoc.MaxCol(),rParam.nRow2,nTab,
InsertDeleteFlags::ALL, false, *pUndoDoc );
// all formulas for reference
rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1,
InsertDeleteFlags::FORMULA, false, *pUndoDoc );
// database and other ranges
ScRangeName* pDocRange = rDoc.GetRangeName(); if (!pDocRange->empty())
pUndoRange.reset(new ScRangeName( *pDocRange ));
ScDBCollection* pDocDB = rDoc.GetDBCollection(); if (!pDocDB->empty())
pUndoDB.reset(new ScDBCollection( *pDocDB ));
}
ScOutlineTable* pOut = rDoc.GetOutlineTable( nTab ); if (pOut)
{ // Remove all existing outlines in the specified range.
ScOutlineArray& rRowArray = pOut->GetRowArray();
sal_uInt16 nDepth = rRowArray.GetDepth(); for (sal_uInt16 i = 0; i < nDepth; ++i)
{ bool bSize;
rRowArray.Remove(aNewParam.nRow1, aNewParam.nRow2, bSize);
}
}
if (rParam.bReplace)
rDoc.RemoveSubTotals( nTab, aNewParam ); bool bSuccess = true; if (bDo)
{ // Sort if ( rParam.bDoSort || pForceNewSort )
{
pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 );
// set subtotal fields before sorting // (duplicate values are dropped, so that they can be called again)
bool ScDBFunc::MakePivotTable( const ScDPSaveData& rData, const ScRange& rDest, bool bNewTable, const ScDPObject& rSource )
{ // error message if no fields are set // this must be removed when drag&drop of fields from a toolbox is available
if ( rData.IsEmpty() )
{
ErrorMessage(STR_PIVOT_NODATA); returnfalse;
}
ScDPObject aObj( rSource );
aObj.SetOutRange( aDestRange ); if ( pDPObj && !rData.GetExistingDimensionData() )
{ // copy dimension data from old object - lost in the dialog //! change the dialog to keep the dimension data
ScDPObject* pDPObj = rDoc.GetDPAtCursor( GetViewData().GetCurX(),
GetViewData().GetCurY(),
GetViewData().GetTabNo() ); if (pDPObj)
{ // Remove existing data cache for the data that this datapilot uses, // to force re-build data cache.
ScDBDocFunc aFunc(rDocSh);
aFunc.RefreshPivotTables(pDPObj, false);
CursorPosChanged(); // shells may be switched
} else
ErrorMessage(STR_PIVOT_NOTFOUND);
}
for (SCROW nRow=nStartRow; nRow<=nEndRow && bContinue; nRow++) for (SCCOL nCol=nStartCol; nCol<=nEndCol && bContinue; nCol++)
{
sheet::DataPilotTableHeaderData aData;
pDPObj->GetHeaderPositionData(ScAddress(nCol, nRow, nTab), aData); if ( aData.Dimension < 0 )
bContinue = false; // not part of any dimension else
{ if ( nStartDimension < 0 ) // first member?
{
nStartDimension = aData.Dimension;
nStartHierarchy = aData.Hierarchy;
nStartLevel = aData.Level;
} if ( aData.Dimension != nStartDimension ||
aData.Hierarchy != nStartHierarchy ||
aData.Level != nStartLevel )
{
bContinue = false; // cannot mix dimensions
}
} if ( bContinue )
{ // accept any part of a member description, also subtotals, // but don't stop if empty parts are contained if ( aData.Flags & sheet::MemberResultFlags::HASMEMBER )
rEntries.insert(aData.MemberName);
}
}
}
rDimension = nStartDimension; // dimension from which the found members came if (!bContinue)
rEntries.clear(); // remove all if not valid
}
bool ScDBFunc::HasSelectionForDateGroup( ScDPNumGroupInfo& rOldInfo, sal_Int32& rParts )
{ // determine if the date group dialog has to be shown for the current selection
ScDPDimensionSaveData* pDimData = const_cast<ScDPDimensionSaveData*>( pDPObj->GetSaveData()->GetExistingDimensionData() ); if ( pDimData )
{ const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); const ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDim( aDimName ); if ( pNumGroupDim )
{ // existing num group dimension
if ( pNumGroupDim->GetDatePart() != 0 )
{ // dimension has date info -> edit settings of this dimension // (parts are collected below)
rOldInfo = pNumGroupDim->GetDateInfo();
bFound = true;
} elseif ( pNumGroupDim->GetInfo().mbDateValues )
{ // Numerical grouping with DateValues flag is used for grouping // of days with a "Number of days" value.
rOldInfo = pNumGroupDim->GetInfo();
rParts = css::sheet::DataPilotFieldGroupBy::DAYS; // not found in CollectDateParts
bFoundParts = true;
bFound = true;
}
bInGroupDim = true;
} elseif ( pGroupDim )
{ // existing additional group dimension
if ( pGroupDim->GetDatePart() != 0 )
{ // dimension has date info -> edit settings of this dimension // (parts are collected below)
rOldInfo = pGroupDim->GetDateInfo();
aBaseDimName = pGroupDim->GetSourceDimName();
bFound = true;
}
bInGroupDim = true;
}
} if ( bFound && !bFoundParts )
{ // collect date parts from all group dimensions
rParts = pDimData->CollectDateParts( aBaseDimName );
} if ( !bFound && !bInGroupDim )
{ // create new date group dimensions if the selection is a single cell // in a normal dimension with date content
bool ScDBFunc::HasSelectionForNumGroup( ScDPNumGroupInfo& rOldInfo )
{ // determine if the numeric group dialog has to be shown for the current selection
ScDPDimensionSaveData* pDimData = const_cast<ScDPDimensionSaveData*>( pDPObj->GetSaveData()->GetExistingDimensionData() ); if ( pDimData )
{ const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); if ( pNumGroupDim )
{ // existing num group dimension // -> edit settings of this dimension
rOldInfo = pNumGroupDim->GetInfo();
bFound = true;
} elseif ( pDimData->GetNamedGroupDim( aDimName ) )
bInGroupDim = true; // in a group dimension
} if ( !bFound && !bInGroupDim )
{ // create a new num group dimension if the selection is a single cell // in a normal dimension with numeric content
ScRange aSelRange; if ( (GetViewData().GetSimpleArea( aSelRange ) == SC_MARK_SIMPLE) &&
aSelRange.aStart == aSelRange.aEnd )
{ if ( rDoc.HasValueData( aSelRange.aStart.Col(), aSelRange.aStart.Row(),
aSelRange.aStart.Tab() ) )
{
bFound = true; // use currently selected value for automatic limits if( rOldInfo.mbAutoStart )
rOldInfo.mfStart = rDoc.GetValue( aSelRange.aStart ); if( rOldInfo.mbAutoEnd )
rOldInfo.mfEnd = rDoc.GetValue( aSelRange.aStart );
}
}
}
}
}
// Remove all group dimensions associated with this source dimension. For // date grouping, we need to remove all existing groups for the affected // source dimension and build new one(s) from scratch. Keep the deleted // names so that they can be reused during re-construction.
aData.RemoveAllGroupDimensions(aBaseDimName, &aDeletedNames);
if ( nParts )
{ // create date group dimensions
bool bFirst = true;
sal_Int32 nMask = 1; for (sal_uInt16 nBit=0; nBit<32; nBit++)
{ if ( nParts & nMask )
{ if ( bFirst )
{ // innermost part: create NumGroupDimension (replacing original values) // Dimension name is left unchanged
if ( (nParts == sheet::DataPilotFieldGroupBy::DAYS) && (rInfo.mfStep >= 1.0) )
{ // only days, and a step value specified: use numerical grouping // with DateValues flag, not date grouping
ScDPSaveData aData( *pDPObj->GetSaveData() );
ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); // created if not there
// find original base
OUString aBaseDimName = aDimName; const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName ); if ( pBaseGroupDim )
{ // any entry's SourceDimName is the original base
aBaseDimName = pBaseGroupDim->GetSourceDimName();
}
// find existing group dimension // (using the selected dim, can be intermediate group dim)
ScDPSaveGroupDimension* pGroupDimension = pDimData->GetGroupDimAccForBase( aDimName );
// remove the selected items from their groups // (empty groups are removed, too) if ( pGroupDimension )
{ for (const OUString& aEntryName : aEntries)
{ if ( pBaseGroupDim )
{ // for each selected (intermediate) group, remove all its items // (same logic as for adding, below) const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName ); if ( pBaseGroup )
pBaseGroup->RemoveElementsFromGroups( *pGroupDimension ); // remove all elements else
pGroupDimension->RemoveFromGroups( aEntryName );
} else
pGroupDimension->RemoveFromGroups( aEntryName );
}
}
std::unique_ptr<ScDPSaveGroupDimension> pNewGroupDim; if ( !pGroupDimension )
{ // create a new group dimension
OUString aGroupDimName =
pDimData->CreateGroupDimName(aBaseDimName, *pDPObj, false, nullptr);
pNewGroupDim.reset(new ScDPSaveGroupDimension( aBaseDimName, aGroupDimName ));
pGroupDimension = pNewGroupDim.get(); // make changes to the new dim if none existed
if ( pBaseGroupDim )
{ // If it's a higher-order group dimension, pre-allocate groups for all // non-selected original groups, so the individual base members aren't // used for automatic groups (this would make the original groups hard // to find). //! Also do this when removing groups? //! Handle this case dynamically with automatic groups?
if (!aEntries.count(rBaseGroup.GetGroupName()))
{ // add an additional group for each item that is not in the selection
ScDPSaveGroupItem aGroup( rBaseGroup.GetGroupName() );
aGroup.AddElementsFromGroup( rBaseGroup );
pGroupDimension->AddGroupItem( aGroup );
}
}
}
}
OUString aGroupDimName = pGroupDimension->GetGroupDimName();
ScDPSaveGroupItem aGroup(pGroupDimension->CreateGroupName(ScResId(STR_PIVOT_GROUP))); for (const OUString& aEntryName : aEntries)
{ if ( pBaseGroupDim )
{ // for each selected (intermediate) group, add all its items const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName ); if ( pBaseGroup )
aGroup.AddElementsFromGroup( *pBaseGroup ); else
aGroup.AddElement( aEntryName ); // no group found -> automatic group, add the item itself
} else
aGroup.AddElement( aEntryName ); // no group dimension, add all items directly
}
pGroupDimension->AddGroupItem( aGroup );
if ( pNewGroupDim )
{
pDimData->AddGroupDimension( *pNewGroupDim );
pNewGroupDim.reset(); // AddGroupDimension copies the object // don't access pGroupDimension after here
}
pGroupDimension = nullptr;
// set orientation
ScDPSaveDimension* pSaveDimension = aData.GetDimensionByName( aGroupDimName ); if ( pSaveDimension->GetOrientation() == sheet::DataPilotFieldOrientation_HIDDEN )
{
ScDPSaveDimension* pOldDimension = aData.GetDimensionByName( aDimName );
pSaveDimension->SetOrientation( pOldDimension->GetOrientation() );
aData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base
}
ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName ); const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); if ( ( pGroupDim && pGroupDim->GetDatePart() != 0 ) ||
( pNumGroupDim && pNumGroupDim->GetDatePart() != 0 ) )
{ // Date grouping: need to remove all affected group dimensions. // This is done using DateGroupDataPilot with nParts=0.
if ( pGroupDim )
{ for (constauto& rEntry : aEntries)
pGroupDim->RemoveGroup(rEntry);
// remove group dimension if empty bool bEmptyDim = pGroupDim->IsEmpty(); if ( !bEmptyDim )
{ // If all remaining groups in the dimension aren't shown, remove // the dimension too, as if it was completely empty.
ScDPUniqueStringSet aVisibleEntries;
pDPObj->GetMemberResultNames( aVisibleEntries, nSelectDimension );
bEmptyDim = pGroupDim->HasOnlyHidden( aVisibleEntries );
} if ( bEmptyDim )
{
pDimData->RemoveGroupDimension( aDimName ); // pGroupDim is deleted
// also remove SaveData settings for the dimension that no longer exists
aData.RemoveDimensionByName( aDimName );
}
} elseif ( pNumGroupDim )
{ // remove the numerical grouping
pDimData->RemoveNumGroupDimension( aDimName ); // SaveData settings can remain unchanged - the same dimension still exists
}
DataPilotFieldOrientation nOrient = DataPilotFieldOrientation_HIDDEN;
tools::Long nField = pDPObj->GetHeaderDim( rPos, nOrient ); if ( nField >= 0 )
{ // changing a field title if ( aData.GetExistingDimensionData() )
{ // only group dimensions can be renamed
ScDPDimensionSaveData* pDimData = aData.GetDimensionData();
ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aOldText ); if ( pGroupDim )
{ // valid name: not empty, no existing dimension (group or other) if (!rString.isEmpty() && !pDPObj->IsDimNameInUse(rString))
{
pGroupDim->Rename( rString );
// also rename in SaveData to preserve the field settings
ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aOldText );
pSaveDim->SetName( rString );
bChange = true;
} else
pErrorId = STR_INVALIDNAME;
}
} elseif (nOrient == DataPilotFieldOrientation_COLUMN || nOrient == DataPilotFieldOrientation_ROW)
{ bool bDataLayout = false;
OUString aDimName = pDPObj->GetDimName(nField, bDataLayout);
ScDPSaveDimension* pDim = bDataLayout ? aData.GetDataLayoutDimension() : aData.GetDimensionByName(aDimName); if (pDim)
{ if (!rString.isEmpty())
{ if (rString.equalsIgnoreAsciiCase(aDimName))
{
pDim->RemoveLayoutName();
bChange = true;
} elseif (!pDPObj->IsDimNameInUse(rString))
{
pDim->SetLayoutName(rString);
bChange = true;
} else
pErrorId = STR_INVALIDNAME;
} else
pErrorId = STR_INVALIDNAME;
}
}
} elseif (pDPObj->IsDataDescriptionCell(rPos))
{ // There is only one data dimension.
ScDPSaveDimension* pDim = aData.GetFirstDimension(sheet::DataPilotFieldOrientation_DATA); if (pDim)
{ if (!rString.isEmpty())
{ if (pDim->GetName().equalsIgnoreAsciiCase(rString))
{
pDim->RemoveLayoutName();
bChange = true;
} elseif (!pDPObj->IsDimNameInUse(rString))
{
pDim->SetLayoutName(rString);
bChange = true;
} else
pErrorId = STR_INVALIDNAME;
} else
pErrorId = STR_INVALIDNAME;
}
} else
{ // This is not a field header.
sheet::DataPilotTableHeaderData aPosData;
pDPObj->GetHeaderPositionData(rPos, aPosData);
ScDPDimensionSaveData* pDimData = aData.GetDimensionData();
ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName ); if ( pGroupDim )
{ // valid name: not empty, no existing group in this dimension //! ignore case? if (!rString.isEmpty() && !pGroupDim->GetNamedGroup(rString))
{
ScDPSaveGroupItem* pGroup = pGroupDim->GetNamedGroupAcc( aOldText ); if ( pGroup )
pGroup->Rename( rString ); // rename the existing group else
{ // create a new group to replace the automatic group
ScDPSaveGroupItem aGroup( rString );
aGroup.AddElement( aOldText );
pGroupDim->AddGroupItem( aGroup );
}
// in both cases also adjust savedata, to preserve member settings (show details)
ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aDimName );
ScDPSaveMember* pSaveMember = pSaveDim->GetExistingMemberByName( aOldText ); if ( pSaveMember )
pSaveMember->SetName( rString );
ScDPSaveDimension* pDim = aData.GetDimensionByName(aPosData.MemberName); if (!pDim) break;
if (rString.isEmpty())
{
pErrorId = STR_INVALIDNAME; break;
}
if (aPosData.MemberName.equalsIgnoreAsciiCase(rString))
{
pDim->RemoveLayoutName();
bChange = true;
} elseif (!pDPObj->IsDimNameInUse(rString))
{
pDim->SetLayoutName(rString);
bChange = true;
} else
pErrorId = STR_INVALIDNAME;
} while (false);
} else
{ // field member do
{
ScDPSaveDimension* pDim = aData.GetDimensionByName(aDimName); if (!pDim) break;
ScDPSaveMember* pMem = pDim->GetExistingMemberByName(aPosData.MemberName); if (!pMem) break;
if (aPosData.Flags & MemberResultFlags::SUBTOTAL)
{ // Change subtotal only when the table has one data dimension. if (aData.GetDataDimensionCount() > 1) break;
// display name for subtotal is allowed only if the subtotal type is 'Automatic'. if (pDim->GetSubTotalsCount() != 1) break;
if (pDim->GetSubTotalFunc(0) != ScGeneralFunction::AUTO) break;
OUString aNew = lcl_replaceMemberNameInSubtotal(rString, aMemberName);
pDim->SetSubtotalName(aNew);
bChange = true;
} else
{ // Check to make sure the member name isn't // already used. if (!rString.isEmpty())
{ if (rString.equalsIgnoreAsciiCase(pMem->GetName()))
{
pMem->RemoveLayoutName();
bChange = true;
} elseif (!pDim->IsMemberNameInUse(rString))
{
pMem->SetLayoutName(rString);
bChange = true;
} else
pErrorId = STR_INVALIDNAME;
} else
pErrorId = STR_INVALIDNAME;
}
} while (false);
}
}
}
}
if ( bChange )
{ // apply changes
ScDBDocFunc aFunc( GetViewData().GetDocShell() );
pDPObj->SetSaveData( aData ); if (bNeedReloadGroups)
{
ScDPCollection* pDPs = rDoc.GetDPCollection(); if (pDPs)
{
o3tl::sorted_vector<ScDPObject*> aRefs; // tdf#111305: Reload groups in cache after modifications.
pDPs->ReloadGroupsInCache(pDPObj, aRefs);
} // pDPs
} // bNeedReloadGroups
aFunc.UpdatePivotTable(*pDPObj, true, false);
} else
{ if (!pErrorId)
pErrorId = STR_ERR_DATAPILOT_INPUT;
ErrorMessage(pErrorId);
}
}
staticvoid lcl_MoveToEnd( ScDPSaveDimension& rDim, const OUString& rItemName )
{
std::unique_ptr<ScDPSaveMember> pNewMember; const ScDPSaveMember* pOldMember = rDim.GetExistingMemberByName( rItemName ); if ( pOldMember )
pNewMember.reset(new ScDPSaveMember( *pOldMember )); else
pNewMember.reset(new ScDPSaveMember( rItemName ));
rDim.AddMember( std::move(pNewMember) ); // AddMember takes ownership of the new pointer, // puts it to the end of the list even if it was in the list before.
}
// manual evaluation of sort order is only needed if a user list id is given if ( pUserListId )
{ typedef ScDPSaveDimension::MemberList MemList; const MemList& rDimMembers = pSaveDim->GetMembers();
vector<OUString> aMembers;
std::unordered_set<OUString> aMemberSet;
size_t nMemberCount = 0; for (ScDPSaveMember* pMem : rDimMembers)
{
aMembers.push_back(pMem->GetName());
aMemberSet.insert(pMem->GetName());
++nMemberCount;
}
// Sort the member list in ascending order.
ScOUStringCollate aCollate( &ScGlobal::GetCollator() );
std::stable_sort(aMembers.begin(), aMembers.end(), aCollate);
// Collect and rank those custom sort strings that also exist in the member name list.
const ScUserListData& rData = rUserList[*pUserListId];
sal_uInt16 n = rData.GetSubCount(); for (sal_uInt16 i = 0; i < n; ++i)
{
OUString aSub = rData.GetSubStr(i); if (!aMemberSet.count(aSub)) // This string doesn't exist in the member name set. Don't add this. continue;
// Re-order ScDPSaveMember instances with the new ranks. for (autoconst& aRankedName : aRankedNames)
{ const ScDPSaveMember* pOldMem = pSaveDim->GetExistingMemberByName(aRankedName); if (!pOldMem) // All members are supposed to be present. continue;
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.