diff options
Diffstat (limited to 'sc/source/core/data/dpgroup.cxx')
-rw-r--r-- | sc/source/core/data/dpgroup.cxx | 1534 |
1 files changed, 1534 insertions, 0 deletions
diff --git a/sc/source/core/data/dpgroup.cxx b/sc/source/core/data/dpgroup.cxx new file mode 100644 index 000000000000..29284b20e4bb --- /dev/null +++ b/sc/source/core/data/dpgroup.cxx @@ -0,0 +1,1534 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: dpgroup.cxx,v $ + * $Revision: 1.11 $ + * + * 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 <tools/debug.hxx> +#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 <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> + +#include <vector> +#include <hash_set> +#include <hash_map> + +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 ::std::hash_set; +using ::std::hash_map; +using ::boost::shared_ptr; + +#define D_TIMEFACTOR 86400.0 + +const USHORT 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; + +// ============================================================================ + +class ScDPGroupDateFilter : public ScDPCacheTable::FilterBase +{ +public: + ScDPGroupDateFilter(double fMatchValue, sal_Int32 nDatePart, + const Date* pNullDate, const ScDPNumGroupInfo* pNumInfo); + + virtual bool match(const ScDPCacheCell &rCell) 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) +{ +// fprintf(stdout, "ScDPCacheTable:DateGroupFilter::DateGroupFilter: match value = %g; date part = %ld\n", +// mfMatchValue, mnDatePart); +} + +bool ScDPGroupDateFilter::match(const ScDPCacheCell& rCell) const +{ + using namespace ::com::sun::star::sheet; + using ::rtl::math::approxFloor; + using ::rtl::math::approxEqual; + + if (!rCell.mbNumeric) + 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 ( rCell.mfValue < mpNumInfo->Start && !approxEqual(rCell.mfValue, mpNumInfo->Start) ) + return static_cast<sal_Int32>(mfMatchValue) == SC_DP_DATE_FIRST; + + if ( rCell.mfValue > mpNumInfo->End && !approxEqual(rCell.mfValue, 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 = rCell.mfValue - approxFloor(rCell.mfValue); + 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: + DBG_ERROR("invalid time part"); + } + return false; + } + + Date date = *mpNullDate + static_cast<long>(approxFloor(rCell.mfValue)); + 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: + DBG_ERROR("invalid date part"); + } + + return false; +} + +// ============================================================================ + +void lcl_AppendDateStr( rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter ) +{ + ULONG nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge ); + String aString; + pFormatter->GetInputLineString( fValue, nFormat, aString ); + rBuffer.append( aString ); +} + +// ----------------------------------------------------------------------- + +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; + + 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: + DBG_ERROR("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: + DBG_ERROR("invalid date part"); + } + } + + return nResult; +} + +BOOL lcl_DateContained( sal_Int32 nGroupPart, const ScDPItemData& rGroupData, + sal_Int32 nBasePart, const ScDPItemData& rBaseData ) +{ + if ( !rGroupData.bHasValue || !rBaseData.bHasValue ) + { + // 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.fValue; + sal_Int32 nBaseValue = (sal_Int32) rBaseData.fValue; + 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 ); + } + + BOOL bContained = 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( TypedScStrCollection& rEntries, const TypedScStrCollection& rOriginal, + SvNumberFormatter* pFormatter ) 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; + + USHORT nOriginalCount = rOriginal.GetCount(); + for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++) + { + const TypedStrData& rStrData = *rOriginal[nOriginalPos]; + if ( rStrData.IsStrData() ) + { + // string data: just copy + TypedStrData* pNew = new TypedStrData( rStrData ); + if ( !rEntries.Insert( pNew ) ) + delete pNew; + } + else + { + double fSourceValue = rStrData.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? + + 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: + DBG_ERROR("invalid date part"); + } + + for ( sal_Int32 nValue = nStart; nValue <= nEnd; nValue++ ) + { + String aName = lcl_GetDateGroupName( nDatePart, nValue, pFormatter ); + TypedStrData* pNew = new TypedStrData( aName, nValue, SC_STRTYPE_VALUE ); + if ( !rEntries.Insert( pNew ) ) + delete pNew; + } + + // add first/last entry (min/max) + + String aFirstName = lcl_GetSpecialDateName( aNumInfo.Start, true, pFormatter ); + TypedStrData* pFirstEntry = new TypedStrData( aFirstName, SC_DP_DATE_FIRST, SC_STRTYPE_VALUE ); + if ( !rEntries.Insert( pFirstEntry ) ) + delete pFirstEntry; + + String aLastName = lcl_GetSpecialDateName( aNumInfo.End, false, pFormatter ); + TypedStrData* pLastEntry = new TypedStrData( aLastName, SC_DP_DATE_LAST, SC_STRTYPE_VALUE ); + if ( !rEntries.Insert( pLastEntry ) ) + delete pLastEntry; +} + +// ----------------------------------------------------------------------- + +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->aString, itr->fValue, itr->bHasValue); +} + +// ----------------------------------------------------------------------- + +ScDPGroupDimension::ScDPGroupDimension( long nSource, const String& rNewName ) : + nSourceDim( nSource ), + nGroupDim( -1 ), + aGroupName( rNewName ), + pDateHelper( NULL ), + pCollection( NULL ) +{ +} + +ScDPGroupDimension::~ScDPGroupDimension() +{ + delete pDateHelper; + delete pCollection; +} + +ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) : + nSourceDim( rOther.nSourceDim ), + nGroupDim( rOther.nGroupDim ), + aGroupName( rOther.aGroupName ), + pDateHelper( NULL ), + aItems( rOther.aItems ), + pCollection( NULL ) // collection isn't copied - allocated on demand +{ + 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; + + delete pCollection; // collection isn't copied - allocated on demand + pCollection = 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 TypedScStrCollection& ScDPGroupDimension::GetColumnEntries( + const TypedScStrCollection& rOriginal, ScDocument* pDoc ) const +{ + if ( !pCollection ) + { + pCollection = new TypedScStrCollection(); + if ( pDateHelper ) + pDateHelper->FillColumnEntries( *pCollection, rOriginal, pDoc->GetFormatTable() ); + else + { + long nCount = aItems.size(); + for (long i=0; i<nCount; i++) + { + //! numeric entries? + TypedStrData* pNew = new TypedStrData( aItems[i].GetName().aString ); + if ( !pCollection->Insert( pNew ) ) + delete pNew; + } + + USHORT nOriginalCount = rOriginal.GetCount(); + for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++) + { + const TypedStrData& rStrData = *rOriginal[nOriginalPos]; + ScDPItemData aItemData( rStrData.GetString(), rStrData.GetValue(), !rStrData.IsStrData() ); + if ( !GetGroupForData( aItemData ) ) + { + // not in any group -> add as its own group + TypedStrData* pNew = new TypedStrData( rStrData ); + if ( !pCollection->Insert( pNew ) ) + delete pNew; + } + } + } + } + return *pCollection; +} + +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() +{ + delete pCollection; + pCollection = NULL; +} + +// ----------------------------------------------------------------------- + +ScDPNumGroupDimension::ScDPNumGroupDimension() : + pDateHelper( NULL ), + pCollection( NULL ), + bHasNonInteger( false ), + cDecSeparator( 0 ) +{ +} + +ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) : + aGroupInfo( rInfo ), + pDateHelper( NULL ), + pCollection( NULL ), + bHasNonInteger( false ), + cDecSeparator( 0 ) +{ +} + +ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) : + aGroupInfo( rOther.aGroupInfo ), + pDateHelper( NULL ), + pCollection( NULL ), // collection isn't copied - allocated on demand + 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; + + delete pCollection; // collection isn't copied - allocated on demand + pCollection = NULL; + bHasNonInteger = false; + return *this; +} + +void ScDPNumGroupDimension::DisposeData() +{ + delete pCollection; + pCollection = NULL; + bHasNonInteger = false; +} + +ScDPNumGroupDimension::~ScDPNumGroupDimension() +{ + delete pDateHelper; + delete pCollection; +} + +void ScDPNumGroupDimension::MakeDateHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart ) +{ + delete pDateHelper; + pDateHelper = new ScDPDateGroupHelper( rInfo, nPart ); + + aGroupInfo.Enable = sal_True; //! or query both? +} + +String lcl_GetNumGroupName( double fStartValue, const ScDPNumGroupInfo& rInfo, + bool bHasNonInteger, sal_Unicode cDecSeparator, SvNumberFormatter* pFormatter ) +{ + DBG_ASSERT( 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 ) +{ + DBG_ASSERT( 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) ); +} + +const TypedScStrCollection& ScDPNumGroupDimension::GetNumEntries( + const TypedScStrCollection& rOriginal, ScDocument* pDoc ) const +{ + if ( !pCollection ) + { + SvNumberFormatter* pFormatter = pDoc->GetFormatTable(); + + pCollection = new TypedScStrCollection(); + if ( pDateHelper ) + pDateHelper->FillColumnEntries( *pCollection, rOriginal, pFormatter ); + 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; + + USHORT nOriginalCount = rOriginal.GetCount(); + for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++) + { + const TypedStrData& rStrData = *rOriginal[nOriginalPos]; + if ( rStrData.IsStrData() ) + { + // string data: just copy + TypedStrData* pNew = new TypedStrData( rStrData ); + if ( !pCollection->Insert( pNew ) ) + delete pNew; + } + else + { + double fSourceValue = rStrData.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) + TypedStrData* pNew = new TypedStrData( aName, fLoop, SC_STRTYPE_VALUE ); + if ( !pCollection->Insert( pNew ) ) + delete pNew; + + ++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 ); + TypedStrData* pFirstEntry = new TypedStrData( aFirstName, aGroupInfo.Start - aGroupInfo.Step, SC_STRTYPE_VALUE ); + if ( !pCollection->Insert( pFirstEntry ) ) + delete pFirstEntry; + + String aLastName = lcl_GetSpecialNumGroupName( aGroupInfo.End, false, cDecSeparator, aGroupInfo.DateValues, pFormatter ); + TypedStrData* pLastEntry = new TypedStrData( aLastName, aGroupInfo.End + aGroupInfo.Step, SC_STRTYPE_VALUE ); + if ( !pCollection->Insert( pLastEntry ) ) + delete pLastEntry; + } + } + return *pCollection; +} + +// ----------------------------------------------------------------------- + +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 ); +} + +ScDPGroupTableData::ScDPGroupTableData( const shared_ptr<ScDPTableData>& pSource, ScDocument* pDocument ) : + ScDPTableData(pDocument), + pSourceData( pSource ), + pDoc( pDocument ) +{ + DBG_ASSERT( 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(); + } +} + +const TypedScStrCollection& ScDPGroupTableData::GetColumnEntries(long nColumn) +{ + // date handling is in ScDPGroupDimension::GetColumnEntries / ScDPNumGroupDimension::GetNumEntries + // (to use the pCollection members) + + 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 + { + const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount]; + long nSourceDim = rGroupDim.GetSourceDim(); + // collection is cached at pSourceData, GetColumnEntries can be called every time + const TypedScStrCollection& rOriginal = pSourceData->GetColumnEntries( nSourceDim ); + return rGroupDim.GetColumnEntries( rOriginal, pDoc ); + } + } + + if ( IsNumGroupDimension( nColumn ) ) + { + // dimension number is unchanged for numerical groups + const TypedScStrCollection& rOriginal = pSourceData->GetColumnEntries( nColumn ); + return pNumGroups[nColumn].GetNumEntries( rOriginal, pDoc ); + } + + return pSourceData->GetColumnEntries( nColumn ); +} + +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 ); +} + +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? +} + +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 ); +} + +UINT32 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( BOOL bIgnoreEmptyRows, 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 hash_map<long, const ScDPGroupDimension*> GroupFieldMapType; + GroupFieldMapType aGroupFieldIds; + { + ScDPGroupDimensionVec::const_iterator itr = aGroups.begin(), itrEnd = aGroups.end(); + for (; itr != itrEnd; ++itr) + aGroupFieldIds.insert( hash_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; + aName.aString = pFilter->getMatchString(); + aName.fValue = pFilter->getMatchValue(); + aName.bHasValue = pFilter->hasValue(); + if (!pGrpItem || !pGrpItem->GetName().IsCaseInsEqual(aName)) + continue; + + ScDPCacheTable::Criterion aCri; + aCri.mnFieldIndex = nSrcDim; + aCri.mpFilter.reset(new ScDPCacheTable::GroupFilter(GetSharedString())); + 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 hash_set<sal_Int32>& rCatDims) +{ + vector<ScDPCacheTable::Criterion> aNewCriteria(rCriteria); + ModifyFilterCriteria(aNewCriteria); + pSourceData->FilterCacheTable(aNewCriteria, rCatDims); +} + +void ScDPGroupTableData::GetDrillDownData(const vector<ScDPCacheTable::Criterion>& rCriteria, const hash_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) +{ + // This CalcInfo instance is used only to retrive data from the original + // data source. + CalcInfo aInfoSrc = rInfo; + CopyFields(rInfo.aColLevelDims, aInfoSrc.aColLevelDims); + CopyFields(rInfo.aRowLevelDims, aInfoSrc.aRowLevelDims); + CopyFields(rInfo.aPageDims, aInfoSrc.aPageDims); + CopyFields(rInfo.aDataSrcCols, aInfoSrc.aDataSrcCols); + + 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, aInfoSrc, 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::CopyFields(const vector<long>& rFieldDims, vector<long>& rNewFieldDims) +{ + size_t nCount = rFieldDims.size(); + if (!nCount) + return; + + long nGroupedColumns = aGroups.size(); + + rNewFieldDims.clear(); + rNewFieldDims.reserve(nCount); + for (size_t i = 0; i < nCount; ++i) + { + if ( rFieldDims[i] >= nSourceCount ) + { + if ( rFieldDims[i] == nSourceCount + nGroupedColumns ) + // data layout in source + rNewFieldDims.push_back(nSourceCount); + else + { + // original dimension + long n = rFieldDims[i] - nSourceCount; + rNewFieldDims.push_back(aGroups[n].GetSourceDim()); + } + } + else + rNewFieldDims.push_back(rFieldDims[i]); + } +} + +void ScDPGroupTableData::FillGroupValues( ScDPItemData* pItemData, long nCount, const long* pDims ) +{ + long nGroupedColumns = aGroups.size(); + + for (long nDim=0; nDim<nCount; nDim++) + { + const ScDPDateGroupHelper* pDateHelper = NULL; + + long nColumn = pDims[nDim]; + if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns ) + { + const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount]; + pDateHelper = rGroupDim.GetDateHelper(); + if ( !pDateHelper ) // date is handled below + { + const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData( pItemData[nDim] ); + if ( pGroupItem ) + pItemData[nDim] = pGroupItem->GetName(); + // if no group is found, keep the original name + } + } + else if ( IsNumGroupDimension( nColumn ) ) + { + pDateHelper = pNumGroups[nColumn].GetDateHelper(); + if ( !pDateHelper ) // date is handled below + { + if ( pItemData[nDim].bHasValue ) + { + ScDPNumGroupInfo aNumInfo; + bool bHasNonInteger = false; + sal_Unicode cDecSeparator = 0; + GetNumGroupInfo( nColumn, aNumInfo, bHasNonInteger, cDecSeparator ); + double fGroupValue; + String aGroupName = lcl_GetNumGroupForValue( pItemData[nDim].fValue, + aNumInfo, bHasNonInteger, cDecSeparator, fGroupValue, pDoc ); + + // consistent with TypedStrData in GetNumEntries + pItemData[nDim] = ScDPItemData( aGroupName, fGroupValue, TRUE ); + } + // else (textual) keep original value + } + } + + if ( pDateHelper ) + { + if ( pItemData[nDim].bHasValue ) + { + sal_Int32 nPartValue = lcl_GetDatePartValue( + pItemData[nDim].fValue, pDateHelper->GetDatePart(), pDoc->GetFormatTable(), + &pDateHelper->GetNumInfo() ); + pItemData[nDim] = ScDPItemData( String(), nPartValue, TRUE ); + } + } + } +} + +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 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 +} + +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; +} + +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 ) + { + DBG_ERROR( "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 ); + } + } + } + + DBG_ERROR("IsInGroup: no group dimension found"); + return TRUE; +} + +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 ) + { + DBG_ERROR( "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 -> 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 -> TRUE if equal + return rFirstData.IsCaseInsEqual( rSecondData ); + } + } + + DBG_ERROR("HasCommonElement: no group dimension found"); + return TRUE; +} + +// ----------------------------------------------------------------------- + |