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 40 MnemonicGenerator::MnemonicGenerator() 41 { 42 memset( maMnemonics, 1, sizeof( maMnemonics ) ); 43 } 44 45 // ----------------------------------------------------------------------- 46 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 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 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 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 372 uno::Reference< i18n::XCharacterClassification > MnemonicGenerator::GetCharClass() 373 { 374 if ( !mxCharClass.is() ) 375 mxCharClass = vcl::unohelper::CreateCharacterClassification(); 376 return mxCharClass; 377 } 378 379 // ----------------------------------------------------------------------- 380 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