Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/vcl/source/window/mnemonic.cxx
Line
Count
Source (jump to first uncovered line)
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 <string.h>
21
#include <vcl/svapp.hxx>
22
#include <vcl/settings.hxx>
23
#include <vcl/mnemonic.hxx>
24
25
#include <vcl/unohelp.hxx>
26
#include <com/sun/star/i18n/XCharacterClassification.hpp>
27
#include <i18nlangtag/languagetag.hxx>
28
#include <i18nlangtag/mslangid.hxx>
29
#include <rtl/character.hxx>
30
#include <sal/log.hxx>
31
32
using namespace ::com::sun::star;
33
34
MnemonicGenerator::MnemonicGenerator(sal_Unicode cMnemonic)
35
0
    : m_cMnemonic(cMnemonic)
36
0
{
37
0
    memset( maMnemonics, 1, sizeof( maMnemonics ) );
38
0
}
39
40
0
MnemonicGenerator& MnemonicGenerator::operator=(MnemonicGenerator const &) = default; //MSVC2022 workaround
41
0
MnemonicGenerator::MnemonicGenerator(MnemonicGenerator const&) = default; //MSVC2022 workaround
42
43
sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
44
0
{
45
0
    static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
46
0
    {
47
0
        MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
48
0
        MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
49
0
        MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
50
0
        MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
51
0
    };
52
53
0
    sal_uInt16 nMnemonicIndex = 0;
54
0
    for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
55
0
    {
56
0
        if ( (c >= aImplMnemonicRangeTab[i*2]) &&
57
0
             (c <= aImplMnemonicRangeTab[i*2+1]) )
58
0
            return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
59
60
0
        nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
61
0
    }
62
63
0
    return MNEMONIC_INDEX_NOTFOUND;
64
0
}
65
66
sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey )
67
0
{
68
0
    sal_Int32 nIndex = 0;
69
0
    while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 )
70
0
    {
71
0
        if (nIndex == rKey.getLength() - 1) {
72
0
            SAL_WARN("vcl", "key \"" << rKey << "\" ends in lone mnemonic prefix");
73
0
            break;
74
0
        }
75
0
        sal_Unicode cMnemonic = rKey[ nIndex+1 ];
76
0
        if ( cMnemonic != m_cMnemonic )
77
0
            return cMnemonic;
78
0
        nIndex += 2;
79
0
    }
80
81
0
    return 0;
82
0
}
83
84
void MnemonicGenerator::RegisterMnemonic( const OUString& rKey )
85
0
{
86
0
    uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
87
88
    // Don't crash even when we don't have access to i18n service
89
0
    if ( !xCharClass.is() )
90
0
        return;
91
92
0
    OUString aKey = xCharClass->toLower(rKey, 0, rKey.getLength(), css::lang::Locale());
93
94
    // If we find a Mnemonic, set the flag. In other case count the
95
    // characters, because we need this to set most as possible
96
    // Mnemonics
97
0
    sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
98
0
    if ( cMnemonic )
99
0
    {
100
0
        sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
101
0
        if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
102
0
            maMnemonics[nMnemonicIndex] = 0;
103
0
    }
104
0
    else
105
0
    {
106
0
        sal_Int32 nIndex = 0;
107
0
        sal_Int32 nLen = aKey.getLength();
108
0
        while ( nIndex < nLen )
109
0
        {
110
0
            sal_Unicode c = aKey[ nIndex ];
111
112
0
            sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
113
0
            if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
114
0
            {
115
0
                if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
116
0
                    maMnemonics[nMnemonicIndex]++;
117
0
            }
118
119
0
            nIndex++;
120
0
        }
121
0
    }
122
0
}
123
124
OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
125
0
{
126
0
    if ( _rKey.isEmpty() || ImplFindMnemonic( _rKey ) )
127
0
        return _rKey;
128
129
0
    uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
130
131
    // Don't crash even when we don't have access to i18n service
132
0
    if ( !xCharClass.is() )
133
0
        return _rKey;
134
135
0
    OUString aKey = xCharClass->toLower(_rKey, 0, _rKey.getLength(), css::lang::Locale());
136
137
0
    bool bChanged = false;
138
0
    sal_Int32 nLen = aKey.getLength();
139
140
0
    bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
141
142
    // #107889# in CJK versions ALL strings (even those that contain latin characters)
143
    // will get mnemonics in the form: xyz (M)
144
    // thus steps 1) and 2) are skipped for CJK locales
145
146
    // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
147
0
    if( bCJK )
148
0
    {
149
0
        bool bLatinOnly = true;
150
0
        bool bMnemonicIndexFound = false;
151
0
        sal_Unicode     c;
152
0
        sal_Int32       nIndex;
153
154
0
        for( nIndex=0; nIndex < nLen; nIndex++ )
155
0
        {
156
0
            c = aKey[ nIndex ];
157
0
            if ( ((c >= 0x3000) && (c <= 0xD7FF)) ||    // cjk
158
0
                 ((c >= 0xFF61) && (c <= 0xFFDC)) )     // halfwidth forms
159
0
            {
160
0
                bLatinOnly = false;
161
0
                break;
162
0
            }
163
0
            if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
164
0
                bMnemonicIndexFound = true;
165
0
        }
166
0
        if( bLatinOnly && !bMnemonicIndexFound )
167
0
            return _rKey;
168
0
    }
169
170
0
    OUString        rKey(_rKey);
171
0
    int             nCJK = 0;
172
0
    sal_uInt16      nMnemonicIndex;
173
0
    sal_Unicode     c;
174
0
    sal_Int32       nIndex = 0;
175
0
    if( !bCJK )
176
0
    {
177
        // 1) first try the first character of a word
178
0
        do
179
0
        {
180
0
            c = aKey[ nIndex ];
181
182
0
            if ( nCJK != 2 )
183
0
            {
184
0
                if ( ((c >= 0x3000) && (c <= 0xD7FF)) ||    // cjk
185
0
                    ((c >= 0xFF61) && (c <= 0xFFDC)) )     // halfwidth forms
186
0
                    nCJK = 1;
187
0
                else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
188
0
                        ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
189
0
                        ((c >= 0x0061) && (c <= 0x007A)) || // latin small
190
0
                        ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
191
0
                        ((c >= 0x0400) && (c <= 0x04FF)) )  // cyrillic
192
0
                    nCJK = 2;
193
0
            }
194
195
0
            nMnemonicIndex = ImplGetMnemonicIndex( c );
196
0
            if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
197
0
            {
198
0
                if ( maMnemonics[nMnemonicIndex] )
199
0
                {
200
0
                    maMnemonics[nMnemonicIndex] = 0;
201
0
                    rKey = rKey.replaceAt( nIndex, 0, rtl::OUStringChar(m_cMnemonic) );
202
0
                    bChanged = true;
203
0
                    break;
204
0
                }
205
0
            }
206
207
            // Search for next word
208
0
            nIndex++;
209
0
            while ( nIndex < nLen )
210
0
            {
211
0
                c = aKey[ nIndex ];
212
0
                if ( c == ' ' )
213
0
                    break;
214
0
                nIndex++;
215
0
            }
216
0
            nIndex++;
217
0
        }
218
0
        while ( nIndex < nLen );
219
220
        // 2) search for a unique/uncommon character
221
0
        if ( !bChanged )
222
0
        {
223
0
            sal_uInt16      nBestCount = 0xFFFF;
224
0
            sal_uInt16      nBestMnemonicIndex = 0;
225
0
            sal_Int32       nBestIndex = 0;
226
0
            nIndex = 0;
227
0
            do
228
0
            {
229
0
                c = aKey[ nIndex ];
230
0
                nMnemonicIndex = ImplGetMnemonicIndex( c );
231
0
                if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
232
0
                {
233
0
                    if ( maMnemonics[nMnemonicIndex] )
234
0
                    {
235
0
                        if ( maMnemonics[nMnemonicIndex] < nBestCount )
236
0
                        {
237
0
                            nBestCount = maMnemonics[nMnemonicIndex];
238
0
                            nBestIndex = nIndex;
239
0
                            nBestMnemonicIndex = nMnemonicIndex;
240
0
                            if ( nBestCount == 2 )
241
0
                                break;
242
0
                        }
243
0
                    }
244
0
                }
245
246
0
                nIndex++;
247
0
            }
248
0
            while ( nIndex < nLen );
249
250
0
            if ( nBestCount != 0xFFFF )
251
0
            {
252
0
                maMnemonics[nBestMnemonicIndex] = 0;
253
0
                rKey = rKey.replaceAt( nBestIndex, 0, rtl::OUStringChar(m_cMnemonic) );
254
0
                bChanged = true;
255
0
            }
256
0
        }
257
0
    }
258
0
    else
259
0
        nCJK = 1;
260
261
    // 3) Add English Mnemonic for CJK Text
262
0
    if ( !bChanged && (nCJK == 1) && !rKey.isEmpty() )
263
0
    {
264
        // Append Ascii Mnemonic
265
0
        for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
266
0
        {
267
0
            nMnemonicIndex = ImplGetMnemonicIndex(c);
268
0
            if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
269
0
            {
270
0
                if ( maMnemonics[nMnemonicIndex] )
271
0
                {
272
0
                    maMnemonics[nMnemonicIndex] = 0;
273
0
                    OUString aStr = OUString::Concat("(") + OUStringChar(m_cMnemonic) +
274
0
                            OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(c))) +
275
0
                            ")";
276
0
                    nIndex = rKey.getLength();
277
0
                    if( nIndex >= 2 )
278
0
                    {
279
0
                        if ( ( rKey[nIndex-2] == '>'    && rKey[nIndex-1] == '>' ) ||
280
0
                             ( rKey[nIndex-2] == 0xFF1E && rKey[nIndex-1] == 0xFF1E ) )
281
0
                            nIndex -= 2;
282
0
                    }
283
0
                    if( nIndex >= 3 )
284
0
                    {
285
0
                        if ( ( rKey[nIndex-3] == '.'    && rKey[nIndex-2] == '.'    && rKey[nIndex-1] == '.' ) ||
286
0
                             ( rKey[nIndex-3] == 0xFF0E && rKey[nIndex-2] == 0xFF0E && rKey[nIndex-1] == 0xFF0E ) )
287
0
                            nIndex -= 3;
288
0
                    }
289
0
                    if( nIndex >= 1)
290
0
                    {
291
0
                        sal_Unicode cLastChar = rKey[ nIndex-1 ];
292
0
                        if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
293
0
                            (cLastChar == '.') || (cLastChar == 0xFF0E) ||
294
0
                            (cLastChar == '?') || (cLastChar == 0xFF1F) ||
295
0
                            (cLastChar == ' ') )
296
0
                            nIndex--;
297
0
                    }
298
0
                    rKey = rKey.replaceAt( nIndex, 0, aStr );
299
0
                    break;
300
0
                }
301
0
            }
302
0
        }
303
0
    }
304
305
0
    return rKey;
306
0
}
307
308
uno::Reference< i18n::XCharacterClassification > const & MnemonicGenerator::GetCharClass()
309
0
{
310
0
    if ( !mxCharClass.is() )
311
0
        mxCharClass = vcl::unohelper::CreateCharacterClassification();
312
0
    return mxCharClass;
313
0
}
314
315
OUString MnemonicGenerator::EraseAllMnemonicChars( const OUString& rStr )
316
45
{
317
45
    OUString    aStr = rStr;
318
45
    sal_Int32   nLen = aStr.getLength();
319
45
    sal_Int32   i    = 0;
320
321
567
    while ( i < nLen )
322
522
    {
323
522
        if ( aStr[ i ] == '~' )
324
1
        {
325
            // check for CJK-style mnemonic
326
1
            if( i > 0 && (i+2) < nLen )
327
0
            {
328
0
                sal_Unicode c = sal_Unicode(rtl::toAsciiLowerCase(aStr[i+1]));
329
0
                if( aStr[ i-1 ] == '(' &&
330
0
                    aStr[ i+2 ] == ')' &&
331
0
                    c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
332
0
                {
333
0
                    aStr = aStr.replaceAt( i-1, 4, u"" );
334
0
                    nLen -= 4;
335
0
                    i--;
336
0
                    continue;
337
0
                }
338
0
            }
339
340
            // remove standard mnemonics
341
1
            aStr = aStr.replaceAt( i, 1, u"" );
342
1
            nLen--;
343
1
        }
344
521
        else
345
521
            i++;
346
522
    }
347
348
45
    return aStr;
349
45
}
350
351
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */