Stop leaking all ScPostIt instances.
[LibreOffice.git] / sc / source / core / data / dputil.cxx
blobe21b2a86bbef749d114cf6f7a59271aff5b746ee
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "dputil.hxx"
11 #include "dpitemdata.hxx"
12 #include "dpnumgroupinfo.hxx"
13 #include "globalnames.hxx"
14 #include "globstr.hrc"
16 #include "comphelper/string.hxx"
17 #include "unotools/localedatawrapper.hxx"
18 #include "unotools/calendarwrapper.hxx"
19 #include "svl/zforlist.hxx"
20 #include "rtl/math.hxx"
22 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
23 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
25 using namespace com::sun::star;
27 namespace {
29 const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
31 OUString getTwoDigitString(sal_Int32 nValue)
33 OUString aRet = OUString::number( nValue );
34 if ( aRet.getLength() < 2 )
35 aRet = "0" + aRet;
36 return aRet;
39 void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
41 sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
42 OUString aString;
43 pFormatter->GetInputLineString(fValue, nFormat, aString);
44 rBuffer.append(aString);
47 OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
49 OUStringBuffer aBuffer;
50 aBuffer.append(sal_Unicode(bFirst ? '<' : '>'));
51 appendDateStr(aBuffer, fValue, pFormatter);
52 return aBuffer.makeStringAndClear();
57 bool ScDPUtil::isDuplicateDimension(const OUString& rName)
59 return rName.endsWith("*");
62 OUString ScDPUtil::getSourceDimensionName(const OUString& rName)
64 return comphelper::string::stripEnd(rName, '*');
67 sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
69 // Count all trailing '*'s.
71 sal_Int32 n = rName.getLength();
72 if (!n)
73 return 0;
75 sal_uInt8 nDupCount = 0;
76 const sal_Unicode* p = rName.getStr();
77 const sal_Unicode* pStart = p;
78 p += n-1; // Set it to the last char.
79 for (; p != pStart; --p, ++nDupCount)
81 if (*p != '*')
82 break;
85 return nDupCount;
88 OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
90 if (!nDupCount)
91 return rOriginal;
93 OUStringBuffer aBuf(rOriginal);
94 for (size_t i = 0; i < nDupCount; ++i)
95 aBuf.append('*');
97 return aBuf.makeStringAndClear();
100 OUString ScDPUtil::getDateGroupName(
101 sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
102 double fStart, double fEnd)
104 if (nValue == ScDPItemData::DateFirst)
105 return getSpecialDateName(fStart, true, pFormatter);
106 if (nValue == ScDPItemData::DateLast)
107 return getSpecialDateName(fEnd, false, pFormatter);
109 switch ( nDatePart )
111 case sheet::DataPilotFieldGroupBy::YEARS:
112 return OUString::number(nValue);
113 case sheet::DataPilotFieldGroupBy::QUARTERS:
114 return ScGlobal::pLocaleData->getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
115 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
116 return ScGlobal::GetCalendar()->getDisplayName(
117 i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
118 case sheet::DataPilotFieldGroupBy::DAYS:
120 Date aDate(1, 1, SC_DP_LEAPYEAR);
121 aDate += (nValue - 1); // nValue is 1-based
122 Date aNullDate = *pFormatter->GetNullDate();
123 long nDays = aDate - aNullDate;
125 sal_uLong nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
126 Color* pColor;
127 OUString aStr;
128 pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
129 return aStr;
131 case sheet::DataPilotFieldGroupBy::HOURS:
133 //! allow am/pm format?
134 return getTwoDigitString(nValue);
136 break;
137 case sheet::DataPilotFieldGroupBy::MINUTES:
138 case sheet::DataPilotFieldGroupBy::SECONDS:
140 OUStringBuffer aBuf(ScGlobal::pLocaleData->getTimeSep());
141 aBuf.append(getTwoDigitString(nValue));
142 return aBuf.makeStringAndClear();
144 break;
145 default:
146 OSL_FAIL("invalid date part");
149 return OUString("FIXME: unhandled value");
152 double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
154 if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
156 rtl::math::setInf(&fValue, true);
157 return fValue;
160 if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
162 rtl::math::setInf(&fValue, false);
163 return fValue;
166 double fDiff = fValue - rInfo.mfStart;
167 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
168 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
170 if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
171 !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
173 if (!rInfo.mbDateValues)
175 // A group that would consist only of the end value is not
176 // created, instead the value is included in the last group
177 // before. So the previous group is used if the calculated group
178 // start value is the selected end value.
180 fDiv -= 1.0;
181 return rInfo.mfStart + fDiv * rInfo.mfStep;
184 // For date values, the end value is instead treated as above the
185 // limit if it would be a group of its own.
187 return rInfo.mfEnd + rInfo.mfStep;
190 return fGroupStart;
193 namespace {
195 void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
197 sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
198 OUString aString;
199 pFormatter->GetInputLineString( fValue, nFormat, aString );
200 rBuffer.append( aString );
203 OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
204 bool bDateValues, SvNumberFormatter* pFormatter )
206 OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
208 OUStringBuffer aBuffer;
209 aBuffer.append((sal_Unicode)( bFirst ? '<' : '>' ));
210 if ( bDateValues )
211 lcl_AppendDateStr( aBuffer, fValue, pFormatter );
212 else
213 rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
214 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
215 return aBuffer.makeStringAndClear();
218 OUString lcl_GetNumGroupName(
219 double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
220 SvNumberFormatter* pFormatter)
222 OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
224 double fStep = rInfo.mfStep;
225 double fEndValue = fStartValue + fStep;
226 if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
228 // The second number of the group label is
229 // (first number + size - 1) if there are only integer numbers,
230 // (first number + size) if any non-integer numbers are involved.
231 // Exception: The last group (containing the end value) is always
232 // shown as including the end value (but not for dates).
234 fEndValue -= 1.0;
237 if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
239 // limit the last group to the end value
241 fEndValue = rInfo.mfEnd;
244 OUStringBuffer aBuffer;
245 if ( rInfo.mbDateValues )
247 lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
248 aBuffer.appendAscii( " - " ); // with spaces
249 lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
251 else
253 rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
254 rtl_math_DecimalPlaces_Max, cDecSep, true );
255 aBuffer.append( '-' );
256 rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
257 rtl_math_DecimalPlaces_Max, cDecSep, true );
260 return aBuffer.makeStringAndClear();
265 OUString ScDPUtil::getNumGroupName(
266 double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
268 if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
269 return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
271 if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
272 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
274 double fDiff = fValue - rInfo.mfStart;
275 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
276 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
278 if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
279 !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
281 if (rInfo.mbDateValues)
283 // For date values, the end value is instead treated as above the limit
284 // if it would be a group of its own.
285 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
289 return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
292 sal_Int32 ScDPUtil::getDatePartValue(
293 double fValue, const ScDPNumGroupInfo& rInfo, sal_Int32 nDatePart,
294 SvNumberFormatter* pFormatter)
296 // Start and end are inclusive
297 // (End date without a time value is included, with a time value it's not)
299 if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
300 return ScDPItemData::DateFirst;
301 if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
302 return ScDPItemData::DateLast;
304 sal_Int32 nResult = 0;
306 if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
307 nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
308 nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
310 // handle time
311 // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
313 double fTime = fValue - rtl::math::approxFloor(fValue);
314 long nSeconds = (long)rtl::math::approxFloor(fTime*DATE_TIME_FACTOR+0.5);
316 switch (nDatePart)
318 case sheet::DataPilotFieldGroupBy::HOURS:
319 nResult = nSeconds / 3600;
320 break;
321 case sheet::DataPilotFieldGroupBy::MINUTES:
322 nResult = ( nSeconds % 3600 ) / 60;
323 break;
324 case sheet::DataPilotFieldGroupBy::SECONDS:
325 nResult = nSeconds % 60;
326 break;
329 else
331 Date aDate = *(pFormatter->GetNullDate());
332 aDate += (long)::rtl::math::approxFloor(fValue);
334 switch ( nDatePart )
336 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
337 nResult = aDate.GetYear();
338 break;
339 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
340 nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
341 break;
342 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
343 nResult = aDate.GetMonth(); // 1..12
344 break;
345 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
347 Date aYearStart(1, 1, aDate.GetYear());
348 nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
349 if (nResult >= 60 && !aDate.IsLeapYear())
351 // days are counted from 1 to 366 - if not from a leap year, adjust
352 ++nResult;
355 break;
356 default:
357 OSL_FAIL("invalid date part");
361 return nResult;
364 namespace {
366 sal_uInt16 nFuncStrIds[12] = {
367 0, // SUBTOTAL_FUNC_NONE
368 STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
369 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
370 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
371 STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
372 STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
373 STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
374 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
375 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
376 STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
377 STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
378 STR_FUN_TEXT_VAR // SUBTOTAL_FUNC_VARP
383 OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
385 OUStringBuffer aRet;
386 sal_uInt16 nId = nFuncStrIds[eFunc];
387 if (nId)
389 aRet.append(ScGlobal::GetRscString(nId)); // function name
390 aRet.append(" - ");
392 aRet.append(rName); // field name
394 return aRet.makeStringAndClear();
397 ScSubTotalFunc ScDPUtil::toSubTotalFunc(com::sun::star::sheet::GeneralFunction eGenFunc)
399 ScSubTotalFunc eSubTotal;
400 switch (eGenFunc)
402 case sheet::GeneralFunction_NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
403 case sheet::GeneralFunction_SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
404 case sheet::GeneralFunction_COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
405 case sheet::GeneralFunction_AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
406 case sheet::GeneralFunction_MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
407 case sheet::GeneralFunction_MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
408 case sheet::GeneralFunction_PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
409 case sheet::GeneralFunction_COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
410 case sheet::GeneralFunction_STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
411 case sheet::GeneralFunction_STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
412 case sheet::GeneralFunction_VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
413 case sheet::GeneralFunction_VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
414 case sheet::GeneralFunction_AUTO:
415 default:
416 eSubTotal = SUBTOTAL_FUNC_NONE;
418 return eSubTotal;
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */