fix baseline build (old cairo) - 'cairo_rectangle_int_t' does not name a type
[LibreOffice.git] / sc / source / core / data / dputil.cxx
blob9e857cc0c848d4029d980b842325312bd85e8ccb
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>
21 #include <osl/diagnose.h>
23 #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
24 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
26 using namespace com::sun::star;
28 namespace {
30 const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
32 OUString getTwoDigitString(sal_Int32 nValue)
34 OUString aRet = OUString::number( nValue );
35 if ( aRet.getLength() < 2 )
36 aRet = "0" + aRet;
37 return aRet;
40 void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
42 sal_uLong nFormat = pFormatter->GetStandardFormat( css::util::NumberFormat::DATE, ScGlobal::eLnge );
43 OUString aString;
44 pFormatter->GetInputLineString(fValue, nFormat, aString);
45 rBuffer.append(aString);
48 OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
50 OUStringBuffer aBuffer;
51 aBuffer.append( bFirst ? '<' : '>' );
52 appendDateStr(aBuffer, fValue, pFormatter);
53 return aBuffer.makeStringAndClear();
58 bool ScDPUtil::isDuplicateDimension(const OUString& rName)
60 return rName.endsWith("*");
63 OUString ScDPUtil::getSourceDimensionName(const OUString& rName)
65 return comphelper::string::stripEnd(rName, '*');
68 sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
70 // Count all trailing '*'s.
72 sal_Int32 n = rName.getLength();
73 if (!n)
74 return 0;
76 sal_uInt8 nDupCount = 0;
77 const sal_Unicode* p = rName.getStr();
78 const sal_Unicode* pStart = p;
79 p += n-1; // Set it to the last char.
80 for (; p != pStart; --p, ++nDupCount)
82 if (*p != '*')
83 break;
86 return nDupCount;
89 OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
91 if (!nDupCount)
92 return rOriginal;
94 OUStringBuffer aBuf(rOriginal);
95 for (size_t i = 0; i < nDupCount; ++i)
96 aBuf.append('*');
98 return aBuf.makeStringAndClear();
101 OUString ScDPUtil::getDateGroupName(
102 sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
103 double fStart, double fEnd)
105 if (nValue == ScDPItemData::DateFirst)
106 return getSpecialDateName(fStart, true, pFormatter);
107 if (nValue == ScDPItemData::DateLast)
108 return getSpecialDateName(fEnd, false, pFormatter);
110 switch ( nDatePart )
112 case sheet::DataPilotFieldGroupBy::YEARS:
113 return OUString::number(nValue);
114 case sheet::DataPilotFieldGroupBy::QUARTERS:
115 return ScGlobal::pLocaleData->getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
116 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
117 return ScGlobal::GetCalendar()->getDisplayName(
118 i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
119 case sheet::DataPilotFieldGroupBy::DAYS:
121 Date aDate(1, 1, SC_DP_LEAPYEAR);
122 aDate += (nValue - 1); // nValue is 1-based
123 Date aNullDate = *pFormatter->GetNullDate();
124 long nDays = aDate - aNullDate;
126 sal_uLong nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
127 Color* pColor;
128 OUString aStr;
129 pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
130 return aStr;
132 case sheet::DataPilotFieldGroupBy::HOURS:
134 //TODO: allow am/pm format?
135 return getTwoDigitString(nValue);
137 break;
138 case sheet::DataPilotFieldGroupBy::MINUTES:
139 case sheet::DataPilotFieldGroupBy::SECONDS:
141 OUStringBuffer aBuf(ScGlobal::pLocaleData->getTimeSep());
142 aBuf.append(getTwoDigitString(nValue));
143 return aBuf.makeStringAndClear();
145 break;
146 default:
147 OSL_FAIL("invalid date part");
150 return OUString("FIXME: unhandled value");
153 double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
155 if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
157 rtl::math::setInf(&fValue, true);
158 return fValue;
161 if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
163 rtl::math::setInf(&fValue, false);
164 return fValue;
167 double fDiff = fValue - rInfo.mfStart;
168 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
169 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
171 if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
172 !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
174 if (!rInfo.mbDateValues)
176 // A group that would consist only of the end value is not
177 // created, instead the value is included in the last group
178 // before. So the previous group is used if the calculated group
179 // start value is the selected end value.
181 fDiv -= 1.0;
182 return rInfo.mfStart + fDiv * rInfo.mfStep;
185 // For date values, the end value is instead treated as above the
186 // limit if it would be a group of its own.
188 return rInfo.mfEnd + rInfo.mfStep;
191 return fGroupStart;
194 namespace {
196 void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
198 sal_uLong nFormat = pFormatter->GetStandardFormat( css::util::NumberFormat::DATE, ScGlobal::eLnge );
199 OUString aString;
200 pFormatter->GetInputLineString( fValue, nFormat, aString );
201 rBuffer.append( aString );
204 OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
205 bool bDateValues, SvNumberFormatter* pFormatter )
207 OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
209 OUStringBuffer aBuffer;
210 aBuffer.append( bFirst ? '<' : '>' );
211 if ( bDateValues )
212 lcl_AppendDateStr( aBuffer, fValue, pFormatter );
213 else
214 rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
215 rtl_math_DecimalPlaces_Max, cDecSeparator, true );
216 return aBuffer.makeStringAndClear();
219 OUString lcl_GetNumGroupName(
220 double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
221 SvNumberFormatter* pFormatter)
223 OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
225 double fStep = rInfo.mfStep;
226 double fEndValue = fStartValue + fStep;
227 if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
229 // The second number of the group label is
230 // (first number + size - 1) if there are only integer numbers,
231 // (first number + size) if any non-integer numbers are involved.
232 // Exception: The last group (containing the end value) is always
233 // shown as including the end value (but not for dates).
235 fEndValue -= 1.0;
238 if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
240 // limit the last group to the end value
242 fEndValue = rInfo.mfEnd;
245 OUStringBuffer aBuffer;
246 if ( rInfo.mbDateValues )
248 lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
249 aBuffer.appendAscii( " - " ); // with spaces
250 lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
252 else
254 rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
255 rtl_math_DecimalPlaces_Max, cDecSep, true );
256 aBuffer.append( '-' );
257 rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
258 rtl_math_DecimalPlaces_Max, cDecSep, true );
261 return aBuffer.makeStringAndClear();
266 OUString ScDPUtil::getNumGroupName(
267 double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
269 if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
270 return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
272 if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
273 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
275 double fDiff = fValue - rInfo.mfStart;
276 double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
277 double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
279 if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
280 !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
282 if (rInfo.mbDateValues)
284 // For date values, the end value is instead treated as above the limit
285 // if it would be a group of its own.
286 return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
290 return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
293 sal_Int32 ScDPUtil::getDatePartValue(
294 double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
295 SvNumberFormatter* pFormatter)
297 // Start and end are inclusive
298 // (End date without a time value is included, with a time value it's not)
300 if (pInfo)
302 if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
303 return ScDPItemData::DateFirst;
304 if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
305 return ScDPItemData::DateLast;
308 sal_Int32 nResult = 0;
310 if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
311 nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
312 nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
314 // handle time
315 // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
317 double fTime = fValue - rtl::math::approxFloor(fValue);
318 long nSeconds = (long)rtl::math::approxFloor(fTime*DATE_TIME_FACTOR+0.5);
320 switch (nDatePart)
322 case sheet::DataPilotFieldGroupBy::HOURS:
323 nResult = nSeconds / 3600;
324 break;
325 case sheet::DataPilotFieldGroupBy::MINUTES:
326 nResult = ( nSeconds % 3600 ) / 60;
327 break;
328 case sheet::DataPilotFieldGroupBy::SECONDS:
329 nResult = nSeconds % 60;
330 break;
333 else
335 Date aDate = *(pFormatter->GetNullDate());
336 aDate += (long)::rtl::math::approxFloor(fValue);
338 switch ( nDatePart )
340 case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
341 nResult = aDate.GetYear();
342 break;
343 case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
344 nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
345 break;
346 case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
347 nResult = aDate.GetMonth(); // 1..12
348 break;
349 case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
351 Date aYearStart(1, 1, aDate.GetYear());
352 nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
353 if (nResult >= 60 && !aDate.IsLeapYear())
355 // days are counted from 1 to 366 - if not from a leap year, adjust
356 ++nResult;
359 break;
360 default:
361 OSL_FAIL("invalid date part");
365 return nResult;
368 namespace {
370 sal_uInt16 nFuncStrIds[12] = {
371 0, // SUBTOTAL_FUNC_NONE
372 STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
373 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
374 STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
375 STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
376 STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
377 STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
378 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
379 STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
380 STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
381 STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
382 STR_FUN_TEXT_VAR // SUBTOTAL_FUNC_VARP
387 OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
389 OUStringBuffer aRet;
390 sal_uInt16 nId = nFuncStrIds[eFunc];
391 if (nId)
393 aRet.append(ScGlobal::GetRscString(nId)); // function name
394 aRet.append(" - ");
396 aRet.append(rName); // field name
398 return aRet.makeStringAndClear();
401 ScSubTotalFunc ScDPUtil::toSubTotalFunc(com::sun::star::sheet::GeneralFunction eGenFunc)
403 ScSubTotalFunc eSubTotal;
404 switch (eGenFunc)
406 case sheet::GeneralFunction_NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
407 case sheet::GeneralFunction_SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
408 case sheet::GeneralFunction_COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
409 case sheet::GeneralFunction_AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
410 case sheet::GeneralFunction_MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
411 case sheet::GeneralFunction_MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
412 case sheet::GeneralFunction_PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
413 case sheet::GeneralFunction_COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
414 case sheet::GeneralFunction_STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
415 case sheet::GeneralFunction_STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
416 case sheet::GeneralFunction_VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
417 case sheet::GeneralFunction_VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
418 case sheet::GeneralFunction_AUTO:
419 default:
420 eSubTotal = SUBTOTAL_FUNC_NONE;
422 return eSubTotal;
425 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */