Version 4.2.0.1, tag libreoffice-4.2.0.1
[LibreOffice.git] / vcl / source / window / mnemonic.cxx
blobfe45ae8928cd779b6020f5211119f3e64f453f3c
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <string.h>
22 #include <vcl/svapp.hxx>
23 #include <vcl/settings.hxx>
24 #include <vcl/mnemonic.hxx>
26 #include <vcl/unohelp.hxx>
27 #include <com/sun/star/i18n/XCharacterClassification.hpp>
28 #include <i18nlangtag/mslangid.hxx>
30 using namespace ::com::sun::star;
33 // =======================================================================
35 MnemonicGenerator::MnemonicGenerator()
37 memset( maMnemonics, 1, sizeof( maMnemonics ) );
40 // -----------------------------------------------------------------------
42 sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
44 static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
46 MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
47 MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
48 MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
49 MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
52 sal_uInt16 nMnemonicIndex = 0;
53 for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
55 if ( (c >= aImplMnemonicRangeTab[i*2]) &&
56 (c <= aImplMnemonicRangeTab[i*2+1]) )
57 return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
59 nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
62 return MNEMONIC_INDEX_NOTFOUND;
65 // -----------------------------------------------------------------------
67 sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey )
69 sal_Int32 nIndex = 0;
70 while ( (nIndex = rKey.indexOf( MNEMONIC_CHAR, nIndex )) != -1 )
72 sal_Unicode cMnemonic = rKey[ nIndex+1 ];
73 if ( cMnemonic != MNEMONIC_CHAR )
74 return cMnemonic;
75 nIndex += 2;
78 return 0;
81 // -----------------------------------------------------------------------
83 void MnemonicGenerator::RegisterMnemonic( const OUString& rKey )
85 const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale();
86 uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
88 // Don't crash even when we don't have access to i18n service
89 if ( !xCharClass.is() )
90 return;
92 OUString aKey = xCharClass->toUpper( rKey, 0, rKey.getLength(), rLocale );
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 sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
98 if ( cMnemonic )
100 sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
101 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
102 maMnemonics[nMnemonicIndex] = 0;
104 else
106 sal_Int32 nIndex = 0;
107 sal_Int32 nLen = aKey.getLength();
108 while ( nIndex < nLen )
110 sal_Unicode c = aKey[ nIndex ];
112 sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
113 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
115 if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
116 maMnemonics[nMnemonicIndex]++;
119 nIndex++;
124 // -----------------------------------------------------------------------
126 OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
128 if ( _rKey.isEmpty() || ImplFindMnemonic( _rKey ) )
129 return _rKey;
131 const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale();
132 uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
134 // Don't crash even when we don't have access to i18n service
135 if ( !xCharClass.is() )
136 return _rKey;
138 OUString aKey = xCharClass->toUpper( _rKey, 0, _rKey.getLength(), rLocale );
140 sal_Bool bChanged = sal_False;
141 sal_Int32 nLen = aKey.getLength();
143 bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
145 // #107889# in CJK versions ALL strings (even those that contain latin characters)
146 // will get mnemonics in the form: xyz (M)
147 // thus steps 1) and 2) are skipped for CJK locales
149 // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
150 if( bCJK )
152 bool bLatinOnly = true;
153 bool bMnemonicIndexFound = false;
154 sal_Unicode c;
155 sal_Int32 nIndex;
157 for( nIndex=0; nIndex < nLen; nIndex++ )
159 c = aKey[ nIndex ];
160 if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
161 ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
163 bLatinOnly = false;
164 break;
166 if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
167 bMnemonicIndexFound = true;
169 if( bLatinOnly && !bMnemonicIndexFound )
170 return _rKey;
173 OUString rKey(_rKey);
174 int nCJK = 0;
175 sal_uInt16 nMnemonicIndex;
176 sal_Unicode c;
177 sal_Int32 nIndex = 0;
178 if( !bCJK )
180 // 1) first try the first character of a word
183 c = aKey[ nIndex ];
185 if ( nCJK != 2 )
187 if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
188 ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
189 nCJK = 1;
190 else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
191 ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
192 ((c >= 0x0061) && (c <= 0x007A)) || // latin small
193 ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
194 ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
195 nCJK = 2;
198 nMnemonicIndex = ImplGetMnemonicIndex( c );
199 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
201 if ( maMnemonics[nMnemonicIndex] )
203 maMnemonics[nMnemonicIndex] = 0;
204 rKey = rKey.replaceAt( nIndex, 0, OUString(MNEMONIC_CHAR) );
205 bChanged = sal_True;
206 break;
210 // Search for next word
211 nIndex++;
212 while ( nIndex < nLen )
214 c = aKey[ nIndex ];
215 if ( c == ' ' )
216 break;
217 nIndex++;
219 nIndex++;
221 while ( nIndex < nLen );
223 // 2) search for a unique/uncommon character
224 if ( !bChanged )
226 sal_uInt16 nBestCount = 0xFFFF;
227 sal_uInt16 nBestMnemonicIndex = 0;
228 sal_Int32 nBestIndex = 0;
229 nIndex = 0;
232 c = aKey[ nIndex ];
233 nMnemonicIndex = ImplGetMnemonicIndex( c );
234 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
236 if ( maMnemonics[nMnemonicIndex] )
238 if ( maMnemonics[nMnemonicIndex] < nBestCount )
240 nBestCount = maMnemonics[nMnemonicIndex];
241 nBestIndex = nIndex;
242 nBestMnemonicIndex = nMnemonicIndex;
243 if ( nBestCount == 2 )
244 break;
249 nIndex++;
251 while ( nIndex < nLen );
253 if ( nBestCount != 0xFFFF )
255 maMnemonics[nBestMnemonicIndex] = 0;
256 rKey = rKey.replaceAt( nBestIndex, 0, OUString(MNEMONIC_CHAR) );
257 bChanged = sal_True;
261 else
262 nCJK = 1;
264 // 3) Add English Mnemonic for CJK Text
265 if ( !bChanged && (nCJK == 1) && !rKey.isEmpty() )
267 // Append Ascii Mnemonic
268 for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
270 nMnemonicIndex = ImplGetMnemonicIndex( c );
271 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
273 if ( maMnemonics[nMnemonicIndex] )
275 maMnemonics[nMnemonicIndex] = 0;
276 OUString aStr = OUStringBuffer().
277 append('(').append(MNEMONIC_CHAR).append(c).
278 append(')').makeStringAndClear();
279 nIndex = rKey.getLength();
280 if( nIndex >= 2 )
282 if ( ( rKey[nIndex-2] == '>' && rKey[nIndex-1] == '>' ) ||
283 ( rKey[nIndex-2] == 0xFF1E && rKey[nIndex-1] == 0xFF1E ) )
284 nIndex -= 2;
286 if( nIndex >= 3 )
288 if ( ( rKey[nIndex-3] == '.' && rKey[nIndex-2] == '.' && rKey[nIndex-1] == '.' ) ||
289 ( rKey[nIndex-3] == 0xFF0E && rKey[nIndex-2] == 0xFF0E && rKey[nIndex-1] == 0xFF0E ) )
290 nIndex -= 3;
292 if( nIndex >= 1)
294 sal_Unicode cLastChar = rKey[ nIndex-1 ];
295 if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
296 (cLastChar == '.') || (cLastChar == 0xFF0E) ||
297 (cLastChar == '?') || (cLastChar == 0xFF1F) ||
298 (cLastChar == ' ') )
299 nIndex--;
301 rKey = rKey.replaceAt( nIndex, 0, aStr );
302 bChanged = sal_True;
303 break;
309 // #i87415# Duplicates mnemonics are bad for consistent keyboard accessibility
310 // It's probably better to not have mnemonics for some widgets, than to have ambiguous ones.
311 // if( ! bChanged )
312 // {
313 // /*
314 // * #97809# if all else fails use the first character of a word
315 // * anyway and live with duplicate mnemonics
316 // */
317 // nIndex = 0;
318 // do
319 // {
320 // c = aKey.GetChar( nIndex );
322 // nMnemonicIndex = ImplGetMnemonicIndex( c );
323 // if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
324 // {
325 // maMnemonics[nMnemonicIndex] = 0;
326 // rKey.Insert( MNEMONIC_CHAR, nIndex );
327 // bChanged = sal_True;
328 // break;
329 // }
331 // // Search for next word
332 // do
333 // {
334 // nIndex++;
335 // c = aKey.GetChar( nIndex );
336 // if ( c == ' ' )
337 // break;
338 // }
339 // while ( nIndex < nLen );
340 // nIndex++;
341 // }
342 // while ( nIndex < nLen );
343 // }
345 return rKey;
348 // -----------------------------------------------------------------------
350 uno::Reference< i18n::XCharacterClassification > MnemonicGenerator::GetCharClass()
352 if ( !mxCharClass.is() )
353 mxCharClass = vcl::unohelper::CreateCharacterClassification();
354 return mxCharClass;
357 // -----------------------------------------------------------------------
359 OUString MnemonicGenerator::EraseAllMnemonicChars( const OUString& rStr )
361 OUString aStr = rStr;
362 sal_Int32 nLen = aStr.getLength();
363 sal_Int32 i = 0;
365 while ( i < nLen )
367 if ( aStr[ i ] == '~' )
369 // check for CJK-style mnemonic
370 if( i > 0 && (i+2) < nLen )
372 sal_Unicode c = aStr[i+1];
373 if( aStr[ i-1 ] == '(' &&
374 aStr[ i+2 ] == ')' &&
375 c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
377 aStr = aStr.replaceAt( i-1, 4, "" );
378 nLen -= 4;
379 i--;
380 continue;
384 // remove standard mnemonics
385 aStr = aStr.replaceAt( i, 1, "" );
386 nLen--;
388 else
389 i++;
392 return aStr;
395 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */