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