Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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 : */
9 :
10 : #include "dputil.hxx"
11 : #include "dpitemdata.hxx"
12 : #include "dpnumgroupinfo.hxx"
13 : #include "globalnames.hxx"
14 : #include "globstr.hrc"
15 :
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 :
22 : #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
23 : #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
24 :
25 : using namespace com::sun::star;
26 :
27 : namespace {
28 :
29 : const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
30 :
31 0 : OUString getTwoDigitString(sal_Int32 nValue)
32 : {
33 0 : OUString aRet = OUString::number( nValue );
34 0 : if ( aRet.getLength() < 2 )
35 0 : aRet = "0" + aRet;
36 0 : return aRet;
37 : }
38 :
39 20 : void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
40 : {
41 20 : sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
42 20 : OUString aString;
43 20 : pFormatter->GetInputLineString(fValue, nFormat, aString);
44 20 : rBuffer.append(aString);
45 20 : }
46 :
47 20 : OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
48 : {
49 20 : OUStringBuffer aBuffer;
50 20 : aBuffer.append( bFirst ? '<' : '>' );
51 20 : appendDateStr(aBuffer, fValue, pFormatter);
52 20 : return aBuffer.makeStringAndClear();
53 : }
54 :
55 : }
56 :
57 314 : bool ScDPUtil::isDuplicateDimension(const OUString& rName)
58 : {
59 314 : return rName.endsWith("*");
60 : }
61 :
62 1702 : OUString ScDPUtil::getSourceDimensionName(const OUString& rName)
63 : {
64 1702 : return comphelper::string::stripEnd(rName, '*');
65 : }
66 :
67 8 : sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
68 : {
69 : // Count all trailing '*'s.
70 :
71 8 : sal_Int32 n = rName.getLength();
72 8 : if (!n)
73 0 : return 0;
74 :
75 8 : sal_uInt8 nDupCount = 0;
76 8 : const sal_Unicode* p = rName.getStr();
77 8 : const sal_Unicode* pStart = p;
78 8 : p += n-1; // Set it to the last char.
79 10 : for (; p != pStart; --p, ++nDupCount)
80 : {
81 10 : if (*p != '*')
82 8 : break;
83 : }
84 :
85 8 : return nDupCount;
86 : }
87 :
88 8 : OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
89 : {
90 8 : if (!nDupCount)
91 0 : return rOriginal;
92 :
93 8 : OUStringBuffer aBuf(rOriginal);
94 16 : for (size_t i = 0; i < nDupCount; ++i)
95 8 : aBuf.append('*');
96 :
97 8 : return aBuf.makeStringAndClear();
98 : }
99 :
100 1592 : OUString ScDPUtil::getDateGroupName(
101 : sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
102 : double fStart, double fEnd)
103 : {
104 1592 : if (nValue == ScDPItemData::DateFirst)
105 10 : return getSpecialDateName(fStart, true, pFormatter);
106 1582 : if (nValue == ScDPItemData::DateLast)
107 10 : return getSpecialDateName(fEnd, false, pFormatter);
108 :
109 1572 : switch ( nDatePart )
110 : {
111 : case sheet::DataPilotFieldGroupBy::YEARS:
112 20 : return OUString::number(nValue);
113 : case sheet::DataPilotFieldGroupBy::QUARTERS:
114 24 : 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 64 : i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
118 : case sheet::DataPilotFieldGroupBy::DAYS:
119 : {
120 1464 : Date aDate(1, 1, SC_DP_LEAPYEAR);
121 1464 : aDate += (nValue - 1); // nValue is 1-based
122 1464 : Date aNullDate = *pFormatter->GetNullDate();
123 1464 : long nDays = aDate - aNullDate;
124 :
125 1464 : sal_uLong nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
126 : Color* pColor;
127 1464 : OUString aStr;
128 1464 : pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
129 1464 : return aStr;
130 : }
131 : case sheet::DataPilotFieldGroupBy::HOURS:
132 : {
133 : //! allow am/pm format?
134 0 : return getTwoDigitString(nValue);
135 : }
136 : break;
137 : case sheet::DataPilotFieldGroupBy::MINUTES:
138 : case sheet::DataPilotFieldGroupBy::SECONDS:
139 : {
140 0 : OUStringBuffer aBuf(ScGlobal::pLocaleData->getTimeSep());
141 0 : aBuf.append(getTwoDigitString(nValue));
142 0 : return aBuf.makeStringAndClear();
143 : }
144 : break;
145 : default:
146 : OSL_FAIL("invalid date part");
147 : }
148 :
149 0 : return OUString("FIXME: unhandled value");
150 : }
151 :
152 36 : double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
153 : {
154 36 : if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
155 : {
156 12 : rtl::math::setInf(&fValue, true);
157 12 : return fValue;
158 : }
159 :
160 24 : if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
161 : {
162 12 : rtl::math::setInf(&fValue, false);
163 12 : return fValue;
164 : }
165 :
166 12 : double fDiff = fValue - rInfo.mfStart;
167 12 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
168 12 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
169 :
170 12 : if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
171 0 : !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
172 : {
173 0 : if (!rInfo.mbDateValues)
174 : {
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.
179 :
180 0 : fDiv -= 1.0;
181 0 : return rInfo.mfStart + fDiv * rInfo.mfStep;
182 : }
183 :
184 : // For date values, the end value is instead treated as above the
185 : // limit if it would be a group of its own.
186 :
187 0 : return rInfo.mfEnd + rInfo.mfStep;
188 : }
189 :
190 12 : return fGroupStart;
191 : }
192 :
193 : namespace {
194 :
195 0 : void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
196 : {
197 0 : sal_uLong nFormat = pFormatter->GetStandardFormat( NUMBERFORMAT_DATE, ScGlobal::eLnge );
198 0 : OUString aString;
199 0 : pFormatter->GetInputLineString( fValue, nFormat, aString );
200 0 : rBuffer.append( aString );
201 0 : }
202 :
203 8 : OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
204 : bool bDateValues, SvNumberFormatter* pFormatter )
205 : {
206 : OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
207 :
208 8 : OUStringBuffer aBuffer;
209 8 : aBuffer.append( bFirst ? '<' : '>' );
210 8 : if ( bDateValues )
211 0 : lcl_AppendDateStr( aBuffer, fValue, pFormatter );
212 : else
213 : rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
214 8 : rtl_math_DecimalPlaces_Max, cDecSeparator, true );
215 8 : return aBuffer.makeStringAndClear();
216 : }
217 :
218 12 : OUString lcl_GetNumGroupName(
219 : double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
220 : SvNumberFormatter* pFormatter)
221 : {
222 : OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
223 :
224 12 : double fStep = rInfo.mfStep;
225 12 : double fEndValue = fStartValue + fStep;
226 12 : if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
227 : {
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).
233 :
234 8 : fEndValue -= 1.0;
235 : }
236 :
237 12 : if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
238 : {
239 : // limit the last group to the end value
240 :
241 0 : fEndValue = rInfo.mfEnd;
242 : }
243 :
244 12 : OUStringBuffer aBuffer;
245 12 : if ( rInfo.mbDateValues )
246 : {
247 0 : lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
248 0 : aBuffer.appendAscii( " - " ); // with spaces
249 0 : lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
250 : }
251 : else
252 : {
253 : rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
254 12 : rtl_math_DecimalPlaces_Max, cDecSep, true );
255 12 : aBuffer.append( '-' );
256 : rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
257 12 : rtl_math_DecimalPlaces_Max, cDecSep, true );
258 : }
259 :
260 12 : return aBuffer.makeStringAndClear();
261 : }
262 :
263 : }
264 :
265 20 : OUString ScDPUtil::getNumGroupName(
266 : double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
267 : {
268 20 : if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
269 4 : return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
270 :
271 16 : if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
272 4 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
273 :
274 12 : double fDiff = fValue - rInfo.mfStart;
275 12 : double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
276 12 : double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
277 :
278 12 : if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
279 0 : !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
280 : {
281 0 : if (rInfo.mbDateValues)
282 : {
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 0 : return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
286 : }
287 : }
288 :
289 12 : return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
290 : }
291 :
292 108 : sal_Int32 ScDPUtil::getDatePartValue(
293 : double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
294 : SvNumberFormatter* pFormatter)
295 : {
296 : // Start and end are inclusive
297 : // (End date without a time value is included, with a time value it's not)
298 :
299 108 : if (pInfo)
300 : {
301 96 : if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
302 0 : return ScDPItemData::DateFirst;
303 96 : if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
304 0 : return ScDPItemData::DateLast;
305 : }
306 :
307 108 : sal_Int32 nResult = 0;
308 :
309 108 : if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
310 108 : nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
311 : nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
312 : {
313 : // handle time
314 : // (as in the cell functions, ScInterpreter::ScGetHour etc.: seconds are rounded)
315 :
316 0 : double fTime = fValue - rtl::math::approxFloor(fValue);
317 0 : long nSeconds = (long)rtl::math::approxFloor(fTime*DATE_TIME_FACTOR+0.5);
318 :
319 0 : switch (nDatePart)
320 : {
321 : case sheet::DataPilotFieldGroupBy::HOURS:
322 0 : nResult = nSeconds / 3600;
323 0 : break;
324 : case sheet::DataPilotFieldGroupBy::MINUTES:
325 0 : nResult = ( nSeconds % 3600 ) / 60;
326 0 : break;
327 : case sheet::DataPilotFieldGroupBy::SECONDS:
328 0 : nResult = nSeconds % 60;
329 0 : break;
330 0 : }
331 : }
332 : else
333 : {
334 108 : Date aDate = *(pFormatter->GetNullDate());
335 108 : aDate += (long)::rtl::math::approxFloor(fValue);
336 :
337 108 : switch ( nDatePart )
338 : {
339 : case com::sun::star::sheet::DataPilotFieldGroupBy::YEARS:
340 44 : nResult = aDate.GetYear();
341 44 : break;
342 : case com::sun::star::sheet::DataPilotFieldGroupBy::QUARTERS:
343 32 : nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
344 32 : break;
345 : case com::sun::star::sheet::DataPilotFieldGroupBy::MONTHS:
346 32 : nResult = aDate.GetMonth(); // 1..12
347 32 : break;
348 : case com::sun::star::sheet::DataPilotFieldGroupBy::DAYS:
349 : {
350 0 : Date aYearStart(1, 1, aDate.GetYear());
351 0 : nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
352 0 : if (nResult >= 60 && !aDate.IsLeapYear())
353 : {
354 : // days are counted from 1 to 366 - if not from a leap year, adjust
355 0 : ++nResult;
356 : }
357 : }
358 0 : break;
359 : default:
360 : OSL_FAIL("invalid date part");
361 : }
362 : }
363 :
364 108 : return nResult;
365 : }
366 :
367 : namespace {
368 :
369 : sal_uInt16 nFuncStrIds[12] = {
370 : 0, // SUBTOTAL_FUNC_NONE
371 : STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
372 : STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
373 : STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
374 : STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
375 : STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
376 : STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
377 : STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
378 : STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
379 : STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
380 : STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
381 : STR_FUN_TEXT_VAR // SUBTOTAL_FUNC_VARP
382 : };
383 :
384 : }
385 :
386 272 : OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
387 : {
388 272 : OUStringBuffer aRet;
389 272 : sal_uInt16 nId = nFuncStrIds[eFunc];
390 272 : if (nId)
391 : {
392 250 : aRet.append(ScGlobal::GetRscString(nId)); // function name
393 250 : aRet.append(" - ");
394 : }
395 272 : aRet.append(rName); // field name
396 :
397 272 : return aRet.makeStringAndClear();
398 : }
399 :
400 266 : ScSubTotalFunc ScDPUtil::toSubTotalFunc(com::sun::star::sheet::GeneralFunction eGenFunc)
401 : {
402 : ScSubTotalFunc eSubTotal;
403 266 : switch (eGenFunc)
404 : {
405 0 : case sheet::GeneralFunction_NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
406 242 : case sheet::GeneralFunction_SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
407 22 : case sheet::GeneralFunction_COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
408 2 : case sheet::GeneralFunction_AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
409 0 : case sheet::GeneralFunction_MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
410 0 : case sheet::GeneralFunction_MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
411 0 : case sheet::GeneralFunction_PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
412 0 : case sheet::GeneralFunction_COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
413 0 : case sheet::GeneralFunction_STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
414 0 : case sheet::GeneralFunction_STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
415 0 : case sheet::GeneralFunction_VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
416 0 : case sheet::GeneralFunction_VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
417 : case sheet::GeneralFunction_AUTO:
418 : default:
419 0 : eSubTotal = SUBTOTAL_FUNC_NONE;
420 : }
421 266 : return eSubTotal;
422 228 : }
423 :
424 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|