Update ooo320-m1
[ooovba.git] / sc / source / core / data / dpgroup.cxx
blob2d62cc8ae02db9c904b4ee2c49c4f0e0bf1b38a0
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: dpgroup.cxx,v $
10 * $Revision: 1.11 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_sc.hxx"
36 // INCLUDE ---------------------------------------------------------------
38 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
40 #include <tools/debug.hxx>
41 #include <rtl/math.hxx>
42 #include <unotools/localedatawrapper.hxx>
43 #include <svtools/zforlist.hxx>
45 #include "dpgroup.hxx"
46 #include "collect.hxx"
47 #include "global.hxx"
48 #include "document.hxx"
49 #include "dpcachetable.hxx"
50 #include "dptabsrc.hxx"
51 #include "dptabres.hxx"
52 #include "dpobject.hxx"
54 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
55 #include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
57 #include <vector>
58 #include <hash_set>
59 #include <hash_map>
61 using namespace ::com::sun::star;
62 using ::com::sun::star::uno::Any;
63 using ::com::sun::star::uno::Reference;
64 using ::com::sun::star::uno::Sequence;
65 using ::com::sun::star::uno::UNO_QUERY;
66 using ::com::sun::star::uno::UNO_QUERY_THROW;
67 using ::rtl::OUString;
68 using ::rtl::OUStringHash;
70 using ::std::vector;
71 using ::std::hash_set;
72 using ::std::hash_map;
73 using ::boost::shared_ptr;
75 #define D_TIMEFACTOR 86400.0
77 const USHORT SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
79 // part values for the extra "<" and ">" entries (same for all parts)
80 const sal_Int32 SC_DP_DATE_FIRST = -1;
81 const sal_Int32 SC_DP_DATE_LAST = 10000;
83 // ============================================================================
85 class ScDPGroupDateFilter : public ScDPCacheTable::FilterBase
87 public:
88 ScDPGroupDateFilter(double fMatchValue, sal_Int32 nDatePart,
89 const Date* pNullDate, const ScDPNumGroupInfo* pNumInfo);
91 virtual bool match(const ScDPCacheCell &rCell) const;
93 private:
94 ScDPGroupDateFilter(); // disabled
96 const Date* mpNullDate;
97 const ScDPNumGroupInfo* mpNumInfo;
98 double mfMatchValue;
99 sal_Int32 mnDatePart;
102 // ----------------------------------------------------------------------------
104 ScDPGroupDateFilter::ScDPGroupDateFilter(double fMatchValue, sal_Int32 nDatePart,
105 const Date* pNullDate, const ScDPNumGroupInfo* pNumInfo) :
106 mpNullDate(pNullDate),
107 mpNumInfo(pNumInfo),
108 mfMatchValue(fMatchValue),
109 mnDatePart(nDatePart)
111 // fprintf(stdout, "ScDPCacheTable:DateGroupFilter::DateGroupFilter: match value = %g; date part = %ld\n",
112 // mfMatchValue, mnDatePart);
115 bool ScDPGroupDateFilter::match(const ScDPCacheCell& rCell) const
117 using namespace ::com::sun::star::sheet;
118 using ::rtl::math::approxFloor;
119 using ::rtl::math::approxEqual;
121 if (!rCell.mbNumeric)
122 return false;
124 if (!mpNumInfo)
125 return false;
127 // Start and end dates are inclusive. (An end date without a time value
128 // is included, while an end date with a time value is not.)
130 if ( rCell.mfValue < mpNumInfo->Start && !approxEqual(rCell.mfValue, mpNumInfo->Start) )
131 return static_cast<sal_Int32>(mfMatchValue) == SC_DP_DATE_FIRST;
133 if ( rCell.mfValue > mpNumInfo->End && !approxEqual(rCell.mfValue, mpNumInfo->End) )
134 return static_cast<sal_Int32>(mfMatchValue) == SC_DP_DATE_LAST;
136 if (mnDatePart == DataPilotFieldGroupBy::HOURS || mnDatePart == DataPilotFieldGroupBy::MINUTES ||
137 mnDatePart == DataPilotFieldGroupBy::SECONDS)
139 // handle time
140 // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
142 double time = rCell.mfValue - approxFloor(rCell.mfValue);
143 long seconds = static_cast<long>(approxFloor(time*D_TIMEFACTOR + 0.5));
145 switch (mnDatePart)
147 case DataPilotFieldGroupBy::HOURS:
149 sal_Int32 hrs = seconds / 3600;
150 sal_Int32 matchHrs = static_cast<sal_Int32>(mfMatchValue);
151 return hrs == matchHrs;
153 case DataPilotFieldGroupBy::MINUTES:
155 sal_Int32 minutes = (seconds % 3600) / 60;
156 sal_Int32 matchMinutes = static_cast<sal_Int32>(mfMatchValue);
157 return minutes == matchMinutes;
159 case DataPilotFieldGroupBy::SECONDS:
161 sal_Int32 sec = seconds % 60;
162 sal_Int32 matchSec = static_cast<sal_Int32>(mfMatchValue);
163 return sec == matchSec;
165 default:
166 DBG_ERROR("invalid time part");
168 return false;
171 Date date = *mpNullDate + static_cast<long>(approxFloor(rCell.mfValue));
172 switch (mnDatePart)
174 case DataPilotFieldGroupBy::YEARS:
176 sal_Int32 year = static_cast<sal_Int32>(date.GetYear());
177 sal_Int32 matchYear = static_cast<sal_Int32>(mfMatchValue);
178 return year == matchYear;
180 case DataPilotFieldGroupBy::QUARTERS:
182 sal_Int32 qtr = 1 + (static_cast<sal_Int32>(date.GetMonth()) - 1) / 3;
183 sal_Int32 matchQtr = static_cast<sal_Int32>(mfMatchValue);
184 return qtr == matchQtr;
186 case DataPilotFieldGroupBy::MONTHS:
188 sal_Int32 month = static_cast<sal_Int32>(date.GetMonth());
189 sal_Int32 matchMonth = static_cast<sal_Int32>(mfMatchValue);
190 return month == matchMonth;
192 case DataPilotFieldGroupBy::DAYS:
194 Date yearStart(1, 1, date.GetYear());
195 sal_Int32 days = (date - yearStart) + 1; // Jan 01 has value 1
196 if (days >= 60 && !date.IsLeapYear())
198 // This is not a leap year. Adjust the value accordingly.
199 ++days;
201 sal_Int32 matchDays = static_cast<sal_Int32>(mfMatchValue);
202 return days == matchDays;
204 default:
205 DBG_ERROR("invalid date part");
208 return false;
211 // ============================================================================
213 void lcl_AppendDateStr( rtl::OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
215 ULONG nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
216 String aString;
217 pFormatter->GetInputLineString( fValue, nFormat, aString );
218 rBuffer.append( aString );
221 // -----------------------------------------------------------------------
223 ScDPDateGroupHelper::ScDPDateGroupHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart ) :
224 aNumInfo( rInfo ),
225 nDatePart( nPart )
229 ScDPDateGroupHelper::~ScDPDateGroupHelper()
233 String lcl_GetTwoDigitString( sal_Int32 nValue )
235 String aRet = String::CreateFromInt32( nValue );
236 if ( aRet.Len() < 2 )
237 aRet.Insert( (sal_Unicode)'0', 0 );
238 return aRet;
241 String lcl_GetDateGroupName( sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter )
243 String aRet;
244 switch ( nDatePart )
246 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
247 aRet = String::CreateFromInt32( nValue );
248 break;
249 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
250 aRet = ScGlobal::pLocaleData->getQuarterAbbreviation( (sal_Int16)(nValue - 1) ); // nValue is 1-based
251 break;
252 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
253 //! cache getMonths() result?
254 aRet = ScGlobal::GetCalendar()->getDisplayName(
255 ::com::sun::star::i18n::CalendarDisplayIndex::MONTH,
256 sal_Int16(nValue-1), 0 ); // 0-based, get short name
257 break;
258 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
260 Date aDate( 1, 1, SC_DP_LEAPYEAR );
261 aDate += ( nValue - 1 ); // nValue is 1-based
262 Date aNullDate = *(pFormatter->GetNullDate());
263 long nDays = aDate - aNullDate;
265 ULONG nFormat = pFormatter->GetFormatIndex( NF_DATE_SYS_DDMMM, ScGlobal::eLnge );
266 Color* pColor;
267 pFormatter->GetOutputString( nDays, nFormat, aRet, &pColor );
269 break;
270 case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS:
271 //! allow am/pm format?
272 aRet = lcl_GetTwoDigitString( nValue );
273 break;
274 case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES:
275 case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS:
276 aRet = ScGlobal::pLocaleData->getTimeSep();
277 aRet.Append( lcl_GetTwoDigitString( nValue ) );
278 break;
279 default:
280 DBG_ERROR("invalid date part");
282 return aRet;
285 sal_Int32 lcl_GetDatePartValue( double fValue, sal_Int32 nDatePart, SvNumberFormatter* pFormatter,
286 const ScDPNumGroupInfo* pNumInfo )
288 // Start and end are inclusive
289 // (End date without a time value is included, with a time value it's not)
291 if ( pNumInfo )
293 if ( fValue < pNumInfo->Start && !rtl::math::approxEqual( fValue, pNumInfo->Start ) )
294 return SC_DP_DATE_FIRST;
295 if ( fValue > pNumInfo->End && !rtl::math::approxEqual( fValue, pNumInfo->End ) )
296 return SC_DP_DATE_LAST;
299 sal_Int32 nResult = 0;
301 if ( nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::HOURS || nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES || nDatePart == com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS )
303 // handle time
304 // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
306 double fTime = fValue - ::rtl::math::approxFloor(fValue);
307 long nSeconds = (long)::rtl::math::approxFloor(fTime*D_TIMEFACTOR+0.5);
309 switch ( nDatePart )
311 case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS:
312 nResult = nSeconds / 3600;
313 break;
314 case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES:
315 nResult = ( nSeconds % 3600 ) / 60;
316 break;
317 case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS:
318 nResult = nSeconds % 60;
319 break;
322 else
324 Date aDate = *(pFormatter->GetNullDate());
325 aDate += (long)::rtl::math::approxFloor( fValue );
327 switch ( nDatePart )
329 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
330 nResult = aDate.GetYear();
331 break;
332 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
333 nResult = 1 + ( aDate.GetMonth() - 1 ) / 3; // 1..4
334 break;
335 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
336 nResult = aDate.GetMonth(); // 1..12
337 break;
338 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
340 Date aYearStart( 1, 1, aDate.GetYear() );
341 nResult = ( aDate - aYearStart ) + 1; // Jan 01 has value 1
342 if ( nResult >= 60 && !aDate.IsLeapYear() )
344 // days are counted from 1 to 366 - if not from a leap year, adjust
345 ++nResult;
348 break;
349 default:
350 DBG_ERROR("invalid date part");
354 return nResult;
357 BOOL lcl_DateContained( sal_Int32 nGroupPart, const ScDPItemData& rGroupData,
358 sal_Int32 nBasePart, const ScDPItemData& rBaseData )
360 if ( !rGroupData.bHasValue || !rBaseData.bHasValue )
362 // non-numeric entries involved: only match equal entries
363 return rGroupData.IsCaseInsEqual( rBaseData );
366 // no approxFloor needed, values were created from integers
367 sal_Int32 nGroupValue = (sal_Int32) rGroupData.fValue;
368 sal_Int32 nBaseValue = (sal_Int32) rBaseData.fValue;
369 if ( nBasePart > nGroupPart )
371 // switch, so the base part is the smaller (inner) part
373 ::std::swap( nGroupPart, nBasePart );
374 ::std::swap( nGroupValue, nBaseValue );
377 if ( nGroupValue == SC_DP_DATE_FIRST || nGroupValue == SC_DP_DATE_LAST ||
378 nBaseValue == SC_DP_DATE_FIRST || nBaseValue == SC_DP_DATE_LAST )
380 // first/last entry matches only itself
381 return ( nGroupValue == nBaseValue );
384 BOOL bContained = TRUE;
385 switch ( nBasePart ) // inner part
387 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
388 // a month is only contained in its quarter
389 if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
391 // months and quarters are both 1-based
392 bContained = ( nGroupValue - 1 == ( nBaseValue - 1 ) / 3 );
394 break;
395 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
396 // a day is only contained in its quarter or month
397 if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS || nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
399 Date aDate( 1, 1, SC_DP_LEAPYEAR );
400 aDate += ( nBaseValue - 1 ); // days are 1-based
401 sal_Int32 nCompare = aDate.GetMonth();
402 if ( nGroupPart == com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS )
403 nCompare = ( ( nCompare - 1 ) / 3 ) + 1; // get quarter from date
405 bContained = ( nGroupValue == nCompare );
407 break;
409 // other parts: everything is contained
412 return bContained;
415 String lcl_GetSpecialDateName( double fValue, bool bFirst, SvNumberFormatter* pFormatter )
417 rtl::OUStringBuffer aBuffer;
418 aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
419 lcl_AppendDateStr( aBuffer, fValue, pFormatter );
420 return aBuffer.makeStringAndClear();
423 void ScDPDateGroupHelper::FillColumnEntries( TypedScStrCollection& rEntries, const TypedScStrCollection& rOriginal,
424 SvNumberFormatter* pFormatter ) const
426 // auto min/max is only used for "Years" part, but the loop is always needed
427 double fSourceMin = 0.0;
428 double fSourceMax = 0.0;
429 bool bFirst = true;
431 USHORT nOriginalCount = rOriginal.GetCount();
432 for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++)
434 const TypedStrData& rStrData = *rOriginal[nOriginalPos];
435 if ( rStrData.IsStrData() )
437 // string data: just copy
438 TypedStrData* pNew = new TypedStrData( rStrData );
439 if ( !rEntries.Insert( pNew ) )
440 delete pNew;
442 else
444 double fSourceValue = rStrData.GetValue();
445 if ( bFirst )
447 fSourceMin = fSourceMax = fSourceValue;
448 bFirst = false;
450 else
452 if ( fSourceValue < fSourceMin )
453 fSourceMin = fSourceValue;
454 if ( fSourceValue > fSourceMax )
455 fSourceMax = fSourceValue;
460 // For the start/end values, use the same date rounding as in ScDPNumGroupDimension::GetNumEntries
461 // (but not for the list of available years):
462 if ( aNumInfo.AutoStart )
463 const_cast<ScDPDateGroupHelper*>(this)->aNumInfo.Start = rtl::math::approxFloor( fSourceMin );
464 if ( aNumInfo.AutoEnd )
465 const_cast<ScDPDateGroupHelper*>(this)->aNumInfo.End = rtl::math::approxFloor( fSourceMax ) + 1;
467 //! if not automatic, limit fSourceMin/fSourceMax for list of year values?
469 long nStart = 0;
470 long nEnd = 0; // including
472 switch ( nDatePart )
474 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
475 nStart = lcl_GetDatePartValue( fSourceMin, com::sun::star::sheet::DataPilotFieldGroupBy::YEARS, pFormatter, NULL );
476 nEnd = lcl_GetDatePartValue( fSourceMax, com::sun::star::sheet::DataPilotFieldGroupBy::YEARS, pFormatter, NULL );
477 break;
478 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4; break;
479 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS: nStart = 1; nEnd = 12; break;
480 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS: nStart = 1; nEnd = 366; break;
481 case com::sun::star::sheet::DataPilotFieldGroupBy::HOURS: nStart = 0; nEnd = 23; break;
482 case com::sun::star::sheet::DataPilotFieldGroupBy::MINUTES: nStart = 0; nEnd = 59; break;
483 case com::sun::star::sheet::DataPilotFieldGroupBy::SECONDS: nStart = 0; nEnd = 59; break;
484 default:
485 DBG_ERROR("invalid date part");
488 for ( sal_Int32 nValue = nStart; nValue <= nEnd; nValue++ )
490 String aName = lcl_GetDateGroupName( nDatePart, nValue, pFormatter );
491 TypedStrData* pNew = new TypedStrData( aName, nValue, SC_STRTYPE_VALUE );
492 if ( !rEntries.Insert( pNew ) )
493 delete pNew;
496 // add first/last entry (min/max)
498 String aFirstName = lcl_GetSpecialDateName( aNumInfo.Start, true, pFormatter );
499 TypedStrData* pFirstEntry = new TypedStrData( aFirstName, SC_DP_DATE_FIRST, SC_STRTYPE_VALUE );
500 if ( !rEntries.Insert( pFirstEntry ) )
501 delete pFirstEntry;
503 String aLastName = lcl_GetSpecialDateName( aNumInfo.End, false, pFormatter );
504 TypedStrData* pLastEntry = new TypedStrData( aLastName, SC_DP_DATE_LAST, SC_STRTYPE_VALUE );
505 if ( !rEntries.Insert( pLastEntry ) )
506 delete pLastEntry;
509 // -----------------------------------------------------------------------
511 ScDPGroupItem::ScDPGroupItem( const ScDPItemData& rName ) :
512 aGroupName( rName )
516 ScDPGroupItem::~ScDPGroupItem()
520 void ScDPGroupItem::AddElement( const ScDPItemData& rName )
522 aElements.push_back( rName );
525 bool ScDPGroupItem::HasElement( const ScDPItemData& rData ) const
527 for ( ScDPItemDataVec::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
528 if ( aIter->IsCaseInsEqual( rData ) )
529 return true;
531 return false;
534 bool ScDPGroupItem::HasCommonElement( const ScDPGroupItem& rOther ) const
536 for ( ScDPItemDataVec::const_iterator aIter(aElements.begin()); aIter != aElements.end(); aIter++ )
537 if ( rOther.HasElement( *aIter ) )
538 return true;
540 return false;
543 void ScDPGroupItem::FillGroupFilter( ScDPCacheTable::GroupFilter& rFilter ) const
545 ScDPItemDataVec::const_iterator itrEnd = aElements.end();
546 for (ScDPItemDataVec::const_iterator itr = aElements.begin(); itr != itrEnd; ++itr)
547 rFilter.addMatchItem(itr->aString, itr->fValue, itr->bHasValue);
550 // -----------------------------------------------------------------------
552 ScDPGroupDimension::ScDPGroupDimension( long nSource, const String& rNewName ) :
553 nSourceDim( nSource ),
554 nGroupDim( -1 ),
555 aGroupName( rNewName ),
556 pDateHelper( NULL ),
557 pCollection( NULL )
561 ScDPGroupDimension::~ScDPGroupDimension()
563 delete pDateHelper;
564 delete pCollection;
567 ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) :
568 nSourceDim( rOther.nSourceDim ),
569 nGroupDim( rOther.nGroupDim ),
570 aGroupName( rOther.aGroupName ),
571 pDateHelper( NULL ),
572 aItems( rOther.aItems ),
573 pCollection( NULL ) // collection isn't copied - allocated on demand
575 if ( rOther.pDateHelper )
576 pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
579 ScDPGroupDimension& ScDPGroupDimension::operator=( const ScDPGroupDimension& rOther )
581 nSourceDim = rOther.nSourceDim;
582 nGroupDim = rOther.nGroupDim;
583 aGroupName = rOther.aGroupName;
584 aItems = rOther.aItems;
586 delete pDateHelper;
587 if ( rOther.pDateHelper )
588 pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
589 else
590 pDateHelper = NULL;
592 delete pCollection; // collection isn't copied - allocated on demand
593 pCollection = NULL;
594 return *this;
597 void ScDPGroupDimension::MakeDateHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
599 delete pDateHelper;
600 pDateHelper = new ScDPDateGroupHelper( rInfo, nPart );
603 void ScDPGroupDimension::AddItem( const ScDPGroupItem& rItem )
605 aItems.push_back( rItem );
608 void ScDPGroupDimension::SetGroupDim( long nDim )
610 nGroupDim = nDim;
613 const TypedScStrCollection& ScDPGroupDimension::GetColumnEntries(
614 const TypedScStrCollection& rOriginal, ScDocument* pDoc ) const
616 if ( !pCollection )
618 pCollection = new TypedScStrCollection();
619 if ( pDateHelper )
620 pDateHelper->FillColumnEntries( *pCollection, rOriginal, pDoc->GetFormatTable() );
621 else
623 long nCount = aItems.size();
624 for (long i=0; i<nCount; i++)
626 //! numeric entries?
627 TypedStrData* pNew = new TypedStrData( aItems[i].GetName().aString );
628 if ( !pCollection->Insert( pNew ) )
629 delete pNew;
632 USHORT nOriginalCount = rOriginal.GetCount();
633 for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++)
635 const TypedStrData& rStrData = *rOriginal[nOriginalPos];
636 ScDPItemData aItemData( rStrData.GetString(), rStrData.GetValue(), !rStrData.IsStrData() );
637 if ( !GetGroupForData( aItemData ) )
639 // not in any group -> add as its own group
640 TypedStrData* pNew = new TypedStrData( rStrData );
641 if ( !pCollection->Insert( pNew ) )
642 delete pNew;
647 return *pCollection;
650 const ScDPGroupItem* ScDPGroupDimension::GetGroupForData( const ScDPItemData& rData ) const
652 for ( ScDPGroupItemVec::const_iterator aIter(aItems.begin()); aIter != aItems.end(); aIter++ )
653 if ( aIter->HasElement( rData ) )
654 return &*aIter;
656 return NULL;
659 const ScDPGroupItem* ScDPGroupDimension::GetGroupForName( const ScDPItemData& rName ) const
661 for ( ScDPGroupItemVec::const_iterator aIter(aItems.begin()); aIter != aItems.end(); aIter++ )
662 if ( aIter->GetName().IsCaseInsEqual( rName ) )
663 return &*aIter;
665 return NULL;
668 const ScDPGroupItem* ScDPGroupDimension::GetGroupByIndex( size_t nIndex ) const
670 if (nIndex >= aItems.size())
671 return NULL;
673 return &aItems[nIndex];
676 void ScDPGroupDimension::DisposeData()
678 delete pCollection;
679 pCollection = NULL;
682 // -----------------------------------------------------------------------
684 ScDPNumGroupDimension::ScDPNumGroupDimension() :
685 pDateHelper( NULL ),
686 pCollection( NULL ),
687 bHasNonInteger( false ),
688 cDecSeparator( 0 )
692 ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) :
693 aGroupInfo( rInfo ),
694 pDateHelper( NULL ),
695 pCollection( NULL ),
696 bHasNonInteger( false ),
697 cDecSeparator( 0 )
701 ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) :
702 aGroupInfo( rOther.aGroupInfo ),
703 pDateHelper( NULL ),
704 pCollection( NULL ), // collection isn't copied - allocated on demand
705 bHasNonInteger( false ),
706 cDecSeparator( 0 )
708 if ( rOther.pDateHelper )
709 pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
712 ScDPNumGroupDimension& ScDPNumGroupDimension::operator=( const ScDPNumGroupDimension& rOther )
714 aGroupInfo = rOther.aGroupInfo;
716 delete pDateHelper;
717 if ( rOther.pDateHelper )
718 pDateHelper = new ScDPDateGroupHelper( *rOther.pDateHelper );
719 else
720 pDateHelper = NULL;
722 delete pCollection; // collection isn't copied - allocated on demand
723 pCollection = NULL;
724 bHasNonInteger = false;
725 return *this;
728 void ScDPNumGroupDimension::DisposeData()
730 delete pCollection;
731 pCollection = NULL;
732 bHasNonInteger = false;
735 ScDPNumGroupDimension::~ScDPNumGroupDimension()
737 delete pDateHelper;
738 delete pCollection;
741 void ScDPNumGroupDimension::MakeDateHelper( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
743 delete pDateHelper;
744 pDateHelper = new ScDPDateGroupHelper( rInfo, nPart );
746 aGroupInfo.Enable = sal_True; //! or query both?
749 String lcl_GetNumGroupName( double fStartValue, const ScDPNumGroupInfo& rInfo,
750 bool bHasNonInteger, sal_Unicode cDecSeparator, SvNumberFormatter* pFormatter )
752 DBG_ASSERT( cDecSeparator != 0, "cDecSeparator not initialized" );
754 double fStep = rInfo.Step;
755 double fEndValue = fStartValue + fStep;
756 if ( !bHasNonInteger && ( rInfo.DateValues || !rtl::math::approxEqual( fEndValue, rInfo.End ) ) )
758 // The second number of the group label is
759 // (first number + size - 1) if there are only integer numbers,
760 // (first number + size) if any non-integer numbers are involved.
761 // Exception: The last group (containing the end value) is always
762 // shown as including the end value (but not for dates).
764 fEndValue -= 1.0;
767 if ( fEndValue > rInfo.End && !rInfo.AutoEnd )
769 // limit the last group to the end value
771 fEndValue = rInfo.End;
774 rtl::OUStringBuffer aBuffer;
775 if ( rInfo.DateValues )
777 lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
778 aBuffer.appendAscii( " - " ); // with spaces
779 lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
781 else
783 rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
784 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
785 aBuffer.append( (sal_Unicode) '-' );
786 rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
787 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
790 return aBuffer.makeStringAndClear();
793 String lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
794 bool bDateValues, SvNumberFormatter* pFormatter )
796 DBG_ASSERT( cDecSeparator != 0, "cDecSeparator not initialized" );
798 rtl::OUStringBuffer aBuffer;
799 aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
800 if ( bDateValues )
801 lcl_AppendDateStr( aBuffer, fValue, pFormatter );
802 else
803 rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
804 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
805 return aBuffer.makeStringAndClear();
808 inline bool IsInteger( double fValue )
810 return rtl::math::approxEqual( fValue, rtl::math::approxFloor(fValue) );
813 const TypedScStrCollection& ScDPNumGroupDimension::GetNumEntries(
814 const TypedScStrCollection& rOriginal, ScDocument* pDoc ) const
816 if ( !pCollection )
818 SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
820 pCollection = new TypedScStrCollection();
821 if ( pDateHelper )
822 pDateHelper->FillColumnEntries( *pCollection, rOriginal, pFormatter );
823 else
825 // Copy textual entries.
826 // Also look through the source entries for non-integer numbers, minimum and maximum.
827 // GetNumEntries (GetColumnEntries) must be called before accessing the groups
828 // (this in ensured by calling ScDPLevel::GetMembersObject for all column/row/page
829 // dimensions before iterating over the values).
831 cDecSeparator = ScGlobal::pLocaleData->getNumDecimalSep().GetChar(0);
833 // non-integer GroupInfo values count, too
834 bHasNonInteger = ( !aGroupInfo.AutoStart && !IsInteger( aGroupInfo.Start ) ) ||
835 ( !aGroupInfo.AutoEnd && !IsInteger( aGroupInfo.End ) ) ||
836 !IsInteger( aGroupInfo.Step );
837 double fSourceMin = 0.0;
838 double fSourceMax = 0.0;
839 bool bFirst = true;
841 USHORT nOriginalCount = rOriginal.GetCount();
842 for (USHORT nOriginalPos=0; nOriginalPos<nOriginalCount; nOriginalPos++)
844 const TypedStrData& rStrData = *rOriginal[nOriginalPos];
845 if ( rStrData.IsStrData() )
847 // string data: just copy
848 TypedStrData* pNew = new TypedStrData( rStrData );
849 if ( !pCollection->Insert( pNew ) )
850 delete pNew;
852 else
854 double fSourceValue = rStrData.GetValue();
855 if ( bFirst )
857 fSourceMin = fSourceMax = fSourceValue;
858 bFirst = false;
860 else
862 if ( fSourceValue < fSourceMin )
863 fSourceMin = fSourceValue;
864 if ( fSourceValue > fSourceMax )
865 fSourceMax = fSourceValue;
867 if ( !bHasNonInteger && !IsInteger( fSourceValue ) )
869 // if any non-integer numbers are involved, the group labels are
870 // shown including their upper limit
871 bHasNonInteger = true;
876 if ( aGroupInfo.DateValues )
878 // special handling for dates: always integer, round down limits
879 bHasNonInteger = false;
880 fSourceMin = rtl::math::approxFloor( fSourceMin );
881 fSourceMax = rtl::math::approxFloor( fSourceMax ) + 1;
884 if ( aGroupInfo.AutoStart )
885 const_cast<ScDPNumGroupDimension*>(this)->aGroupInfo.Start = fSourceMin;
886 if ( aGroupInfo.AutoEnd )
887 const_cast<ScDPNumGroupDimension*>(this)->aGroupInfo.End = fSourceMax;
889 //! limit number of entries?
891 long nLoopCount = 0;
892 double fLoop = aGroupInfo.Start;
894 // Use "less than" instead of "less or equal" for the loop - don't create a group
895 // that consists only of the end value. Instead, the end value is then included
896 // in the last group (last group is bigger than the others).
897 // The first group has to be created nonetheless. GetNumGroupForValue has corresponding logic.
899 bool bFirstGroup = true;
900 while ( bFirstGroup || ( fLoop < aGroupInfo.End && !rtl::math::approxEqual( fLoop, aGroupInfo.End ) ) )
902 String aName = lcl_GetNumGroupName( fLoop, aGroupInfo, bHasNonInteger, cDecSeparator, pFormatter );
903 // create a numerical entry to ensure proper sorting
904 // (in FillMemberResults this needs special handling)
905 TypedStrData* pNew = new TypedStrData( aName, fLoop, SC_STRTYPE_VALUE );
906 if ( !pCollection->Insert( pNew ) )
907 delete pNew;
909 ++nLoopCount;
910 fLoop = aGroupInfo.Start + nLoopCount * aGroupInfo.Step;
911 bFirstGroup = false;
913 // ScDPItemData values are compared with approxEqual
916 String aFirstName = lcl_GetSpecialNumGroupName( aGroupInfo.Start, true, cDecSeparator, aGroupInfo.DateValues, pFormatter );
917 TypedStrData* pFirstEntry = new TypedStrData( aFirstName, aGroupInfo.Start - aGroupInfo.Step, SC_STRTYPE_VALUE );
918 if ( !pCollection->Insert( pFirstEntry ) )
919 delete pFirstEntry;
921 String aLastName = lcl_GetSpecialNumGroupName( aGroupInfo.End, false, cDecSeparator, aGroupInfo.DateValues, pFormatter );
922 TypedStrData* pLastEntry = new TypedStrData( aLastName, aGroupInfo.End + aGroupInfo.Step, SC_STRTYPE_VALUE );
923 if ( !pCollection->Insert( pLastEntry ) )
924 delete pLastEntry;
927 return *pCollection;
930 // -----------------------------------------------------------------------
932 String lcl_GetNumGroupForValue( double fValue, const ScDPNumGroupInfo& rInfo, bool bHasNonInteger,
933 sal_Unicode cDecSeparator, double& rGroupValue, ScDocument* pDoc )
935 SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
937 if ( fValue < rInfo.Start && !rtl::math::approxEqual( fValue, rInfo.Start ) )
939 rGroupValue = rInfo.Start - rInfo.Step;
940 return lcl_GetSpecialNumGroupName( rInfo.Start, true, cDecSeparator, rInfo.DateValues, pFormatter );
943 if ( fValue > rInfo.End && !rtl::math::approxEqual( fValue, rInfo.End ) )
945 rGroupValue = rInfo.End + rInfo.Step;
946 return lcl_GetSpecialNumGroupName( rInfo.End, false, cDecSeparator, rInfo.DateValues, pFormatter );
949 double fDiff = fValue - rInfo.Start;
950 double fDiv = rtl::math::approxFloor( fDiff / rInfo.Step );
951 double fGroupStart = rInfo.Start + fDiv * rInfo.Step;
953 if ( rtl::math::approxEqual( fGroupStart, rInfo.End ) &&
954 !rtl::math::approxEqual( fGroupStart, rInfo.Start ) )
956 if ( !rInfo.DateValues )
958 // A group that would consist only of the end value is not created,
959 // instead the value is included in the last group before. So the
960 // previous group is used if the calculated group start value is the
961 // selected end value.
963 fDiv -= 1.0;
964 fGroupStart = rInfo.Start + fDiv * rInfo.Step;
966 else
968 // For date values, the end value is instead treated as above the limit
969 // if it would be a group of its own.
971 rGroupValue = rInfo.End + rInfo.Step;
972 return lcl_GetSpecialNumGroupName( rInfo.End, false, cDecSeparator, rInfo.DateValues, pFormatter );
976 rGroupValue = fGroupStart;
978 return lcl_GetNumGroupName( fGroupStart, rInfo, bHasNonInteger, cDecSeparator, pFormatter );
981 ScDPGroupTableData::ScDPGroupTableData( const shared_ptr<ScDPTableData>& pSource, ScDocument* pDocument ) :
982 ScDPTableData(pDocument),
983 pSourceData( pSource ),
984 pDoc( pDocument )
986 DBG_ASSERT( pSource, "ScDPGroupTableData: pSource can't be NULL" );
988 CreateCacheTable();
989 nSourceCount = pSource->GetColumnCount(); // real columns, excluding data layout
990 pNumGroups = new ScDPNumGroupDimension[nSourceCount];
993 ScDPGroupTableData::~ScDPGroupTableData()
995 delete[] pNumGroups;
998 void ScDPGroupTableData::AddGroupDimension( const ScDPGroupDimension& rGroup )
1000 ScDPGroupDimension aNewGroup( rGroup );
1001 aNewGroup.SetGroupDim( GetColumnCount() ); // new dimension will be at the end
1002 aGroups.push_back( aNewGroup );
1003 aGroupNames.insert( OUString(aNewGroup.GetName()) );
1006 void ScDPGroupTableData::SetNumGroupDimension( long nIndex, const ScDPNumGroupDimension& rGroup )
1008 if ( nIndex < nSourceCount )
1010 pNumGroups[nIndex] = rGroup;
1012 // automatic minimum / maximum is handled in GetNumEntries
1016 long ScDPGroupTableData::GetDimensionIndex( const String& rName )
1018 for (long i=0; i<nSourceCount; i++) // nSourceCount excludes data layout
1019 if ( pSourceData->getDimensionName(i) == rName ) //! ignore case?
1020 return i;
1022 return -1; // none
1025 long ScDPGroupTableData::GetColumnCount()
1027 return nSourceCount + aGroups.size();
1030 bool ScDPGroupTableData::IsNumGroupDimension( long nDimension ) const
1032 return ( nDimension < nSourceCount && pNumGroups[nDimension].GetInfo().Enable );
1035 void ScDPGroupTableData::GetNumGroupInfo( long nDimension, ScDPNumGroupInfo& rInfo,
1036 bool& rNonInteger, sal_Unicode& rDecimal )
1038 if ( nDimension < nSourceCount )
1040 rInfo = pNumGroups[nDimension].GetInfo();
1041 rNonInteger = pNumGroups[nDimension].HasNonInteger();
1042 rDecimal = pNumGroups[nDimension].GetDecSeparator();
1046 const TypedScStrCollection& ScDPGroupTableData::GetColumnEntries(long nColumn)
1048 // date handling is in ScDPGroupDimension::GetColumnEntries / ScDPNumGroupDimension::GetNumEntries
1049 // (to use the pCollection members)
1051 if ( nColumn >= nSourceCount )
1053 if ( nColumn == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
1054 nColumn = nSourceCount; // index of data layout in source data
1055 else
1057 const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
1058 long nSourceDim = rGroupDim.GetSourceDim();
1059 // collection is cached at pSourceData, GetColumnEntries can be called every time
1060 const TypedScStrCollection& rOriginal = pSourceData->GetColumnEntries( nSourceDim );
1061 return rGroupDim.GetColumnEntries( rOriginal, pDoc );
1065 if ( IsNumGroupDimension( nColumn ) )
1067 // dimension number is unchanged for numerical groups
1068 const TypedScStrCollection& rOriginal = pSourceData->GetColumnEntries( nColumn );
1069 return pNumGroups[nColumn].GetNumEntries( rOriginal, pDoc );
1072 return pSourceData->GetColumnEntries( nColumn );
1075 String ScDPGroupTableData::getDimensionName(long nColumn)
1077 if ( nColumn >= nSourceCount )
1079 if ( nColumn == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
1080 nColumn = nSourceCount; // index of data layout in source data
1081 else
1082 return aGroups[nColumn - nSourceCount].GetName();
1085 return pSourceData->getDimensionName( nColumn );
1088 BOOL ScDPGroupTableData::getIsDataLayoutDimension(long nColumn)
1090 // position of data layout dimension is moved from source data
1091 return ( nColumn == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ); // data layout dimension?
1094 BOOL ScDPGroupTableData::IsDateDimension(long nDim)
1096 if ( nDim >= nSourceCount )
1098 if ( nDim == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
1099 nDim = nSourceCount; // index of data layout in source data
1100 else
1101 nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
1104 return pSourceData->IsDateDimension( nDim );
1107 UINT32 ScDPGroupTableData::GetNumberFormat(long nDim)
1109 if ( nDim >= nSourceCount )
1111 if ( nDim == sal::static_int_cast<long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
1112 nDim = nSourceCount; // index of data layout in source data
1113 else
1114 nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
1117 return pSourceData->GetNumberFormat( nDim );
1120 void ScDPGroupTableData::DisposeData()
1122 for ( ScDPGroupDimensionVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1123 aIter->DisposeData();
1125 for ( long i=0; i<nSourceCount; i++ )
1126 pNumGroups[i].DisposeData();
1128 pSourceData->DisposeData();
1131 void ScDPGroupTableData::SetEmptyFlags( BOOL bIgnoreEmptyRows, BOOL bRepeatIfEmpty )
1133 pSourceData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
1136 bool ScDPGroupTableData::IsRepeatIfEmpty()
1138 return pSourceData->IsRepeatIfEmpty();
1141 void ScDPGroupTableData::CreateCacheTable()
1143 pSourceData->CreateCacheTable();
1146 void ScDPGroupTableData::ModifyFilterCriteria(vector<ScDPCacheTable::Criterion>& rCriteria)
1148 typedef hash_map<long, const ScDPGroupDimension*> GroupFieldMapType;
1149 GroupFieldMapType aGroupFieldIds;
1151 ScDPGroupDimensionVec::const_iterator itr = aGroups.begin(), itrEnd = aGroups.end();
1152 for (; itr != itrEnd; ++itr)
1153 aGroupFieldIds.insert( hash_map<long, const ScDPGroupDimension*>::value_type(itr->GetGroupDim(), &(*itr)) );
1156 vector<ScDPCacheTable::Criterion> aNewCriteria;
1157 aNewCriteria.reserve(rCriteria.size() + aGroups.size());
1159 // Go through all the filtered field names and process them appropriately.
1161 vector<ScDPCacheTable::Criterion>::const_iterator itrEnd = rCriteria.end();
1162 GroupFieldMapType::const_iterator itrGrpEnd = aGroupFieldIds.end();
1163 for (vector<ScDPCacheTable::Criterion>::const_iterator itr = rCriteria.begin(); itr != itrEnd; ++itr)
1165 ScDPCacheTable::SingleFilter* pFilter = dynamic_cast<ScDPCacheTable::SingleFilter*>(itr->mpFilter.get());
1166 if (!pFilter)
1167 // We expect this to be a single filter.
1168 continue;
1170 GroupFieldMapType::const_iterator itrGrp = aGroupFieldIds.find(itr->mnFieldIndex);
1171 if (itrGrp == itrGrpEnd)
1173 if (IsNumGroupDimension(itr->mnFieldIndex))
1175 // internal number group field
1176 const ScDPNumGroupDimension& rNumGrpDim = pNumGroups[itr->mnFieldIndex];
1177 const ScDPDateGroupHelper* pDateHelper = rNumGrpDim.GetDateHelper();
1178 if (!pDateHelper)
1180 // What do we do here !?
1181 continue;
1184 ScDPCacheTable::Criterion aCri;
1185 aCri.mnFieldIndex = itr->mnFieldIndex;
1186 aCri.mpFilter.reset(new ScDPGroupDateFilter(
1187 pFilter->getMatchValue(), pDateHelper->GetDatePart(),
1188 pDoc->GetFormatTable()->GetNullDate(), &pDateHelper->GetNumInfo()));
1190 aNewCriteria.push_back(aCri);
1192 else
1194 // This is a regular source field.
1195 aNewCriteria.push_back(*itr);
1198 else
1200 // This is an ordinary group field or external number group field.
1202 const ScDPGroupDimension* pGrpDim = itrGrp->second;
1203 long nSrcDim = pGrpDim->GetSourceDim();
1204 const ScDPDateGroupHelper* pDateHelper = pGrpDim->GetDateHelper();
1206 if (pDateHelper)
1208 // external number group
1209 ScDPCacheTable::Criterion aCri;
1210 aCri.mnFieldIndex = nSrcDim; // use the source dimension, not the group dimension.
1211 aCri.mpFilter.reset(new ScDPGroupDateFilter(
1212 pFilter->getMatchValue(), pDateHelper->GetDatePart(),
1213 pDoc->GetFormatTable()->GetNullDate(), &pDateHelper->GetNumInfo()));
1215 aNewCriteria.push_back(aCri);
1217 else
1219 // normal group
1221 // Note that each group dimension may have multiple group names!
1222 size_t nGroupItemCount = pGrpDim->GetItemCount();
1223 for (size_t i = 0; i < nGroupItemCount; ++i)
1225 const ScDPGroupItem* pGrpItem = pGrpDim->GetGroupByIndex(i);
1226 ScDPItemData aName;
1227 aName.aString = pFilter->getMatchString();
1228 aName.fValue = pFilter->getMatchValue();
1229 aName.bHasValue = pFilter->hasValue();
1230 if (!pGrpItem || !pGrpItem->GetName().IsCaseInsEqual(aName))
1231 continue;
1233 ScDPCacheTable::Criterion aCri;
1234 aCri.mnFieldIndex = nSrcDim;
1235 aCri.mpFilter.reset(new ScDPCacheTable::GroupFilter(GetSharedString()));
1236 ScDPCacheTable::GroupFilter* pGrpFilter =
1237 static_cast<ScDPCacheTable::GroupFilter*>(aCri.mpFilter.get());
1239 pGrpItem->FillGroupFilter(*pGrpFilter);
1240 aNewCriteria.push_back(aCri);
1245 rCriteria.swap(aNewCriteria);
1248 void ScDPGroupTableData::FilterCacheTable(const vector<ScDPCacheTable::Criterion>& rCriteria, const hash_set<sal_Int32>& rCatDims)
1250 vector<ScDPCacheTable::Criterion> aNewCriteria(rCriteria);
1251 ModifyFilterCriteria(aNewCriteria);
1252 pSourceData->FilterCacheTable(aNewCriteria, rCatDims);
1255 void ScDPGroupTableData::GetDrillDownData(const vector<ScDPCacheTable::Criterion>& rCriteria, const hash_set<sal_Int32>& rCatDims, Sequence< Sequence<Any> >& rData)
1257 vector<ScDPCacheTable::Criterion> aNewCriteria(rCriteria);
1258 ModifyFilterCriteria(aNewCriteria);
1259 pSourceData->GetDrillDownData(aNewCriteria, rCatDims, rData);
1262 void ScDPGroupTableData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
1264 // This CalcInfo instance is used only to retrive data from the original
1265 // data source.
1266 CalcInfo aInfoSrc = rInfo;
1267 CopyFields(rInfo.aColLevelDims, aInfoSrc.aColLevelDims);
1268 CopyFields(rInfo.aRowLevelDims, aInfoSrc.aRowLevelDims);
1269 CopyFields(rInfo.aPageDims, aInfoSrc.aPageDims);
1270 CopyFields(rInfo.aDataSrcCols, aInfoSrc.aDataSrcCols);
1272 const ScDPCacheTable& rCacheTable = pSourceData->GetCacheTable();
1273 sal_Int32 nRowSize = rCacheTable.getRowSize();
1274 for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
1276 if (!rCacheTable.isRowActive(nRow))
1277 continue;
1279 CalcRowData aData;
1280 FillRowDataFromCacheTable(nRow, rCacheTable, aInfoSrc, aData);
1282 if ( !rInfo.aColLevelDims.empty() )
1283 FillGroupValues(&aData.aColData[0], rInfo.aColLevelDims.size(), &rInfo.aColLevelDims[0]);
1284 if ( !rInfo.aRowLevelDims.empty() )
1285 FillGroupValues(&aData.aRowData[0], rInfo.aRowLevelDims.size(), &rInfo.aRowLevelDims[0]);
1286 if ( !rInfo.aPageDims.empty() )
1287 FillGroupValues(&aData.aPageData[0], rInfo.aPageDims.size(), &rInfo.aPageDims[0]);
1289 ProcessRowData(rInfo, aData, bAutoShow);
1293 const ScDPCacheTable& ScDPGroupTableData::GetCacheTable() const
1295 return pSourceData->GetCacheTable();
1298 void ScDPGroupTableData::CopyFields(const vector<long>& rFieldDims, vector<long>& rNewFieldDims)
1300 size_t nCount = rFieldDims.size();
1301 if (!nCount)
1302 return;
1304 long nGroupedColumns = aGroups.size();
1306 rNewFieldDims.clear();
1307 rNewFieldDims.reserve(nCount);
1308 for (size_t i = 0; i < nCount; ++i)
1310 if ( rFieldDims[i] >= nSourceCount )
1312 if ( rFieldDims[i] == nSourceCount + nGroupedColumns )
1313 // data layout in source
1314 rNewFieldDims.push_back(nSourceCount);
1315 else
1317 // original dimension
1318 long n = rFieldDims[i] - nSourceCount;
1319 rNewFieldDims.push_back(aGroups[n].GetSourceDim());
1322 else
1323 rNewFieldDims.push_back(rFieldDims[i]);
1327 void ScDPGroupTableData::FillGroupValues( ScDPItemData* pItemData, long nCount, const long* pDims )
1329 long nGroupedColumns = aGroups.size();
1331 for (long nDim=0; nDim<nCount; nDim++)
1333 const ScDPDateGroupHelper* pDateHelper = NULL;
1335 long nColumn = pDims[nDim];
1336 if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns )
1338 const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
1339 pDateHelper = rGroupDim.GetDateHelper();
1340 if ( !pDateHelper ) // date is handled below
1342 const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData( pItemData[nDim] );
1343 if ( pGroupItem )
1344 pItemData[nDim] = pGroupItem->GetName();
1345 // if no group is found, keep the original name
1348 else if ( IsNumGroupDimension( nColumn ) )
1350 pDateHelper = pNumGroups[nColumn].GetDateHelper();
1351 if ( !pDateHelper ) // date is handled below
1353 if ( pItemData[nDim].bHasValue )
1355 ScDPNumGroupInfo aNumInfo;
1356 bool bHasNonInteger = false;
1357 sal_Unicode cDecSeparator = 0;
1358 GetNumGroupInfo( nColumn, aNumInfo, bHasNonInteger, cDecSeparator );
1359 double fGroupValue;
1360 String aGroupName = lcl_GetNumGroupForValue( pItemData[nDim].fValue,
1361 aNumInfo, bHasNonInteger, cDecSeparator, fGroupValue, pDoc );
1363 // consistent with TypedStrData in GetNumEntries
1364 pItemData[nDim] = ScDPItemData( aGroupName, fGroupValue, TRUE );
1366 // else (textual) keep original value
1370 if ( pDateHelper )
1372 if ( pItemData[nDim].bHasValue )
1374 sal_Int32 nPartValue = lcl_GetDatePartValue(
1375 pItemData[nDim].fValue, pDateHelper->GetDatePart(), pDoc->GetFormatTable(),
1376 &pDateHelper->GetNumInfo() );
1377 pItemData[nDim] = ScDPItemData( String(), nPartValue, TRUE );
1383 BOOL ScDPGroupTableData::IsBaseForGroup(long nDim) const
1385 for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1387 const ScDPGroupDimension& rDim = *aIter;
1388 if ( rDim.GetSourceDim() == nDim )
1389 return TRUE;
1392 return FALSE;
1395 long ScDPGroupTableData::GetGroupBase(long nGroupDim) const
1397 for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1399 const ScDPGroupDimension& rDim = *aIter;
1400 if ( rDim.GetGroupDim() == nGroupDim )
1401 return rDim.GetSourceDim();
1404 return -1; // none
1407 BOOL ScDPGroupTableData::IsNumOrDateGroup(long nDimension) const
1409 // Virtual method from ScDPTableData, used in result data to force text labels.
1411 if ( nDimension < nSourceCount )
1413 return pNumGroups[nDimension].GetInfo().Enable ||
1414 pNumGroups[nDimension].GetDateHelper();
1417 for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1419 const ScDPGroupDimension& rDim = *aIter;
1420 if ( rDim.GetGroupDim() == nDimension )
1421 return ( rDim.GetDateHelper() != NULL );
1424 return FALSE;
1427 BOOL ScDPGroupTableData::IsInGroup( const ScDPItemData& rGroupData, long nGroupIndex,
1428 const ScDPItemData& rBaseData, long nBaseIndex ) const
1430 for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1432 const ScDPGroupDimension& rDim = *aIter;
1433 if ( rDim.GetGroupDim() == nGroupIndex && rDim.GetSourceDim() == nBaseIndex )
1435 const ScDPDateGroupHelper* pGroupDateHelper = rDim.GetDateHelper();
1436 if ( pGroupDateHelper )
1438 //! transform rBaseData (innermost date part)
1439 //! -> always do "HasCommonElement" style comparison
1440 //! (only Quarter, Month, Day affected)
1442 const ScDPDateGroupHelper* pBaseDateHelper = NULL;
1443 if ( nBaseIndex < nSourceCount )
1444 pBaseDateHelper = pNumGroups[nBaseIndex].GetDateHelper();
1446 // If there's a date group dimension, the base dimension must have
1447 // date group information, too.
1448 if ( !pBaseDateHelper )
1450 DBG_ERROR( "mix of date and non-date groups" );
1451 return TRUE;
1454 sal_Int32 nGroupPart = pGroupDateHelper->GetDatePart();
1455 sal_Int32 nBasePart = pBaseDateHelper->GetDatePart();
1456 return lcl_DateContained( nGroupPart, rGroupData, nBasePart, rBaseData );
1458 else
1460 // If the item is in a group, only that group is valid.
1461 // If the item is not in any group, its own name is valid.
1463 const ScDPGroupItem* pGroup = rDim.GetGroupForData( rBaseData );
1464 return pGroup ? pGroup->GetName().IsCaseInsEqual( rGroupData ) :
1465 rGroupData.IsCaseInsEqual( rBaseData );
1470 DBG_ERROR("IsInGroup: no group dimension found");
1471 return TRUE;
1474 BOOL ScDPGroupTableData::HasCommonElement( const ScDPItemData& rFirstData, long nFirstIndex,
1475 const ScDPItemData& rSecondData, long nSecondIndex ) const
1477 const ScDPGroupDimension* pFirstDim = NULL;
1478 const ScDPGroupDimension* pSecondDim = NULL;
1479 for ( ScDPGroupDimensionVec::const_iterator aIter(aGroups.begin()); aIter != aGroups.end(); aIter++ )
1481 const ScDPGroupDimension* pDim = &(*aIter);
1482 if ( pDim->GetGroupDim() == nFirstIndex )
1483 pFirstDim = pDim;
1484 else if ( pDim->GetGroupDim() == nSecondIndex )
1485 pSecondDim = pDim;
1487 if ( pFirstDim && pSecondDim )
1489 const ScDPDateGroupHelper* pFirstDateHelper = pFirstDim->GetDateHelper();
1490 const ScDPDateGroupHelper* pSecondDateHelper = pSecondDim->GetDateHelper();
1491 if ( pFirstDateHelper || pSecondDateHelper )
1493 // If one is a date group dimension, the other one must be, too.
1494 if ( !pFirstDateHelper || !pSecondDateHelper )
1496 DBG_ERROR( "mix of date and non-date groups" );
1497 return TRUE;
1500 sal_Int32 nFirstPart = pFirstDateHelper->GetDatePart();
1501 sal_Int32 nSecondPart = pSecondDateHelper->GetDatePart();
1502 return lcl_DateContained( nFirstPart, rFirstData, nSecondPart, rSecondData );
1505 const ScDPGroupItem* pFirstItem = pFirstDim->GetGroupForName( rFirstData );
1506 const ScDPGroupItem* pSecondItem = pSecondDim->GetGroupForName( rSecondData );
1507 if ( pFirstItem && pSecondItem )
1509 // two existing groups -> TRUE if they have a common element
1510 return pFirstItem->HasCommonElement( *pSecondItem );
1512 else if ( pFirstItem )
1514 // "automatic" group contains only its own name
1515 return pFirstItem->HasElement( rSecondData );
1517 else if ( pSecondItem )
1519 // "automatic" group contains only its own name
1520 return pSecondItem->HasElement( rFirstData );
1522 else
1524 // no groups -> TRUE if equal
1525 return rFirstData.IsCaseInsEqual( rSecondData );
1529 DBG_ERROR("HasCommonElement: no group dimension found");
1530 return TRUE;
1533 // -----------------------------------------------------------------------