1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_vcl.hxx"
26
27 #include <string.h>
28 #include <vcl/svapp.hxx>
29 #include <vcl/settings.hxx>
30 #include <vcl/mnemonic.hxx>
31
32 #include <vcl/unohelp.hxx>
33 #include <com/sun/star/i18n/XCharacterClassification.hpp>
34
35 using namespace ::com::sun::star;
36
37
38 // =======================================================================
39
MnemonicGenerator()40 MnemonicGenerator::MnemonicGenerator()
41 {
42 memset( maMnemonics, 1, sizeof( maMnemonics ) );
43 }
44
45 // -----------------------------------------------------------------------
46
ImplGetMnemonicIndex(sal_Unicode c)47 sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
48 {
49 static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
50 {
51 MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
52 MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
53 MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
54 MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
55 };
56
57 sal_uInt16 nMnemonicIndex = 0;
58 for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
59 {
60 if ( (c >= aImplMnemonicRangeTab[i*2]) &&
61 (c <= aImplMnemonicRangeTab[i*2+1]) )
62 return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
63
64 nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
65 }
66
67 return MNEMONIC_INDEX_NOTFOUND;
68 }
69
70 // -----------------------------------------------------------------------
71
ImplFindMnemonic(const XubString & rKey)72 sal_Unicode MnemonicGenerator::ImplFindMnemonic( const XubString& rKey )
73 {
74 xub_StrLen nIndex = 0;
75 while ( (nIndex = rKey.Search( MNEMONIC_CHAR, nIndex )) != STRING_NOTFOUND )
76 {
77 sal_Unicode cMnemonic = rKey.GetChar( nIndex+1 );
78 if ( cMnemonic != MNEMONIC_CHAR )
79 return cMnemonic;
80 nIndex += 2;
81 }
82
83 return 0;
84 }
85
86 // -----------------------------------------------------------------------
87
RegisterMnemonic(const XubString & rKey)88 void MnemonicGenerator::RegisterMnemonic( const XubString& rKey )
89 {
90 const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILocale();
91 uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
92
93 // Don't crash even when we don't have access to i18n service
94 if ( !xCharClass.is() )
95 return;
96
97 XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
98
99 // If we find a Mnemonic, set the flag. In other case count the
100 // characters, because we need this to set most as possible
101 // Mnemonics
102 sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
103 if ( cMnemonic )
104 {
105 sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
106 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
107 maMnemonics[nMnemonicIndex] = 0;
108 }
109 else
110 {
111 xub_StrLen nIndex = 0;
112 xub_StrLen nLen = aKey.Len();
113 while ( nIndex < nLen )
114 {
115 sal_Unicode c = aKey.GetChar( nIndex );
116
117 sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
118 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
119 {
120 if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
121 maMnemonics[nMnemonicIndex]++;
122 }
123
124 nIndex++;
125 }
126 }
127 }
128
129 // -----------------------------------------------------------------------
130
CreateMnemonic(XubString & rKey)131 sal_Bool MnemonicGenerator::CreateMnemonic( XubString& rKey )
132 {
133 if ( !rKey.Len() || ImplFindMnemonic( rKey ) )
134 return sal_False;
135
136 const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILocale();
137 uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
138
139 // Don't crash even when we don't have access to i18n service
140 if ( !xCharClass.is() )
141 return sal_False;
142
143 XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
144
145 sal_Bool bChanged = sal_False;
146 xub_StrLen nLen = aKey.Len();
147
148 sal_Bool bCJK = sal_False;
149 switch( Application::GetSettings().GetUILanguage() )
150 {
151 case LANGUAGE_JAPANESE:
152 case LANGUAGE_CHINESE_TRADITIONAL:
153 case LANGUAGE_CHINESE_SIMPLIFIED:
154 case LANGUAGE_CHINESE_HONGKONG:
155 case LANGUAGE_CHINESE_SINGAPORE:
156 case LANGUAGE_CHINESE_MACAU:
157 case LANGUAGE_KOREAN:
158 case LANGUAGE_KOREAN_JOHAB:
159 bCJK = sal_True;
160 break;
161 default:
162 break;
163 }
164 // #107889# in CJK versions ALL strings (even those that contain latin characters)
165 // will get mnemonics in the form: xyz (M)
166 // thus steps 1) and 2) are skipped for CJK locales
167
168 // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
169 if( bCJK )
170 {
171 sal_Bool bLatinOnly = sal_True;
172 sal_Bool bMnemonicIndexFound = sal_False;
173 sal_Unicode c;
174 xub_StrLen nIndex;
175
176 for( nIndex=0; nIndex < nLen; nIndex++ )
177 {
178 c = aKey.GetChar( nIndex );
179 if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
180 ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
181 {
182 bLatinOnly = sal_False;
183 break;
184 }
185 if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
186 bMnemonicIndexFound = sal_True;
187 }
188 if( bLatinOnly && !bMnemonicIndexFound )
189 return sal_False;
190 }
191
192
193 int nCJK = 0;
194 sal_uInt16 nMnemonicIndex;
195 sal_Unicode c;
196 xub_StrLen nIndex = 0;
197 if( !bCJK )
198 {
199 // 1) first try the first character of a word
200 do
201 {
202 c = aKey.GetChar( nIndex );
203
204 if ( nCJK != 2 )
205 {
206 if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
207 ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
208 nCJK = 1;
209 else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
210 ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
211 ((c >= 0x0061) && (c <= 0x007A)) || // latin small
212 ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
213 ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
214 nCJK = 2;
215 }
216
217 nMnemonicIndex = ImplGetMnemonicIndex( c );
218 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
219 {
220 if ( maMnemonics[nMnemonicIndex] )
221 {
222 maMnemonics[nMnemonicIndex] = 0;
223 rKey.Insert( MNEMONIC_CHAR, nIndex );
224 bChanged = sal_True;
225 break;
226 }
227 }
228
229 // Search for next word
230 do
231 {
232 nIndex++;
233 c = aKey.GetChar( nIndex );
234 if ( c == ' ' )
235 break;
236 }
237 while ( nIndex < nLen );
238 nIndex++;
239 }
240 while ( nIndex < nLen );
241
242 // 2) search for a unique/uncommon character
243 if ( !bChanged )
244 {
245 sal_uInt16 nBestCount = 0xFFFF;
246 sal_uInt16 nBestMnemonicIndex = 0;
247 xub_StrLen nBestIndex = 0;
248 nIndex = 0;
249 do
250 {
251 c = aKey.GetChar( nIndex );
252 nMnemonicIndex = ImplGetMnemonicIndex( c );
253 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
254 {
255 if ( maMnemonics[nMnemonicIndex] )
256 {
257 if ( maMnemonics[nMnemonicIndex] < nBestCount )
258 {
259 nBestCount = maMnemonics[nMnemonicIndex];
260 nBestIndex = nIndex;
261 nBestMnemonicIndex = nMnemonicIndex;
262 if ( nBestCount == 2 )
263 break;
264 }
265 }
266 }
267
268 nIndex++;
269 }
270 while ( nIndex < nLen );
271
272 if ( nBestCount != 0xFFFF )
273 {
274 maMnemonics[nBestMnemonicIndex] = 0;
275 rKey.Insert( MNEMONIC_CHAR, nBestIndex );
276 bChanged = sal_True;
277 }
278 }
279 }
280 else
281 nCJK = 1;
282
283 // 3) Add English Mnemonic for CJK Text
284 if ( !bChanged && (nCJK == 1) && rKey.Len() )
285 {
286 // Append Ascii Mnemonic
287 for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
288 {
289 nMnemonicIndex = ImplGetMnemonicIndex( c );
290 if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
291 {
292 if ( maMnemonics[nMnemonicIndex] )
293 {
294 maMnemonics[nMnemonicIndex] = 0;
295 UniString aStr( '(' );
296 aStr += MNEMONIC_CHAR;
297 aStr += c;
298 aStr += ')';
299 nIndex = rKey.Len();
300 if( nIndex >= 2 )
301 {
302 static sal_Unicode cGreaterGreater[] = { 0xFF1E, 0xFF1E };
303 if ( rKey.EqualsAscii( ">>", nIndex-2, 2 ) ||
304 rKey.Equals( cGreaterGreater, nIndex-2, 2 ) )
305 nIndex -= 2;
306 }
307 if( nIndex >= 3 )
308 {
309 static sal_Unicode cDotDotDot[] = { 0xFF0E, 0xFF0E, 0xFF0E };
310 if ( rKey.EqualsAscii( "...", nIndex-3, 3 ) ||
311 rKey.Equals( cDotDotDot, nIndex-3, 3 ) )
312 nIndex -= 3;
313 }
314 if( nIndex >= 1)
315 {
316 sal_Unicode cLastChar = rKey.GetChar( nIndex-1 );
317 if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
318 (cLastChar == '.') || (cLastChar == 0xFF0E) ||
319 (cLastChar == '?') || (cLastChar == 0xFF1F) ||
320 (cLastChar == ' ') )
321 nIndex--;
322 }
323 rKey.Insert( aStr, nIndex );
324 bChanged = sal_True;
325 break;
326 }
327 }
328 }
329 }
330
331 // #i87415# Duplicates mnemonics are bad for consistent keyboard accessibility
332 // It's probably better to not have mnemonics for some widgets, than to have ambiguous ones.
333 // if( ! bChanged )
334 // {
335 // /*
336 // * #97809# if all else fails use the first character of a word
337 // * anyway and live with duplicate mnemonics
338 // */
339 // nIndex = 0;
340 // do
341 // {
342 // c = aKey.GetChar( nIndex );
343 //
344 // nMnemonicIndex = ImplGetMnemonicIndex( c );
345 // if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
346 // {
347 // maMnemonics[nMnemonicIndex] = 0;
348 // rKey.Insert( MNEMONIC_CHAR, nIndex );
349 // bChanged = sal_True;
350 // break;
351 // }
352 //
353 // // Search for next word
354 // do
355 // {
356 // nIndex++;
357 // c = aKey.GetChar( nIndex );
358 // if ( c == ' ' )
359 // break;
360 // }
361 // while ( nIndex < nLen );
362 // nIndex++;
363 // }
364 // while ( nIndex < nLen );
365 // }
366
367 return bChanged;
368 }
369
370 // -----------------------------------------------------------------------
371
GetCharClass()372 uno::Reference< i18n::XCharacterClassification > MnemonicGenerator::GetCharClass()
373 {
374 if ( !mxCharClass.is() )
375 mxCharClass = vcl::unohelper::CreateCharacterClassification();
376 return mxCharClass;
377 }
378
379 // -----------------------------------------------------------------------
380
EraseAllMnemonicChars(const String & rStr)381 String MnemonicGenerator::EraseAllMnemonicChars( const String& rStr )
382 {
383 String aStr = rStr;
384 xub_StrLen nLen = aStr.Len();
385 xub_StrLen i = 0;
386
387 while ( i < nLen )
388 {
389 if ( aStr.GetChar( i ) == '~' )
390 {
391 // check for CJK-style mnemonic
392 if( i > 0 && (i+2) < nLen )
393 {
394 sal_Unicode c = aStr.GetChar(i+1);
395 if( aStr.GetChar( i-1 ) == '(' &&
396 aStr.GetChar( i+2 ) == ')' &&
397 c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
398 {
399 aStr.Erase( i-1, 4 );
400 nLen -= 4;
401 i--;
402 continue;
403 }
404 }
405
406 // remove standard mnemonics
407 aStr.Erase( i, 1 );
408 nLen--;
409 }
410 else
411 i++;
412 }
413
414 return aStr;
415 }
416