1566 lines
57 KiB
C++
1566 lines
57 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*************************************************************************
|
|
*
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* Copyright 2000, 2010 Oracle and/or its affiliates.
|
|
*
|
|
* OpenOffice.org - a multi-platform office productivity suite
|
|
*
|
|
* This file is part of OpenOffice.org.
|
|
*
|
|
* OpenOffice.org is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License version 3
|
|
* only, as published by the Free Software Foundation.
|
|
*
|
|
* OpenOffice.org is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License version 3 for more details
|
|
* (a copy is included in the LICENSE file that accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* version 3 along with OpenOffice.org. If not, see
|
|
* <http://www.openoffice.org/license.html>
|
|
* for a copy of the LGPLv3 License.
|
|
*
|
|
************************************************************************/
|
|
|
|
// MARKER(update_precomp.py): autogen include statement, do not remove
|
|
#include "precompiled_sc.hxx"
|
|
|
|
|
|
|
|
// INCLUDE ---------------------------------------------------------------
|
|
|
|
#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
|
|
|
|
#include <rtl/math.hxx>
|
|
#include <unotools/localedatawrapper.hxx>
|
|
#include <svl/zforlist.hxx>
|
|
|
|
#include "dpgroup.hxx"
|
|
#include "collect.hxx"
|
|
#include "global.hxx"
|
|
#include "document.hxx"
|
|
#include "dpcachetable.hxx"
|
|
#include "dptabsrc.hxx"
|
|
#include "dptabres.hxx"
|
|
#include "dpobject.hxx"
|
|
#include "dpglobal.hxx"
|
|
|
|
#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
|
|
#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
|
|
|
|
#include <vector>
|
|
#include <boost/unordered_map.hpp>
|
|
#include <boost/unordered_set.hpp>
|
|
|
|
using namespace ::com::sun::star;
|
|
using ::com::sun::star::uno::Any;
|
|
using ::com::sun::star::uno::Reference;
|
|
using ::com::sun::star::uno::Sequence;
|
|
using ::com::sun::star::uno::UNO_QUERY;
|
|
using ::com::sun::star::uno::UNO_QUERY_THROW;
|
|
using ::rtl::OUString;
|
|
using ::rtl::OUStringHash;
|
|
|
|
using ::std::vector;
|
|
using ::boost::shared_ptr;
|
|
|
|
#define D_TIMEFACTOR 86400.0
|
|
|
|
const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
|
|
|
|
// part values for the extra "<" and ">" entries (same for all parts)
|
|
const sal_Int32 SC_DP_DATE_FIRST = -1;
|
|
const sal_Int32 SC_DP_DATE_LAST = 10000;
|
|
|
|
// ============================================================================
|
|
namespace
|
|
{
|
|
sal_Bool lcl_Search( SCCOL nSourceDim, const ScDPCache* pCache , const std::vector< SCROW >& vIdx, SCROW nNew , SCROW& rIndex)
|
|
{
|
|
rIndex = vIdx.size();
|
|
sal_Bool bFound = false;
|
|
SCROW nLo = 0;
|
|
SCROW nHi = vIdx.size() - 1;
|
|
SCROW nIndex;
|
|
long nCompare;
|
|
while (nLo <= nHi)
|
|
{
|
|
nIndex = (nLo + nHi) / 2;
|
|
|
|
const ScDPItemData* pData = pCache->GetItemDataById( nSourceDim, vIdx[nIndex] );
|
|
const ScDPItemData* pDataInsert = pCache->GetItemDataById( nSourceDim, nNew );
|
|
|
|
nCompare = ScDPItemData::Compare( *pData, *pDataInsert );
|
|
if (nCompare < 0)
|
|
nLo = nIndex + 1;
|
|
else
|
|
{
|
|
nHi = nIndex - 1;
|
|
if (nCompare == 0)
|
|
{
|
|
bFound = sal_True;
|
|
nLo = nIndex;
|
|
}
|
|
}
|
|
}
|
|
rIndex = nLo;
|
|
return bFound;
|
|
}
|
|
|
|
void lcl_Insert( SCCOL nSourceDim, const ScDPCache* pCache , std::vector< SCROW >& vIdx, SCROW nNew )
|
|
{
|
|
SCROW nIndex = 0;
|
|
if ( !lcl_Search( nSourceDim, pCache, vIdx, nNew ,nIndex ) )
|
|
vIdx.insert( vIdx.begin()+nIndex, nNew );
|
|
}
|
|
|
|
template<bool bUpdateData>
|
|
SCROW lcl_InsertValue(SCCOL nSourceDim, const ScDPCache* pCache, std::vector<SCROW>& vIdx, const ScDPItemData & rData);
|
|
|
|
template<>
|
|
SCROW lcl_InsertValue<false>(SCCOL nSourceDim, const ScDPCache* pCache, std::vector<SCROW>& vIdx, const ScDPItemData & rData)
|
|
{
|
|
SCROW nNewID = pCache->GetAdditionalItemID(rData);
|
|
lcl_Insert(nSourceDim, pCache, vIdx, nNewID);
|
|
return nNewID;
|
|
}
|
|
|
|
template<>
|
|
SCROW lcl_InsertValue<true>(SCCOL nSourceDim, const ScDPCache* pCache, std::vector<SCROW>& vIdx, const ScDPItemData & rData)
|
|
{
|
|
SCROW nItemId = lcl_InsertValue<false>( nSourceDim, pCache, vIdx, rData );
|
|
|
|
if( const ScDPItemData *pData = pCache->GetItemDataById( nSourceDim, nItemId ) )
|
|
const_cast<ScDPItemData&>(*pData) = rData;
|
|
|
|
return nItemId;
|
|
}
|
|
|
|
template<bool bUpdateData>
|
|
void lcl_InsertValue ( SCCOL nSourceDim, const ScDPCache* pCache, std::vector< SCROW >& vIdx, const String& rString, const double& fValue )
|
|
{
|
|
lcl_InsertValue<bUpdateData>( nSourceDim, pCache, vIdx, ScDPItemData( rString, fValue, sal_True ) );
|
|
}
|
|
|
|
template<bool bUpdateData>
|
|
void lcl_InsertValue ( SCCOL nSourceDim, const ScDPCache* pCache, std::vector< SCROW >& vIdx, const String& rString, const double& fValue, sal_Int32 nDatePart )
|
|
{
|
|
lcl_InsertValue<bUpdateData>( nSourceDim, pCache, vIdx, ScDPItemData( nDatePart, rString, fValue, ScDPItemData::MK_DATA|ScDPItemData::MK_VAL|ScDPItemData::MK_DATEPART ) );
|
|
}
|
|
|
|
void lcl_AppendDateStr( rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
|
|
{
|
|
sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
|
|
String aString;
|
|
pFormatter->GetInputLineString( fValue, nFormat, aString );
|
|
rBuffer.append( aString );
|
|
}
|
|
|
|
String lcl_GetNumGroupName( double fStartValue, const ScDPNumGroupInfo& rInfo,
|
|
bool bHasNonInteger, sal_Unicode cDecSeparator, SvNumberFormatter* pFormatter )
|
|
{
|
|
OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
|
|
|
|
double fStep = rInfo.Step;
|
|
double fEndValue = fStartValue + fStep;
|
|
if ( !bHasNonInteger && ( rInfo.DateValues || !rtl::math::approxEqual( fEndValue, rInfo.End ) ) )
|
|
{
|
|
// The second number of the group label is
|
|
// (first number + size - 1) if there are only integer numbers,
|
|
// (first number + size) if any non-integer numbers are involved.
|
|
// Exception: The last group (containing the end value) is always
|
|
// shown as including the end value (but not for dates).
|
|
|
|
fEndValue -= 1.0;
|
|
}
|
|
|
|
if ( fEndValue > rInfo.End && !rInfo.AutoEnd )
|
|
{
|
|
// limit the last group to the end value
|
|
|
|
fEndValue = rInfo.End;
|
|
}
|
|
|
|
rtl::OUStringBuffer aBuffer;
|
|
if ( rInfo.DateValues )
|
|
{
|
|
lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
|
|
aBuffer.appendAscii( " - " ); // with spaces
|
|
lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
|
|
}
|
|
else
|
|
{
|
|
rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
|
|
rtl_math_DecimalPlaces_Max, cDecSeparator, true );
|
|
aBuffer.append( (sal_Unicode) '-' );
|
|
rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
|
|
rtl_math_DecimalPlaces_Max, cDecSeparator, true );
|
|
}
|
|
|
|
return aBuffer.makeStringAndClear();
|
|
}
|
|
|
|
String lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
|
|
bool bDateValues, SvNumberFormatter* pFormatter )
|
|
{
|
|
OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
|
|
|
|
rtl::OUStringBuffer aBuffer;
|
|
aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
|
|
if ( bDateValues )
|
|
lcl_AppendDateStr( aBuffer, fValue, pFormatter );
|
|
else
|
|
rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
|
|
rtl_math_DecimalPlaces_Max, cDecSeparator, true );
|
|
return aBuffer.makeStringAndClear();
|
|
}
|
|
|
|
inline bool IsInteger( double fValue )
|
|
{
|
|
return rtl::math::approxEqual( fValue, rtl::math::approxFloor(fValue) );
|
|
}
|
|
|
|
String lcl_GetNumGroupForValue( double fValue, const ScDPNumGroupInfo& rInfo, bool bHasNonInteger,
|
|
sal_Unicode cDecSeparator, double& rGroupValue, ScDocument* pDoc )
|
|
{
|
|
SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
|
|
|
|
if ( fValue < rInfo.Start && !rtl::math::approxEqual( fValue, rInfo.Start ) )
|
|
{
|
|
rGroupValue = rInfo.Start - rInfo.Step;
|
|
return lcl_GetSpecialNumGroupName( rInfo.Start, true, cDecSeparator, rInfo.DateValues, pFormatter );
|
|
}
|
|
|
|
if ( fValue > rInfo.End && !rtl::math::approxEqual( fValue, rInfo.End ) )
|
|
{
|
|
rGroupValue = rInfo.End + rInfo.Step;
|
|
return lcl_GetSpecialNumGroupName( rInfo.End, false, cDecSeparator, rInfo.DateValues, pFormatter );
|
|
}
|
|
|
|
double fDiff = fValue - rInfo.Start;
|
|
double fDiv = rtl::math::approxFloor( fDiff / rInfo.Step );
|
|
double fGroupStart = rInfo.Start + fDiv * rInfo.Step;
|
|
|
|
if ( rtl::math::approxEqual( fGroupStart, rInfo.End ) &&
|
|
!rtl::math::approxEqual( fGroupStart, rInfo.Start ) )
|
|
{
|
|
if ( !rInfo.DateValues )
|
|
{
|
|
// A group that would consist only of the end value is not created,
|
|
// instead the value is included in the last group before. So the
|
|
// previous group is used if the calculated group start value is the
|
|
// selected end value.
|
|
|
|
fDiv -= 1.0;
|
|
fGroupStart = rInfo.Start + fDiv * rInfo.Step;
|
|
}
|
|
else
|
|
{
|
|
// For date values, the end value is instead treated as above the limit
|
|
// if it would be a group of its own.
|
|
|
|
rGroupValue = rInfo.End + rInfo.Step;
|
|
return lcl_GetSpecialNumGroupName( rInfo.End, false, cDecSeparator, rInfo.DateValues, pFormatter );
|
|
}
|
|
}
|
|
|
|
rGroupValue = fGroupStart;
|
|
|
|
return lcl_GetNumGroupName( fGroupStart, rInfo, bHasNonInteger, cDecSeparator, pFormatter );
|
|
}
|
|
}
|
|
|
|
class ScDPGroupDateFilter : public ScDPCacheTable::FilterBase
|
|
{
|
|
public:
|
|
ScDPGroupDateFilter(double fMatchValue, sal_Int32 nDatePart,
|
|
const Date* pNullDate, const ScDPNumGroupInfo* pNumInfo);
|
|
|
|
virtual bool match(const ScDPItemData & rCellData) const;
|
|
|
|
private:
|
|
ScDPGroupDateFilter(); // disabled
|
|
|
|
const Date* mpNullDate;
|
|
const ScDPNumGroupInfo* mpNumInfo;
|
|
double mfMatchValue;
|
|
sal_Int32 mnDatePart;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
ScDPGroupDateFilter::ScDPGroupDateFilter(double fMatchValue, sal_Int32 nDatePart,
|
|
const Date* pNullDate, const ScDPNumGroupInfo* pNumInfo) :
|
|
mpNullDate(pNullDate),
|
|
mpNumInfo(pNumInfo),
|
|
mfMatchValue(fMatchValue),
|
|
mnDatePart(nDatePart)
|
|
{
|
|
|
|
}
|
|
bool ScDPGroupDateFilter::match( const ScDPItemData & rCellData ) const
|
|
{
|
|
using namespace ::com::sun::star::sheet;
|
|
using ::rtl::math::approxFloor;
|
|
using ::rtl::math::approxEqual;
|
|
|
|
if ( !rCellData.IsValue() )
|
|
return false;
|
|
|
|
if (!mpNumInfo)
|
|
return false;
|
|
|
|
// Start and end dates are inclusive. (An end date without a time value
|
|
// is included, while an end date with a time value is not.)
|
|
|
|
if ( rCellData.GetValue() < mpNumInfo->Start && !approxEqual(rCellData.GetValue(), mpNumInfo->Start) )
|
|
return static_cast<sal_Int32>(mfMatchValue) == SC_DP_DATE_FIRST;
|
|
|
|
if ( rCellData.GetValue() > mpNumInfo->End && !approxEqual(rCellData.GetValue(), mpNumInfo->End) )
|
|
return static_cast<sal_Int32>(mfMatchValue) == SC_DP_DATE_LAST;
|
|
|
|
if (mnDatePart == DataPilotFieldGroupBy::HOURS || mnDatePart == DataPilotFieldGroupBy::MINUTES ||
|
|
mnDatePart == DataPilotFieldGroupBy::SECONDS)
|
|
{
|
|
// handle time
|
|
// (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
|
|
|
|
double time = rCellData.GetValue() - approxFloor(rCellData.GetValue());
|
|
long seconds = static_cast<long>(approxFloor(time*D_TIMEFACTOR + 0.5));
|
|
|
|
switch (mnDatePart)
|
|
{
|
|
case DataPilotFieldGroupBy::HOURS:
|
|
{
|
|
sal_Int32 hrs = seconds / 3600;
|
|
sal_Int32 matchHrs = static_cast<sal_Int32>(mfMatchValue);
|
|
return hrs == matchHrs;
|
|
}
|
|
case DataPilotFieldGroupBy::MINUTES:
|
|
{
|
|
sal_Int32 minutes = (seconds % 3600) / 60;
|
|
sal_Int32 matchMinutes = static_cast<sal_Int32>(mfMatchValue);
|
|
return minutes == matchMinutes;
|
|
}
|
|
case DataPilotFieldGroupBy::SECONDS:
|
|
{
|
|
sal_Int32 sec = seconds % 60;
|
|
sal_Int32 matchSec = static_cast<sal_Int32>(mfMatchValue);
|
|
return sec == matchSec;
|
|
}
|
|
default:
|
|
OSL_FAIL("invalid time part");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Date date = *mpNullDate + static_cast<long>(approxFloor(rCellData.GetValue()));
|
|
switch (mnDatePart)
|
|
{
|
|
case DataPilotFieldGroupBy::YEARS:
|
|
{
|
|
sal_Int32 year = static_cast<sal_Int32>(date.GetYear());
|
|
sal_Int32 matchYear = static_cast<sal_Int32>(mfMatchValue);
|
|
return year == matchYear;
|
|
}
|
|
case DataPilotFieldGroupBy::QUARTERS:
|
|
{
|
|
sal_Int32 qtr = 1 + (static_cast<sal_Int32>(date.GetMonth()) - 1) / 3;
|
|
sal_Int32 matchQtr = static_cast<sal_Int32>(mfMatchValue);
|
|
return qtr == matchQtr;
|
|
}
|
|
case DataPilotFieldGroupBy::MONTHS:
|
|
{
|
|
sal_Int32 month = static_cast<sal_Int32>(date.GetMonth());
|
|
sal_Int32 matchMonth = static_cast<sal_Int32>(mfMatchValue);
|
|
return month == matchMonth;
|
|
}
|
|
case DataPilotFieldGroupBy::DAYS:
|
|
{
|
|
Date yearStart(1, 1, date.GetYear());
|
|
sal_Int32 days = (date - yearStart) + 1; // Jan 01 has value 1
|
|
if (days >= 60 && !date.IsLeapYear())
|
|
{
|
|
// This is not a leap year. Adjust the value accordingly.
|
|
++days;
|
|
}
|
|
sal_Int32 matchDays = static_cast<sal_Int32>(mfMatchValue);
|
|
return days == matchDays;
|
|
}
|
|
default:
|
|
OSL_FAIL("invalid date part");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
// -----------------------------------------------------------------------
|
|
|
|
ScDPDateGroupHelper::ScDPDateGroupHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart ) :
|
|
aNumInfo( rInfo ),
|
|
nDatePart( nPart )
|
|
{
|
|
}
|
|
|
|
ScDPDateGroupHelper::~ScDPDateGroupHelper()
|
|
{
|
|
}
|
|
|
|
String lcl_GetTwoDigitString( sal_Int32 nValue )
|
|
{
|
|
String aRet = String::CreateFromInt32( nValue );
|
|
if ( aRet.Len() < 2 )
|
|
aRet.Insert( (sal_Unicode)'0', 0 );
|
|
return aRet;
|
|
}
|
|
|
|
String lcl_GetDateGroupName( sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter )
|
|
{
|
|
String aRet;
|
|
switch ( nDatePart )
|
|
{
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
|
|
aRet = String::CreateFromInt32( nValue );
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
|
|
aRet = ScGlobal::pLocaleData->getQuarterAbbreviation( (sal_Int16)(nValue - 1) ); // nValue is 1-based
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
|
|
//! cache getMonths() result?
|
|
aRet = ScGlobal::GetCalendar()->getDisplayName(
|
|
::com::sun::star::i18n::CalendarDisplayIndex::MONTH,
|
|
sal_Int16(nValue-1), 0 ); // 0-based, get short name
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
|
|
{
|
|
Date aDate( 1, 1, SC_DP_LEAPYEAR );
|
|
aDate += ( nValue - 1 ); // nValue is 1-based
|
|
Date aNullDate = *(pFormatter->GetNullDate());
|
|
long nDays = aDate - aNullDate;
|
|
|
|
sal_uLong nFormat = pFormatter->GetFormatIndex( NF_DATE_SYS_DDMMM, ScGlobal::eLnge );
|
|
Color* pColor;
|
|
pFormatter->GetOutputString( nDays, nFormat, aRet, &pColor );
|
|
}
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS:
|
|
//! allow am/pm format?
|
|
aRet = lcl_GetTwoDigitString( nValue );
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES:
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS:
|
|
aRet = ScGlobal::pLocaleData->getTimeSep();
|
|
aRet.Append( lcl_GetTwoDigitString( nValue ) );
|
|
break;
|
|
default:
|
|
OSL_FAIL("invalid date part");
|
|
}
|
|
return aRet;
|
|
}
|
|
|
|
sal_Int32 lcl_GetDatePartValue( double fValue, sal_Int32 nDatePart, SvNumberFormatter* pFormatter,
|
|
const ScDPNumGroupInfo* pNumInfo )
|
|
{
|
|
// Start and end are inclusive
|
|
// (End date without a time value is included, with a time value it's not)
|
|
|
|
if ( pNumInfo )
|
|
{
|
|
if ( fValue < pNumInfo->Start && !rtl::math::approxEqual( fValue, pNumInfo->Start ) )
|
|
return SC_DP_DATE_FIRST;
|
|
if ( fValue > pNumInfo->End && !rtl::math::approxEqual( fValue, pNumInfo->End ) )
|
|
return SC_DP_DATE_LAST;
|
|
}
|
|
|
|
sal_Int32 nResult = 0;
|
|
|
|
if ( nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::HOURS || nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES || nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS )
|
|
{
|
|
// handle time
|
|
// (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
|
|
|
|
double fTime = fValue - ::rtl::math::approxFloor(fValue);
|
|
long nSeconds = (long)::rtl::math::approxFloor(fTime*D_TIMEFACTOR+0.5);
|
|
|
|
switch ( nDatePart )
|
|
{
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS:
|
|
nResult = nSeconds / 3600;
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES:
|
|
nResult = ( nSeconds % 3600 ) / 60;
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS:
|
|
nResult = nSeconds % 60;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Date aDate = *(pFormatter->GetNullDate());
|
|
aDate += (long)::rtl::math::approxFloor( fValue );
|
|
|
|
switch ( nDatePart )
|
|
{
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
|
|
nResult = aDate.GetYear();
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
|
|
nResult = 1 + ( aDate.GetMonth() - 1 ) / 3; // 1..4
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
|
|
nResult = aDate.GetMonth(); // 1..12
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
|
|
{
|
|
Date aYearStart( 1, 1, aDate.GetYear() );
|
|
nResult = ( aDate - aYearStart ) + 1; // Jan 01 has value 1
|
|
if ( nResult >= 60 && !aDate.IsLeapYear() )
|
|
{
|
|
// days are counted from 1 to 366 - if not from a leap year, adjust
|
|
++nResult;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
OSL_FAIL("invalid date part");
|
|
}
|
|
}
|
|
|
|
return nResult;
|
|
}
|
|
|
|
sal_Bool lcl_DateContained( sal_Int32 nGroupPart, const ScDPItemData& rGroupData,
|
|
sal_Int32 nBasePart, const ScDPItemData& rBaseData )
|
|
{
|
|
if ( !rGroupData.IsValue() || !rBaseData.IsValue() )
|
|
{
|
|
// non-numeric entries involved: only match equal entries
|
|
return rGroupData.IsCaseInsEqual( rBaseData );
|
|
}
|
|
|
|
// no approxFloor needed, values were created from integers
|
|
sal_Int32 nGroupValue = (sal_Int32) rGroupData.GetValue();
|
|
sal_Int32 nBaseValue = (sal_Int32) rBaseData.GetValue();
|
|
if ( nBasePart > nGroupPart )
|
|
{
|
|
// switch, so the base part is the smaller (inner) part
|
|
|
|
::std::swap( nGroupPart, nBasePart );
|
|
::std::swap( nGroupValue, nBaseValue );
|
|
}
|
|
|
|
if ( nGroupValue == SC_DP_DATE_FIRST || nGroupValue == SC_DP_DATE_LAST ||
|
|
nBaseValue == SC_DP_DATE_FIRST || nBaseValue == SC_DP_DATE_LAST )
|
|
{
|
|
// first/last entry matches only itself
|
|
return ( nGroupValue == nBaseValue );
|
|
}
|
|
|
|
sal_Bool bContained = sal_True;
|
|
switch ( nBasePart ) // inner part
|
|
{
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
|
|
// a month is only contained in its quarter
|
|
if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
|
|
{
|
|
// months and quarters are both 1-based
|
|
bContained = ( nGroupValue - 1 == ( nBaseValue - 1 ) / 3 );
|
|
}
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
|
|
// a day is only contained in its quarter or month
|
|
if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS || nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
|
|
{
|
|
Date aDate( 1, 1, SC_DP_LEAPYEAR );
|
|
aDate += ( nBaseValue - 1 ); // days are 1-based
|
|
sal_Int32 nCompare = aDate.GetMonth();
|
|
if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
|
|
nCompare = ( ( nCompare - 1 ) / 3 ) + 1; // get quarter from date
|
|
|
|
bContained = ( nGroupValue == nCompare );
|
|
}
|
|
break;
|
|
|
|
// other parts: everything is contained
|
|
}
|
|
|
|
return bContained;
|
|
}
|
|
|
|
String lcl_GetSpecialDateName( double fValue, bool bFirst, SvNumberFormatter* pFormatter )
|
|
{
|
|
rtl::OUStringBuffer aBuffer;
|
|
aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
|
|
lcl_AppendDateStr( aBuffer, fValue, pFormatter );
|
|
return aBuffer.makeStringAndClear();
|
|
}
|
|
|
|
void ScDPDateGroupHelper::FillColumnEntries(
|
|
SCCOL nSourceDim, const ScDPCache* pCache, std::vector<SCROW>& rEntries, const std::vector<SCROW>& rOriginal) const
|
|
{
|
|
// auto min/max is only used for "Years" part, but the loop is always needed
|
|
double fSourceMin = 0.0;
|
|
double fSourceMax = 0.0;
|
|
bool bFirst = true;
|
|
|
|
size_t nOriginalCount = rOriginal.size();
|
|
for (size_t nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++)
|
|
{
|
|
const ScDPItemData* pItemData = pCache->GetItemDataById( nSourceDim, rOriginal[nOriginalPos] );
|
|
if ( pItemData->HasStringData() )
|
|
{
|
|
// string data: just copy
|
|
lcl_Insert( nSourceDim, pCache , rEntries, rOriginal[nOriginalPos] );
|
|
}
|
|
else
|
|
{
|
|
double fSourceValue = pItemData->GetValue();
|
|
if ( bFirst )
|
|
{
|
|
fSourceMin = fSourceMax = fSourceValue;
|
|
bFirst = false;
|
|
}
|
|
else
|
|
{
|
|
if ( fSourceValue < fSourceMin )
|
|
fSourceMin = fSourceValue;
|
|
if ( fSourceValue > fSourceMax )
|
|
fSourceMax = fSourceValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// For the start/end values, use the same date rounding as in ScDPNumGroupDimension::GetNumEntries
|
|
// (but not for the list of available years):
|
|
if ( aNumInfo.AutoStart )
|
|
const_cast<ScDPDateGroupHelper*>(this)->aNumInfo.Start = rtl::math::approxFloor( fSourceMin );
|
|
if ( aNumInfo.AutoEnd )
|
|
const_cast<ScDPDateGroupHelper*>(this)->aNumInfo.End = rtl::math::approxFloor( fSourceMax ) + 1;
|
|
|
|
//! if not automatic, limit fSourceMin/fSourceMax for list of year values?
|
|
SvNumberFormatter* pFormatter = pCache->GetDoc()->GetFormatTable();
|
|
|
|
long nStart = 0;
|
|
long nEnd = 0; // including
|
|
|
|
switch ( nDatePart )
|
|
{
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
|
|
nStart = lcl_GetDatePartValue( fSourceMin, com::sun::star::sheet::DataPilotFieldGroupBy::YEARS, pFormatter, NULL );
|
|
nEnd = lcl_GetDatePartValue( fSourceMax, com::sun::star::sheet::DataPilotFieldGroupBy::YEARS, pFormatter, NULL );
|
|
break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4; break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS: nStart = 1; nEnd = 12; break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS: nStart = 1; nEnd = 366; break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS: nStart = 0; nEnd = 23; break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES: nStart = 0; nEnd = 59; break;
|
|
case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS: nStart = 0; nEnd = 59; break;
|
|
default:
|
|
OSL_FAIL("invalid date part");
|
|
}
|
|
|
|
for ( sal_Int32 nValue = nStart; nValue <= nEnd; nValue++ )
|
|
{
|
|
String aName = lcl_GetDateGroupName( nDatePart, nValue, pFormatter );
|
|
lcl_InsertValue<false>( nSourceDim, pCache, rEntries, aName, nValue, nDatePart );
|
|
}
|
|
|
|
// add first/last entry (min/max)
|
|
String aFirstName = lcl_GetSpecialDateName( aNumInfo.Start, true, pFormatter );
|
|
lcl_InsertValue<true>( nSourceDim, pCache, rEntries, aFirstName, SC_DP_DATE_FIRST, nDatePart );
|
|
|
|
String aLastName = lcl_GetSpecialDateName( aNumInfo.End, false, pFormatter );
|
|
lcl_InsertValue<true>( nSourceDim, pCache, rEntries, aLastName, SC_DP_DATE_LAST, nDatePart );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
ScDPGroupItem::ScDPGroupItem( const ScDPItemData& rName ) :
|
|
aGroupName( rName )
|
|
{
|
|
}
|
|
|
|
ScDPGroupItem::~ScDPGroupItem()
|
|
{
|
|
}
|
|
|
|
void ScDPGroupItem::AddElement( const ScDPItemData& rName )
|
|
{
|
|
aElements.push_back( rName );
|
|
}
|
|
|
|
bool ScDPGroupItem::HasElement( const ScDPItemData& rData ) const
|
|
{
|
|
for ( ScDPItemDataVec::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
|
|
if ( aIter->IsCaseInsEqual( rData ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScDPGroupItem::HasCommonElement( const ScDPGroupItem& rOther ) const
|
|
{
|
|
for ( ScDPItemDataVec::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
|
|
if ( rOther.HasElement( *aIter ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void ScDPGroupItem::FillGroupFilter( ScDPCacheTable::GroupFilter& rFilter ) const
|
|
{
|
|
ScDPItemDataVec::const_iterator itrEnd = aElements.end();
|
|
for (ScDPItemDataVec::const_iterator itr = aElements.begin(); itr != itrEnd; ++itr)
|
|
rFilter.addMatchItem(itr->GetString(), itr->GetValue(), itr->IsValue());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
ScDPGroupDimension::ScDPGroupDimension( long nSource, const String& rNewName ) :
|
|
nSourceDim( nSource ),
|
|
nGroupDim( -1 ),
|
|
aGroupName( rNewName ),
|
|
pDateHelper( NULL )
|
|
{
|
|
}
|
|
|
|
ScDPGroupDimension::~ScDPGroupDimension()
|
|
{
|
|
delete pDateHelper;
|
|
maMemberEntries.clear();
|
|
}
|
|
|
|
ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) :
|
|
nSourceDim( rOther.nSourceDim ),
|
|
nGroupDim( rOther.nGroupDim ),
|
|
aGroupName( rOther.aGroupName ),
|
|
pDateHelper( NULL ),
|
|
aItems( rOther.aItems )
|
|
{
|
|
if ( rOther.pDateHelper )
|
|
pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
|
|
}
|
|
|
|
ScDPGroupDimension& ScDPGroupDimension::operator=( const ScDPGroupDimension& rOther )
|
|
{
|
|
nSourceDim = rOther.nSourceDim;
|
|
nGroupDim = rOther.nGroupDim;
|
|
aGroupName = rOther.aGroupName;
|
|
aItems = rOther.aItems;
|
|
|
|
delete pDateHelper;
|
|
if ( rOther.pDateHelper )
|
|
pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
|
|
else
|
|
pDateHelper = NULL;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void ScDPGroupDimension::MakeDateHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
|
|
{
|
|
delete pDateHelper;
|
|
pDateHelper = new ScDPDateGroupHelper( rInfo, nPart );
|
|
}
|
|
|
|
void ScDPGroupDimension::AddItem( const ScDPGroupItem& rItem )
|
|
{
|
|
aItems.push_back( rItem );
|
|
}
|
|
|
|
void ScDPGroupDimension::SetGroupDim( long nDim )
|
|
{
|
|
nGroupDim = nDim;
|
|
}
|
|
const std::vector< SCROW >& ScDPGroupDimension::GetColumnEntries( const ScDPCacheTable& rCacheTable, const std::vector< SCROW >& rOriginal ) const
|
|
{
|
|
if ( maMemberEntries.empty() )
|
|
{
|
|
if ( pDateHelper )
|
|
{
|
|
pDateHelper->FillColumnEntries( (SCCOL)GetSourceDim(), rCacheTable.getCache(), maMemberEntries, rOriginal );
|
|
}
|
|
else
|
|
{
|
|
for (size_t i =0; i < rOriginal.size( ); i ++)
|
|
{
|
|
const ScDPItemData* pItemData = rCacheTable.getCache()->GetItemDataById( (SCCOL)GetSourceDim(), rOriginal[i] );
|
|
if ( !pItemData || !GetGroupForData( *pItemData ) )
|
|
{
|
|
// not in any group -> add as its own group
|
|
maMemberEntries.push_back( rOriginal[i] );
|
|
}
|
|
}
|
|
|
|
long nCount = aItems.size();
|
|
for (long i=0; i<nCount; i++)
|
|
{
|
|
SCROW nNew = rCacheTable.getCache()->GetAdditionalItemID( aItems[i].GetName() );
|
|
lcl_Insert ( (SCCOL)GetSourceDim(), rCacheTable.getCache(), maMemberEntries, nNew );
|
|
}
|
|
}
|
|
}
|
|
return maMemberEntries;
|
|
}
|
|
|
|
|
|
|
|
const ScDPGroupItem* ScDPGroupDimension::GetGroupForData( const ScDPItemData& rData ) const
|
|
{
|
|
for ( ScDPGroupItemVec::const_iterator aIter(aItems.begin()); aIter != aItems.end(); aIter++ )
|
|
if ( aIter->HasElement( rData ) )
|
|
return &*aIter;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const ScDPGroupItem* ScDPGroupDimension::GetGroupForName( const ScDPItemData& rName ) const
|
|
{
|
|
for ( ScDPGroupItemVec::const_iterator aIter(aItems.begin()); aIter != aItems.end(); aIter++ )
|
|
if ( aIter->GetName().IsCaseInsEqual( rName ) )
|
|
return &*aIter;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const ScDPGroupItem* ScDPGroupDimension::GetGroupByIndex( size_t nIndex ) const
|
|
{
|
|
if (nIndex >= aItems.size())
|
|
return NULL;
|
|
|
|
return &aItems[nIndex];
|
|
}
|
|
|
|
void ScDPGroupDimension::DisposeData()
|
|
{
|
|
maMemberEntries.clear();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
ScDPNumGroupDimension::ScDPNumGroupDimension() :
|
|
pDateHelper( NULL ),
|
|
bHasNonInteger( false ),
|
|
cDecSeparator( 0 )
|
|
{
|
|
}
|
|
|
|
ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) :
|
|
aGroupInfo( rInfo ),
|
|
pDateHelper( NULL ),
|
|
bHasNonInteger( false ),
|
|
cDecSeparator( 0 )
|
|
{
|
|
}
|
|
|
|
ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) :
|
|
aGroupInfo( rOther.aGroupInfo ),
|
|
pDateHelper( NULL ),
|
|
bHasNonInteger( false ),
|
|
cDecSeparator( 0 )
|
|
{
|
|
if ( rOther.pDateHelper )
|
|
pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
|
|
}
|
|
|
|
ScDPNumGroupDimension& ScDPNumGroupDimension::operator=( const ScDPNumGroupDimension& rOther )
|
|
{
|
|
aGroupInfo = rOther.aGroupInfo;
|
|
|
|
delete pDateHelper;
|
|
if ( rOther.pDateHelper )
|
|
pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
|
|
else
|
|
pDateHelper = NULL;
|
|
|
|
bHasNonInteger = false;
|
|
return *this;
|
|
}
|
|
|
|
void ScDPNumGroupDimension::DisposeData()
|
|
{
|
|
bHasNonInteger = false;
|
|
maMemberEntries.clear();
|
|
}
|
|
|
|
ScDPNumGroupDimension::~ScDPNumGroupDimension()
|
|
{
|
|
delete pDateHelper;
|
|
}
|
|
|
|
void ScDPNumGroupDimension::MakeDateHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
|
|
{
|
|
delete pDateHelper;
|
|
pDateHelper = new ScDPDateGroupHelper( rInfo, nPart );
|
|
|
|
aGroupInfo.Enable = sal_True; //! or query both?
|
|
}
|
|
|
|
const std::vector<SCROW>& ScDPNumGroupDimension::GetNumEntries(
|
|
SCCOL nSourceDim, const ScDPCache* pCache, const std::vector<SCROW>& rOriginal) const
|
|
{
|
|
if ( maMemberEntries.empty() )
|
|
{
|
|
SvNumberFormatter* pFormatter = pCache->GetDoc()->GetFormatTable();
|
|
|
|
if ( pDateHelper )
|
|
pDateHelper->FillColumnEntries( nSourceDim, pCache, maMemberEntries,rOriginal );
|
|
else
|
|
{
|
|
// Copy textual entries.
|
|
// Also look through the source entries for non-integer numbers, minimum and maximum.
|
|
// GetNumEntries (GetColumnEntries) must be called before accessing the groups
|
|
// (this in ensured by calling ScDPLevel::GetMembersObject for all column/row/page
|
|
// dimensions before iterating over the values).
|
|
|
|
cDecSeparator = ScGlobal::pLocaleData->getNumDecimalSep().GetChar(0);
|
|
|
|
// non-integer GroupInfo values count, too
|
|
bHasNonInteger = ( !aGroupInfo.AutoStart && !IsInteger( aGroupInfo.Start ) ) ||
|
|
( !aGroupInfo.AutoEnd && !IsInteger( aGroupInfo.End ) ) ||
|
|
!IsInteger( aGroupInfo.Step );
|
|
double fSourceMin = 0.0;
|
|
double fSourceMax = 0.0;
|
|
bool bFirst = true;
|
|
|
|
size_t nOriginalCount = rOriginal.size();
|
|
for (size_t nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++)
|
|
{
|
|
const ScDPItemData* pItemData = pCache->GetItemDataById( nSourceDim , rOriginal[nOriginalPos] );
|
|
|
|
if ( pItemData && pItemData ->HasStringData() )
|
|
{
|
|
lcl_Insert( nSourceDim, pCache, maMemberEntries, rOriginal[nOriginalPos] );
|
|
}
|
|
else
|
|
{
|
|
double fSourceValue = pItemData->GetValue();
|
|
if ( bFirst )
|
|
{
|
|
fSourceMin = fSourceMax = fSourceValue;
|
|
bFirst = false;
|
|
}
|
|
else
|
|
{
|
|
if ( fSourceValue < fSourceMin )
|
|
fSourceMin = fSourceValue;
|
|
if ( fSourceValue > fSourceMax )
|
|
fSourceMax = fSourceValue;
|
|
}
|
|
if ( !bHasNonInteger && !IsInteger( fSourceValue ) )
|
|
{
|
|
// if any non-integer numbers are involved, the group labels are
|
|
// shown including their upper limit
|
|
bHasNonInteger = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( aGroupInfo.DateValues )
|
|
{
|
|
// special handling for dates: always integer, round down limits
|
|
bHasNonInteger = false;
|
|
fSourceMin = rtl::math::approxFloor( fSourceMin );
|
|
fSourceMax = rtl::math::approxFloor( fSourceMax ) + 1;
|
|
}
|
|
|
|
if ( aGroupInfo.AutoStart )
|
|
const_cast<ScDPNumGroupDimension*>(this)->aGroupInfo.Start = fSourceMin;
|
|
if ( aGroupInfo.AutoEnd )
|
|
const_cast<ScDPNumGroupDimension*>(this)->aGroupInfo.End = fSourceMax;
|
|
|
|
//! limit number of entries?
|
|
|
|
long nLoopCount = 0;
|
|
double fLoop = aGroupInfo.Start;
|
|
|
|
// Use "less than" instead of "less or equal" for the loop - don't create a group
|
|
// that consists only of the end value. Instead, the end value is then included
|
|
// in the last group (last group is bigger than the others).
|
|
// The first group has to be created nonetheless. GetNumGroupForValue has corresponding logic.
|
|
|
|
bool bFirstGroup = true;
|
|
while ( bFirstGroup || ( fLoop < aGroupInfo.End && !rtl::math::approxEqual( fLoop, aGroupInfo.End ) ) )
|
|
{
|
|
String aName = lcl_GetNumGroupName( fLoop, aGroupInfo, bHasNonInteger, cDecSeparator, pFormatter );
|
|
// create a numerical entry to ensure proper sorting
|
|
// (in FillMemberResults this needs special handling)
|
|
lcl_InsertValue<true>( nSourceDim, pCache, maMemberEntries, aName, fLoop );
|
|
++nLoopCount;
|
|
fLoop = aGroupInfo.Start + nLoopCount * aGroupInfo.Step;
|
|
bFirstGroup = false;
|
|
|
|
// ScDPItemData values are compared with approxEqual
|
|
}
|
|
|
|
String aFirstName = lcl_GetSpecialNumGroupName( aGroupInfo.Start, true, cDecSeparator, aGroupInfo.DateValues, pFormatter );
|
|
lcl_InsertValue<true>( nSourceDim, pCache, maMemberEntries, aFirstName, aGroupInfo.Start - aGroupInfo.Step );
|
|
|
|
String aLastName = lcl_GetSpecialNumGroupName( aGroupInfo.End, false, cDecSeparator, aGroupInfo.DateValues, pFormatter );
|
|
lcl_InsertValue<true>( nSourceDim, pCache, maMemberEntries, aLastName, aGroupInfo.End + aGroupInfo.Step );
|
|
}
|
|
}
|
|
return maMemberEntries;
|
|
}
|
|
|
|
ScDPGroupTableData::ScDPGroupTableData( const shared_ptr<ScDPTableData>& pSource, ScDocument* pDocument ) :
|
|
ScDPTableData(pDocument),
|
|
pSourceData( pSource ),
|
|
pDoc( pDocument )
|
|
{
|
|
OSL_ENSURE( pSource, "ScDPGroupTableData: pSource can't be NULL" );
|
|
|
|
CreateCacheTable();
|
|
nSourceCount = pSource->GetColumnCount(); // real columns, excluding data layout
|
|
pNumGroups = new ScDPNumGroupDimension[nSourceCount];
|
|
}
|
|
|
|
ScDPGroupTableData::~ScDPGroupTableData()
|
|
{
|
|
delete[] pNumGroups;
|
|
}
|
|
|
|
void ScDPGroupTableData::AddGroupDimension( const ScDPGroupDimension& rGroup )
|
|
{
|
|
ScDPGroupDimension aNewGroup( rGroup );
|
|
aNewGroup.SetGroupDim( GetColumnCount() ); // new dimension will be at the end
|
|
aGroups.push_back( aNewGroup );
|
|
aGroupNames.insert( OUString(aNewGroup.GetName()) );
|
|
}
|
|
|
|
void ScDPGroupTableData::SetNumGroupDimension( long nIndex, const ScDPNumGroupDimension& rGroup )
|
|
{
|
|
if ( nIndex < nSourceCount )
|
|
{
|
|
pNumGroups[nIndex] = rGroup;
|
|
|
|
// automatic minimum / maximum is handled in GetNumEntries
|
|
}
|
|
}
|
|
|
|
long ScDPGroupTableData::GetDimensionIndex( const String& rName )
|
|
{
|
|
for (long i=0; i<nSourceCount; i++) // nSourceCount excludes data layout
|
|
if ( pSourceData->getDimensionName(i) == rName ) //! ignore case?
|
|
return i;
|
|
return -1; // none
|
|
}
|
|
|
|
long ScDPGroupTableData::GetColumnCount()
|
|
{
|
|
return nSourceCount + aGroups.size();
|
|
}
|
|
|
|
bool ScDPGroupTableData::IsNumGroupDimension( long nDimension ) const
|
|
{
|
|
return ( nDimension < nSourceCount && pNumGroups[nDimension].GetInfo().Enable );
|
|
}
|
|
|
|
void ScDPGroupTableData::GetNumGroupInfo( long nDimension, ScDPNumGroupInfo& rInfo,
|
|
bool& rNonInteger, sal_Unicode& rDecimal )
|
|
{
|
|
if ( nDimension < nSourceCount )
|
|
{
|
|
rInfo = pNumGroups[nDimension].GetInfo();
|
|
rNonInteger = pNumGroups[nDimension].HasNonInteger();
|
|
rDecimal = pNumGroups[nDimension].GetDecSeparator();
|
|
}
|
|
}
|
|
long ScDPGroupTableData::GetMembersCount( long nDim )
|
|
{
|
|
const std::vector< SCROW >& members = GetColumnEntries( nDim );
|
|
return members.size();
|
|
}
|
|
const std::vector< SCROW >& ScDPGroupTableData::GetColumnEntries( long nColumn )
|
|
{
|
|
if ( nColumn >= nSourceCount )
|
|
{
|
|
if ( getIsDataLayoutDimension( nColumn) ) // data layout dimension?
|
|
nColumn = nSourceCount; // index of data layout in source data
|
|
else
|
|
{
|
|
const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
|
|
long nSourceDim = rGroupDim.GetSourceDim();
|
|
// collection is cached at pSourceData, GetColumnEntries can be called every time
|
|
const std::vector< SCROW >& rOriginal = pSourceData->GetColumnEntries( nSourceDim );
|
|
return rGroupDim.GetColumnEntries( GetCacheTable(), rOriginal );
|
|
}
|
|
}
|
|
|
|
if ( IsNumGroupDimension( nColumn ) )
|
|
{
|
|
// dimension number is unchanged for numerical groups
|
|
const std::vector< SCROW >& rOriginal = pSourceData->GetColumnEntries( nColumn );
|
|
return pNumGroups[nColumn].GetNumEntries( (SCCOL)nColumn, GetCacheTable().getCache(), rOriginal );
|
|
}
|
|
|
|
return pSourceData->GetColumnEntries( nColumn );
|
|
}
|
|
|
|
const ScDPItemData* ScDPGroupTableData::GetMemberById( long nDim, long nId )
|
|
{
|
|
if ( nDim >= nSourceCount )
|
|
{
|
|
if ( getIsDataLayoutDimension( nDim) )
|
|
nDim = nSourceCount;
|
|
else
|
|
{
|
|
const ScDPGroupDimension& rGroupDim = aGroups[nDim - nSourceCount];
|
|
nDim = rGroupDim.GetSourceDim();
|
|
}
|
|
}
|
|
return pSourceData->GetMemberById( nDim, nId );
|
|
}
|
|
|
|
String ScDPGroupTableData::getDimensionName(long nColumn)
|
|
{
|
|
if ( nColumn >= nSourceCount )
|
|
{
|
|
if ( nColumn == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
|
|
nColumn = nSourceCount; // index of data layout in source data
|
|
else
|
|
return aGroups[nColumn - nSourceCount].GetName();
|
|
}
|
|
|
|
return pSourceData->getDimensionName( nColumn );
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::getIsDataLayoutDimension(long nColumn)
|
|
{
|
|
// position of data layout dimension is moved from source data
|
|
return ( nColumn == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ); // data layout dimension?
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::IsDateDimension(long nDim)
|
|
{
|
|
if ( nDim >= nSourceCount )
|
|
{
|
|
if ( nDim == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
|
|
nDim = nSourceCount; // index of data layout in source data
|
|
else
|
|
nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
|
|
}
|
|
|
|
return pSourceData->IsDateDimension( nDim );
|
|
}
|
|
|
|
sal_uLong ScDPGroupTableData::GetNumberFormat(long nDim)
|
|
{
|
|
if ( nDim >= nSourceCount )
|
|
{
|
|
if ( nDim == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
|
|
nDim = nSourceCount; // index of data layout in source data
|
|
else
|
|
nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
|
|
}
|
|
|
|
return pSourceData->GetNumberFormat( nDim );
|
|
}
|
|
|
|
void ScDPGroupTableData::DisposeData()
|
|
{
|
|
for ( ScDPGroupDimensionVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
aIter->DisposeData();
|
|
|
|
for ( long i=0; i<nSourceCount; i++ )
|
|
pNumGroups[i].DisposeData();
|
|
|
|
pSourceData->DisposeData();
|
|
}
|
|
|
|
void ScDPGroupTableData::SetEmptyFlags( sal_Bool bIgnoreEmptyRows, sal_Bool bRepeatIfEmpty )
|
|
{
|
|
pSourceData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
|
|
}
|
|
|
|
bool ScDPGroupTableData::IsRepeatIfEmpty()
|
|
{
|
|
return pSourceData->IsRepeatIfEmpty();
|
|
}
|
|
|
|
void ScDPGroupTableData::CreateCacheTable()
|
|
{
|
|
pSourceData->CreateCacheTable();
|
|
}
|
|
|
|
void ScDPGroupTableData::ModifyFilterCriteria(vector<ScDPCacheTable::Criterion>& rCriteria)
|
|
{
|
|
typedef boost::unordered_map<long, const ScDPGroupDimension*> GroupFieldMapType;
|
|
GroupFieldMapType aGroupFieldIds;
|
|
{
|
|
ScDPGroupDimensionVec::const_iterator itr = aGroups.begin(), itrEnd = aGroups.end();
|
|
for (; itr != itrEnd; ++itr)
|
|
aGroupFieldIds.insert( boost::unordered_map<long, const ScDPGroupDimension*>::value_type(itr->GetGroupDim(), &(*itr)) );
|
|
}
|
|
|
|
vector<ScDPCacheTable::Criterion> aNewCriteria;
|
|
aNewCriteria.reserve(rCriteria.size() + aGroups.size());
|
|
|
|
// Go through all the filtered field names and process them appropriately.
|
|
|
|
vector<ScDPCacheTable::Criterion>::const_iterator itrEnd = rCriteria.end();
|
|
GroupFieldMapType::const_iterator itrGrpEnd = aGroupFieldIds.end();
|
|
for (vector<ScDPCacheTable::Criterion>::const_iterator itr = rCriteria.begin(); itr != itrEnd; ++itr)
|
|
{
|
|
ScDPCacheTable::SingleFilter* pFilter = dynamic_cast<ScDPCacheTable::SingleFilter*>(itr->mpFilter.get());
|
|
if (!pFilter)
|
|
// We expect this to be a single filter.
|
|
continue;
|
|
|
|
GroupFieldMapType::const_iterator itrGrp = aGroupFieldIds.find(itr->mnFieldIndex);
|
|
if (itrGrp == itrGrpEnd)
|
|
{
|
|
if (IsNumGroupDimension(itr->mnFieldIndex))
|
|
{
|
|
// internal number group field
|
|
const ScDPNumGroupDimension& rNumGrpDim = pNumGroups[itr->mnFieldIndex];
|
|
const ScDPDateGroupHelper* pDateHelper = rNumGrpDim.GetDateHelper();
|
|
if (!pDateHelper)
|
|
{
|
|
// What do we do here !?
|
|
continue;
|
|
}
|
|
|
|
ScDPCacheTable::Criterion aCri;
|
|
aCri.mnFieldIndex = itr->mnFieldIndex;
|
|
aCri.mpFilter.reset(new ScDPGroupDateFilter(
|
|
pFilter->getMatchValue(), pDateHelper->GetDatePart(),
|
|
pDoc->GetFormatTable()->GetNullDate(), &pDateHelper->GetNumInfo()));
|
|
|
|
aNewCriteria.push_back(aCri);
|
|
}
|
|
else
|
|
{
|
|
// This is a regular source field.
|
|
aNewCriteria.push_back(*itr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is an ordinary group field or external number group field.
|
|
|
|
const ScDPGroupDimension* pGrpDim = itrGrp->second;
|
|
long nSrcDim = pGrpDim->GetSourceDim();
|
|
const ScDPDateGroupHelper* pDateHelper = pGrpDim->GetDateHelper();
|
|
|
|
if (pDateHelper)
|
|
{
|
|
// external number group
|
|
ScDPCacheTable::Criterion aCri;
|
|
aCri.mnFieldIndex = nSrcDim; // use the source dimension, not the group dimension.
|
|
aCri.mpFilter.reset(new ScDPGroupDateFilter(
|
|
pFilter->getMatchValue(), pDateHelper->GetDatePart(),
|
|
pDoc->GetFormatTable()->GetNullDate(), &pDateHelper->GetNumInfo()));
|
|
|
|
aNewCriteria.push_back(aCri);
|
|
}
|
|
else
|
|
{
|
|
// normal group
|
|
|
|
// Note that each group dimension may have multiple group names!
|
|
size_t nGroupItemCount = pGrpDim->GetItemCount();
|
|
for (size_t i = 0; i < nGroupItemCount; ++i)
|
|
{
|
|
const ScDPGroupItem* pGrpItem = pGrpDim->GetGroupByIndex(i);
|
|
ScDPItemData aName( pFilter->getMatchString(),pFilter->getMatchValue(),pFilter->hasValue()) ;
|
|
|
|
if (!pGrpItem || !pGrpItem->GetName().IsCaseInsEqual(aName))
|
|
continue;
|
|
|
|
ScDPCacheTable::Criterion aCri;
|
|
aCri.mnFieldIndex = nSrcDim;
|
|
aCri.mpFilter.reset(new ScDPCacheTable::GroupFilter());
|
|
ScDPCacheTable::GroupFilter* pGrpFilter =
|
|
static_cast<ScDPCacheTable::GroupFilter*>(aCri.mpFilter.get());
|
|
|
|
pGrpItem->FillGroupFilter(*pGrpFilter);
|
|
aNewCriteria.push_back(aCri);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rCriteria.swap(aNewCriteria);
|
|
}
|
|
|
|
void ScDPGroupTableData::FilterCacheTable(const vector<ScDPCacheTable::Criterion>& rCriteria, const boost::unordered_set<sal_Int32>& rCatDims)
|
|
{
|
|
vector<ScDPCacheTable::Criterion> aNewCriteria(rCriteria);
|
|
ModifyFilterCriteria(aNewCriteria);
|
|
pSourceData->FilterCacheTable(aNewCriteria, rCatDims);
|
|
}
|
|
|
|
void ScDPGroupTableData::GetDrillDownData(const vector<ScDPCacheTable::Criterion>& rCriteria, const boost::unordered_set<sal_Int32>& rCatDims, Sequence< Sequence<Any> >& rData)
|
|
{
|
|
vector<ScDPCacheTable::Criterion> aNewCriteria(rCriteria);
|
|
ModifyFilterCriteria(aNewCriteria);
|
|
pSourceData->GetDrillDownData(aNewCriteria, rCatDims, rData);
|
|
}
|
|
|
|
void ScDPGroupTableData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
|
|
{
|
|
// #i111435# Inside FillRowDataFromCacheTable/GetItemData, virtual methods
|
|
// getIsDataLayoutDimension and GetSourceDim are used, so it has to be called
|
|
// with original rInfo, containing dimension indexes of the grouped data.
|
|
|
|
const ScDPCacheTable& rCacheTable = pSourceData->GetCacheTable();
|
|
sal_Int32 nRowSize = rCacheTable.getRowSize();
|
|
for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
|
|
{
|
|
if (!rCacheTable.isRowActive(nRow))
|
|
continue;
|
|
|
|
CalcRowData aData;
|
|
FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData);
|
|
|
|
if ( !rInfo.aColLevelDims.empty() )
|
|
FillGroupValues(&aData.aColData[0], rInfo.aColLevelDims.size(), &rInfo.aColLevelDims[0]);
|
|
if ( !rInfo.aRowLevelDims.empty() )
|
|
FillGroupValues(&aData.aRowData[0], rInfo.aRowLevelDims.size(), &rInfo.aRowLevelDims[0]);
|
|
if ( !rInfo.aPageDims.empty() )
|
|
FillGroupValues(&aData.aPageData[0], rInfo.aPageDims.size(), &rInfo.aPageDims[0]);
|
|
|
|
ProcessRowData(rInfo, aData, bAutoShow);
|
|
}
|
|
}
|
|
|
|
const ScDPCacheTable& ScDPGroupTableData::GetCacheTable() const
|
|
{
|
|
return pSourceData->GetCacheTable();
|
|
}
|
|
|
|
void ScDPGroupTableData::FillGroupValues( /*ScDPItemData* pItemData*/ SCROW* pItemDataIndex, long nCount, const long* pDims )
|
|
{
|
|
long nGroupedColumns = aGroups.size();
|
|
|
|
const ScDPCache* pCache = GetCacheTable().getCache();
|
|
for (long nDim=0; nDim<nCount; nDim++)
|
|
{
|
|
const ScDPDateGroupHelper* pDateHelper = NULL;
|
|
|
|
long nColumn = pDims[nDim];
|
|
long nSourceDim = nColumn;
|
|
if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns )
|
|
{
|
|
const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
|
|
nSourceDim= rGroupDim.GetSourceDim();
|
|
pDateHelper = rGroupDim.GetDateHelper();
|
|
if ( !pDateHelper ) // date is handled below
|
|
{
|
|
const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData( *GetMemberById( nSourceDim, pItemDataIndex[nDim] ));
|
|
if ( pGroupItem )
|
|
pItemDataIndex[nDim] = pCache->GetAdditionalItemID( pGroupItem->GetName() );
|
|
}
|
|
}
|
|
else if ( IsNumGroupDimension( nColumn ) )
|
|
{
|
|
pDateHelper = pNumGroups[nColumn].GetDateHelper();
|
|
if ( !pDateHelper ) // date is handled below
|
|
{
|
|
const ScDPItemData* pData = pCache->GetItemDataById( (SCCOL)nSourceDim, pItemDataIndex[nDim]);
|
|
if ( pData ->IsValue() )
|
|
{
|
|
ScDPNumGroupInfo aNumInfo;
|
|
bool bHasNonInteger = false;
|
|
sal_Unicode cDecSeparator = 0;
|
|
GetNumGroupInfo( nColumn, aNumInfo, bHasNonInteger, cDecSeparator );
|
|
double fGroupValue;
|
|
String aGroupName = lcl_GetNumGroupForValue( pData->GetValue(),
|
|
aNumInfo, bHasNonInteger, cDecSeparator, fGroupValue, pDoc );
|
|
ScDPItemData aItemData ( aGroupName, fGroupValue, sal_True ) ;
|
|
pItemDataIndex[nDim] = pCache->GetAdditionalItemID( aItemData );
|
|
}
|
|
// else (textual) keep original value
|
|
}
|
|
}
|
|
|
|
if ( pDateHelper )
|
|
{
|
|
const ScDPItemData* pData = GetCacheTable().getCache()->GetItemDataById( (SCCOL)nSourceDim, pItemDataIndex[nDim]);
|
|
if ( pData ->IsValue() )
|
|
{
|
|
sal_Int32 nPartValue = lcl_GetDatePartValue(
|
|
pData->GetValue(), pDateHelper->GetDatePart(), pDoc->GetFormatTable(),
|
|
&pDateHelper->GetNumInfo() );
|
|
ScDPItemData aItemData( pDateHelper->GetDatePart(), String(), nPartValue, ScDPItemData::MK_DATA|ScDPItemData::MK_VAL|ScDPItemData::MK_DATEPART );
|
|
pItemDataIndex[nDim] = GetCacheTable().getCache()->GetAdditionalItemID( aItemData );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::IsBaseForGroup(long nDim) const
|
|
{
|
|
for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
{
|
|
const ScDPGroupDimension& rDim = *aIter;
|
|
if ( rDim.GetSourceDim() == nDim )
|
|
return sal_True;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
long ScDPGroupTableData::GetGroupBase(long nGroupDim) const
|
|
{
|
|
for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
{
|
|
const ScDPGroupDimension& rDim = *aIter;
|
|
if ( rDim.GetGroupDim() == nGroupDim )
|
|
return rDim.GetSourceDim();
|
|
}
|
|
|
|
return -1; // none
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::IsNumOrDateGroup(long nDimension) const
|
|
{
|
|
// Virtual method from ScDPTableData, used in result data to force text labels.
|
|
|
|
if ( nDimension < nSourceCount )
|
|
{
|
|
return pNumGroups[nDimension].GetInfo().Enable ||
|
|
pNumGroups[nDimension].GetDateHelper();
|
|
}
|
|
|
|
for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
{
|
|
const ScDPGroupDimension& rDim = *aIter;
|
|
if ( rDim.GetGroupDim() == nDimension )
|
|
return ( rDim.GetDateHelper() != NULL );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::IsInGroup( const ScDPItemData& rGroupData, long nGroupIndex,
|
|
const ScDPItemData& rBaseData, long nBaseIndex ) const
|
|
{
|
|
for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
{
|
|
const ScDPGroupDimension& rDim = *aIter;
|
|
if ( rDim.GetGroupDim() == nGroupIndex && rDim.GetSourceDim() == nBaseIndex )
|
|
{
|
|
const ScDPDateGroupHelper* pGroupDateHelper = rDim.GetDateHelper();
|
|
if ( pGroupDateHelper )
|
|
{
|
|
//! transform rBaseData (innermost date part)
|
|
//! -> always do "HasCommonElement" style comparison
|
|
//! (only Quarter, Month, Day affected)
|
|
|
|
const ScDPDateGroupHelper* pBaseDateHelper = NULL;
|
|
if ( nBaseIndex < nSourceCount )
|
|
pBaseDateHelper = pNumGroups[nBaseIndex].GetDateHelper();
|
|
|
|
// If there's a date group dimension, the base dimension must have
|
|
// date group information, too.
|
|
if ( !pBaseDateHelper )
|
|
{
|
|
OSL_FAIL( "mix of date and non-date groups" );
|
|
return true;
|
|
}
|
|
|
|
sal_Int32 nGroupPart = pGroupDateHelper->GetDatePart();
|
|
sal_Int32 nBasePart = pBaseDateHelper->GetDatePart();
|
|
return lcl_DateContained( nGroupPart, rGroupData, nBasePart, rBaseData );
|
|
}
|
|
else
|
|
{
|
|
// If the item is in a group, only that group is valid.
|
|
// If the item is not in any group, its own name is valid.
|
|
|
|
const ScDPGroupItem* pGroup = rDim.GetGroupForData( rBaseData );
|
|
return pGroup ? pGroup->GetName().IsCaseInsEqual( rGroupData ) :
|
|
rGroupData.IsCaseInsEqual( rBaseData );
|
|
}
|
|
}
|
|
}
|
|
|
|
OSL_FAIL("IsInGroup: no group dimension found");
|
|
return true;
|
|
}
|
|
|
|
sal_Bool ScDPGroupTableData::HasCommonElement( const ScDPItemData& rFirstData, long nFirstIndex,
|
|
const ScDPItemData& rSecondData, long nSecondIndex ) const
|
|
{
|
|
const ScDPGroupDimension* pFirstDim = NULL;
|
|
const ScDPGroupDimension* pSecondDim = NULL;
|
|
for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
|
|
{
|
|
const ScDPGroupDimension* pDim = &(*aIter);
|
|
if ( pDim->GetGroupDim() == nFirstIndex )
|
|
pFirstDim = pDim;
|
|
else if ( pDim->GetGroupDim() == nSecondIndex )
|
|
pSecondDim = pDim;
|
|
}
|
|
if ( pFirstDim && pSecondDim )
|
|
{
|
|
const ScDPDateGroupHelper* pFirstDateHelper = pFirstDim->GetDateHelper();
|
|
const ScDPDateGroupHelper* pSecondDateHelper = pSecondDim->GetDateHelper();
|
|
if ( pFirstDateHelper || pSecondDateHelper )
|
|
{
|
|
// If one is a date group dimension, the other one must be, too.
|
|
if ( !pFirstDateHelper || !pSecondDateHelper )
|
|
{
|
|
OSL_FAIL( "mix of date and non-date groups" );
|
|
return true;
|
|
}
|
|
|
|
sal_Int32 nFirstPart = pFirstDateHelper->GetDatePart();
|
|
sal_Int32 nSecondPart = pSecondDateHelper->GetDatePart();
|
|
return lcl_DateContained( nFirstPart, rFirstData, nSecondPart, rSecondData );
|
|
}
|
|
|
|
const ScDPGroupItem* pFirstItem = pFirstDim->GetGroupForName( rFirstData );
|
|
const ScDPGroupItem* pSecondItem = pSecondDim->GetGroupForName( rSecondData );
|
|
if ( pFirstItem && pSecondItem )
|
|
{
|
|
// two existing groups -> sal_True if they have a common element
|
|
return pFirstItem->HasCommonElement( *pSecondItem );
|
|
}
|
|
else if ( pFirstItem )
|
|
{
|
|
// "automatic" group contains only its own name
|
|
return pFirstItem->HasElement( rSecondData );
|
|
}
|
|
else if ( pSecondItem )
|
|
{
|
|
// "automatic" group contains only its own name
|
|
return pSecondItem->HasElement( rFirstData );
|
|
}
|
|
else
|
|
{
|
|
// no groups -> sal_True if equal
|
|
return rFirstData.IsCaseInsEqual( rSecondData );
|
|
}
|
|
}
|
|
|
|
OSL_FAIL("HasCommonElement: no group dimension found");
|
|
return true;
|
|
}
|
|
|
|
long ScDPGroupTableData::GetSourceDim( long nDim )
|
|
{
|
|
if ( getIsDataLayoutDimension( nDim ) )
|
|
return nSourceCount;
|
|
if ( nDim >= nSourceCount && nDim < nSourceCount +(long) aGroups.size() )
|
|
{
|
|
const ScDPGroupDimension& rGroupDim = aGroups[nDim - nSourceCount];
|
|
return rGroupDim.GetSourceDim();
|
|
}
|
|
return nDim;
|
|
}
|
|
long ScDPGroupTableData::Compare( long nDim, long nDataId1, long nDataId2)
|
|
{
|
|
if ( getIsDataLayoutDimension(nDim) )
|
|
return 0;
|
|
return ScDPItemData::Compare( *GetMemberById(nDim, nDataId1),*GetMemberById(nDim, nDataId2) );
|
|
}
|
|
// -----------------------------------------------------------------------
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|