Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/control/longcurr.cxx
Line
Count
Source
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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <string_view>
23
24
#include <comphelper/string.hxx>
25
#include <tools/bigint.hxx>
26
#include <sal/log.hxx>
27
28
#include <vcl/event.hxx>
29
#include <vcl/svapp.hxx>
30
#include <vcl/toolkit/longcurr.hxx>
31
#include <vcl/weldutils.hxx>
32
33
#include <unotools/localedatawrapper.hxx>
34
35
using namespace ::comphelper;
36
37
namespace
38
{
39
40
BigInt ImplPower10( sal_uInt16 n )
41
0
{
42
0
    sal_uInt16 i;
43
0
    BigInt   nValue = 1;
44
45
0
    for ( i=0; i < n; i++ )
46
0
        nValue *= 10;
47
48
0
    return nValue;
49
0
}
50
51
OUString ImplGetCurr( const LocaleDataWrapper& rLocaleDataWrapper, const BigInt &rNumber, sal_uInt16 nDigits, std::u16string_view rCurrSymbol, bool bShowThousandSep )
52
0
{
53
0
    SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
54
55
0
    if ( rNumber.IsZero() || static_cast<tools::Long>(rNumber) )
56
0
        return rLocaleDataWrapper.getCurr( static_cast<tools::Long>(rNumber), nDigits, rCurrSymbol, bShowThousandSep );
57
58
0
    BigInt aTmp( ImplPower10( nDigits ) );
59
0
    BigInt aInteger;
60
0
    BigInt aFraction;
61
0
    rNumber.Abs().DivMod(aTmp, &aInteger, &aFraction);
62
0
    if ( !aInteger.IsZero() )
63
0
    {
64
0
        aFraction += aTmp;
65
0
        aTmp       = 1000000000;
66
0
    }
67
0
    if ( rNumber.IsNeg() )
68
0
        aFraction *= -1;
69
70
0
    OUStringBuffer aTemplate(rLocaleDataWrapper.getCurr( static_cast<tools::Long>(aFraction), nDigits, rCurrSymbol, bShowThousandSep ));
71
0
    while( !aInteger.IsZero() )
72
0
    {
73
0
        aInteger.DivMod(aTmp, &aInteger, &aFraction);
74
0
        if( !aInteger.IsZero() )
75
0
            aFraction += aTmp;
76
77
0
        OUString aFractionStr = rLocaleDataWrapper.getNum( static_cast<tools::Long>(aFraction), 0 );
78
79
0
        sal_Int32 nSPos = aTemplate.indexOf( '1' );
80
0
        if (nSPos == -1)
81
0
            break;
82
0
        if ( aFractionStr.getLength() == 1 )
83
0
            aTemplate[ nSPos ] = aFractionStr[0];
84
0
        else
85
0
        {
86
0
            aTemplate.remove( nSPos, 1 );
87
0
            aTemplate.insert( nSPos, aFractionStr  );
88
0
        }
89
0
    }
90
91
0
    return aTemplate.makeStringAndClear();
92
0
}
93
94
bool ImplCurrencyGetValue( const OUString& rStr, BigInt& rValue,
95
                                 sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
96
0
{
97
0
    OUString aStr = rStr;
98
0
    OUStringBuffer aStr1;
99
0
    OUStringBuffer aStr2;
100
0
    sal_Int32 nDecPos;
101
0
    bool bNegative = false;
102
103
    // On empty string
104
0
    if ( rStr.isEmpty() )
105
0
        return false;
106
107
    // Trim leading and trailing spaces
108
0
    aStr = string::strip(aStr, ' ');
109
110
    // Find decimal sign's position
111
0
    nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
112
0
    if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
113
0
        nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
114
115
0
    if ( nDecPos != -1 )
116
0
    {
117
0
        aStr1 = aStr.subView( 0, nDecPos );
118
0
        aStr2.append(aStr.subView(nDecPos+1));
119
0
    }
120
0
    else
121
0
        aStr1 = aStr;
122
123
    // Negative?
124
0
    if ( (aStr[ 0 ] == '(') && (aStr[ aStr.getLength()-1 ] == ')') )
125
0
        bNegative = true;
126
0
    if ( !bNegative )
127
0
    {
128
0
        for (sal_Int32 i=0; i < aStr.getLength(); i++ )
129
0
        {
130
0
            if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
131
0
                break;
132
0
            else if ( aStr[ i ] == '-' )
133
0
            {
134
0
                bNegative = true;
135
0
                break;
136
0
            }
137
0
        }
138
0
    }
139
0
    if ( !bNegative && !aStr.isEmpty() )
140
0
    {
141
0
        sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
142
0
        if ( (nFormat == 3) || (nFormat == 6)  ||
143
0
             (nFormat == 7) || (nFormat == 10) )
144
0
        {
145
0
            for (sal_Int32 i = aStr.getLength()-1; i > 0; i++ )
146
0
            {
147
0
                if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
148
0
                    break;
149
0
                else if ( aStr[ i ] == '-' )
150
0
                {
151
0
                    bNegative = true;
152
0
                    break;
153
0
                }
154
0
            }
155
0
        }
156
0
    }
157
158
    // delete unwanted characters
159
0
    for (sal_Int32 i=0; i < aStr1.getLength(); )
160
0
    {
161
0
        if ( (aStr1[ i ] >= '0') && (aStr1[ i ] <= '9') )
162
0
            i++;
163
0
        else
164
0
            aStr1.remove( i, 1 );
165
0
    }
166
0
    for (sal_Int32 i=0; i < aStr2.getLength(); )
167
0
    {
168
0
        if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
169
0
            ++i;
170
0
        else
171
0
            aStr2.remove(i, 1);
172
0
    }
173
174
0
    if ( aStr1.isEmpty() && aStr2.isEmpty())
175
0
        return false;
176
177
0
    if ( aStr1.isEmpty() )
178
0
        aStr1 = "0";
179
0
    if ( bNegative )
180
0
        aStr1.insert( 0, '-');
181
182
    // Cut down decimal part and round while doing so
183
0
    bool bRound = false;
184
0
    if (aStr2.getLength() > nDecDigits)
185
0
    {
186
0
        if (aStr2[nDecDigits] >= '5')
187
0
            bRound = true;
188
0
        string::truncateToLength(aStr2, nDecDigits);
189
0
    }
190
0
    string::padToLength(aStr2, nDecDigits, '0');
191
192
0
    aStr1.append(aStr2);
193
0
    aStr  = aStr1.makeStringAndClear();
194
195
    // check range
196
0
    BigInt nValue( aStr );
197
0
    if ( bRound )
198
0
    {
199
0
        if ( !bNegative )
200
0
            nValue+=1;
201
0
        else
202
0
            nValue-=1;
203
0
    }
204
205
0
    rValue = nValue;
206
207
0
    return true;
208
0
}
209
210
} // namespace
211
212
namespace weld
213
{
214
    IMPL_LINK(LongCurrencyFormatter, FormatOutputHdl, double, fValue, std::optional<OUString>)
215
0
    {
216
0
        const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
217
0
        const OUString& rCurrencySymbol = !m_aCurrencySymbol.isEmpty() ? m_aCurrencySymbol : rLocaleDataWrapper.getCurrSymbol();
218
0
        sal_uInt16 nDecimalDigits = GetDecimalDigits();
219
0
        if (nDecimalDigits)
220
0
        {
221
            // tdf#158669 round to decimal digits
222
0
            fValue = std::round(fValue * weld::SpinButton::Power10(nDecimalDigits));
223
0
        }
224
0
        OUString aText = ImplGetCurr(rLocaleDataWrapper, fValue, GetDecimalDigits(), rCurrencySymbol, m_bThousandSep);
225
0
        return std::optional<OUString>(aText);
226
0
    }
227
228
    IMPL_LINK(LongCurrencyFormatter, ParseInputHdl, const OUString&, rText, Formatter::ParseResult)
229
0
    {
230
0
        const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
231
232
0
        BigInt value;
233
0
        bool bRet = ImplCurrencyGetValue(rText, value, GetDecimalDigits(), rLocaleDataWrapper);
234
235
0
        double fValue = 0;
236
0
        if (bRet)
237
0
            fValue = double(value) / weld::SpinButton::Power10(GetDecimalDigits());
238
239
0
        return Formatter::ParseResult(bRet ? TRISTATE_TRUE : TRISTATE_FALSE, fValue);
240
0
    }
241
}
242
243
bool ImplLongCurrencyReformat( const OUString& rStr, BigInt const & nMin, BigInt const & nMax,
244
                               sal_uInt16 nDecDigits,
245
                               const LocaleDataWrapper& rLocaleDataWrapper, OUString& rOutStr,
246
                               LongCurrencyFormatter const & rFormatter )
247
0
{
248
0
    BigInt nValue;
249
0
    if ( !ImplCurrencyGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
250
0
        return true;
251
0
    else
252
0
    {
253
0
        BigInt nTempVal = nValue;
254
0
        if ( nTempVal > nMax )
255
0
            nTempVal = nMax;
256
0
        else if ( nTempVal < nMin )
257
0
            nTempVal = nMin;
258
259
0
        rOutStr = ImplGetCurr( rLocaleDataWrapper, nTempVal, nDecDigits, rFormatter.GetCurrencySymbol(), /*IsUseThousandSep*/true );
260
0
        return true;
261
0
    }
262
0
}
263
264
void LongCurrencyFormatter::ImpInit()
265
0
{
266
0
    mnLastValue         = 0;
267
0
    mnMin               = 0;
268
0
    mnMax               = 0x7FFFFFFF;
269
0
    mnMax              *= 0x7FFFFFFF;
270
271
0
    ReformatAll();
272
0
}
273
274
LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
275
0
    : FormatterBase(pEdit)
276
0
{
277
0
    ImpInit();
278
0
}
279
280
LongCurrencyFormatter::~LongCurrencyFormatter()
281
0
{
282
0
}
283
284
OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
285
0
{
286
0
    return GetLocaleDataWrapper().getCurrSymbol();
287
0
}
288
289
void LongCurrencyFormatter::SetValue(const BigInt& rNewValue)
290
0
{
291
0
    SetUserValue(rNewValue);
292
0
    SetEmptyFieldValueData( false );
293
0
}
294
295
void LongCurrencyFormatter::SetUserValue( BigInt nNewValue )
296
0
{
297
0
    if ( nNewValue > mnMax )
298
0
        nNewValue = mnMax;
299
0
    else if ( nNewValue < mnMin )
300
0
        nNewValue = mnMin;
301
0
    mnLastValue = nNewValue;
302
303
0
    if ( !GetField() )
304
0
        return;
305
306
0
    OUString aStr = ImplGetCurr( GetLocaleDataWrapper(), nNewValue, GetDecimalDigits(), GetCurrencySymbol(), /*UseThousandSep*/true );
307
0
    if ( GetField()->HasFocus() )
308
0
    {
309
0
        Selection aSelection = GetField()->GetSelection();
310
0
        GetField()->SetText( aStr );
311
0
        GetField()->SetSelection( aSelection );
312
0
    }
313
0
    else
314
0
        GetField()->SetText( aStr );
315
0
    MarkToBeReformatted( false );
316
0
}
317
318
BigInt LongCurrencyFormatter::GetValue() const
319
0
{
320
0
    if ( !GetField() )
321
0
        return 0;
322
323
0
    BigInt nTempValue;
324
0
    if (ImplCurrencyGetValue(GetField()->GetText(), nTempValue, GetDecimalDigits(), GetLocaleDataWrapper()))
325
0
    {
326
0
        if ( nTempValue > mnMax )
327
0
            nTempValue = mnMax;
328
0
        else if ( nTempValue < mnMin )
329
0
            nTempValue = mnMin;
330
0
        return nTempValue;
331
0
    }
332
0
    else
333
0
        return mnLastValue;
334
0
}
335
336
void LongCurrencyFormatter::Reformat()
337
0
{
338
0
    if ( !GetField() )
339
0
        return;
340
341
0
    if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
342
0
        return;
343
344
0
    OUString aStr;
345
0
    bool bOK = ImplLongCurrencyReformat( GetField()->GetText(), mnMin, mnMax,
346
0
                                         GetDecimalDigits(), GetLocaleDataWrapper(), aStr, *this );
347
0
    if ( !bOK )
348
0
        return;
349
350
0
    if ( !aStr.isEmpty() )
351
0
    {
352
0
        GetField()->SetText( aStr );
353
0
        MarkToBeReformatted( false );
354
0
        ImplCurrencyGetValue(aStr, mnLastValue, GetDecimalDigits(), GetLocaleDataWrapper());
355
0
    }
356
0
    else
357
0
        SetValue( mnLastValue );
358
0
}
359
360
void LongCurrencyFormatter::ReformatAll()
361
0
{
362
0
    Reformat();
363
0
}
364
365
LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
366
0
    : ComboBox(pParent, nWinStyle)
367
0
    , LongCurrencyFormatter(this)
368
0
{
369
0
    Reformat();
370
0
}
Unexecuted instantiation: LongCurrencyBox::LongCurrencyBox(vcl::Window*, long)
Unexecuted instantiation: LongCurrencyBox::LongCurrencyBox(vcl::Window*, long)
371
372
bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
373
0
{
374
0
    if( rNEvt.GetType() == NotifyEventType::GETFOCUS )
375
0
    {
376
0
        MarkToBeReformatted( false );
377
0
    }
378
0
    else if( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
379
0
    {
380
0
        if ( MustBeReformatted() )
381
0
        {
382
0
            Reformat();
383
0
            ComboBox::Modify();
384
0
        }
385
0
    }
386
0
    return ComboBox::EventNotify( rNEvt );
387
0
}
388
389
void LongCurrencyBox::Modify()
390
0
{
391
0
    MarkToBeReformatted( true );
392
0
    ComboBox::Modify();
393
0
}
394
395
void LongCurrencyBox::ReformatAll()
396
0
{
397
0
    OUString aStr;
398
0
    SetUpdateMode( false );
399
0
    const sal_Int32 nEntryCount = GetEntryCount();
400
0
    for ( sal_Int32 i=0; i < nEntryCount; ++i )
401
0
    {
402
0
        ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
403
0
                                  GetDecimalDigits(), GetLocaleDataWrapper(),
404
0
                                  aStr, *this );
405
0
        RemoveEntryAt(i);
406
0
        InsertEntry( aStr, i );
407
0
    }
408
0
    LongCurrencyFormatter::Reformat();
409
0
    SetUpdateMode( true );
410
0
}
411
412
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */