blob: 96eacc821385db72adc18e5aa89e8f4621b038d5 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include <string.h>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/unohelp.hxx>
#include <com/sun/star/i18n/XCharacterClassification.hpp>
using namespace ::com::sun::star;
// =======================================================================
MnemonicGenerator::MnemonicGenerator()
{
memset( maMnemonics, 1, sizeof( maMnemonics ) );
}
// -----------------------------------------------------------------------
sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
{
static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
{
MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
};
sal_uInt16 nMnemonicIndex = 0;
for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
{
if ( (c >= aImplMnemonicRangeTab[i*2]) &&
(c <= aImplMnemonicRangeTab[i*2+1]) )
return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
}
return MNEMONIC_INDEX_NOTFOUND;
}
// -----------------------------------------------------------------------
sal_Unicode MnemonicGenerator::ImplFindMnemonic( const XubString& rKey )
{
xub_StrLen nIndex = 0;
while ( (nIndex = rKey.Search( MNEMONIC_CHAR, nIndex )) != STRING_NOTFOUND )
{
sal_Unicode cMnemonic = rKey.GetChar( nIndex+1 );
if ( cMnemonic != MNEMONIC_CHAR )
return cMnemonic;
nIndex += 2;
}
return 0;
}
// -----------------------------------------------------------------------
void MnemonicGenerator::RegisterMnemonic( const XubString& rKey )
{
const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILocale();
uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
// Don't crash even when we don't have access to i18n service
if ( !xCharClass.is() )
return;
XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
// If we find a Mnemonic, set the flag. In other case count the
// characters, because we need this to set most as possible
// Mnemonics
sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
if ( cMnemonic )
{
sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
maMnemonics[nMnemonicIndex] = 0;
}
else
{
xub_StrLen nIndex = 0;
xub_StrLen nLen = aKey.Len();
while ( nIndex < nLen )
{
sal_Unicode c = aKey.GetChar( nIndex );
sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
{
if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
maMnemonics[nMnemonicIndex]++;
}
nIndex++;
}
}
}
// -----------------------------------------------------------------------
sal_Bool MnemonicGenerator::CreateMnemonic( XubString& rKey )
{
if ( !rKey.Len() || ImplFindMnemonic( rKey ) )
return sal_False;
const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILocale();
uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
// Don't crash even when we don't have access to i18n service
if ( !xCharClass.is() )
return sal_False;
XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
sal_Bool bChanged = sal_False;
xub_StrLen nLen = aKey.Len();
sal_Bool bCJK = sal_False;
switch( Application::GetSettings().GetUILanguage() )
{
case LANGUAGE_JAPANESE:
case LANGUAGE_CHINESE_TRADITIONAL:
case LANGUAGE_CHINESE_SIMPLIFIED:
case LANGUAGE_CHINESE_HONGKONG:
case LANGUAGE_CHINESE_SINGAPORE:
case LANGUAGE_CHINESE_MACAU:
case LANGUAGE_KOREAN:
case LANGUAGE_KOREAN_JOHAB:
bCJK = sal_True;
break;
default:
break;
}
// #107889# in CJK versions ALL strings (even those that contain latin characters)
// will get mnemonics in the form: xyz (M)
// thus steps 1) and 2) are skipped for CJK locales
// #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
if( bCJK )
{
sal_Bool bLatinOnly = sal_True;
sal_Bool bMnemonicIndexFound = sal_False;
sal_Unicode c;
xub_StrLen nIndex;
for( nIndex=0; nIndex < nLen; nIndex++ )
{
c = aKey.GetChar( nIndex );
if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
{
bLatinOnly = sal_False;
break;
}
if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
bMnemonicIndexFound = sal_True;
}
if( bLatinOnly && !bMnemonicIndexFound )
return sal_False;
}
int nCJK = 0;
sal_uInt16 nMnemonicIndex;
sal_Unicode c;
xub_StrLen nIndex = 0;
if( !bCJK )
{
// 1) first try the first character of a word
do
{
c = aKey.GetChar( nIndex );
if ( nCJK != 2 )
{
if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
nCJK = 1;
else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
((c >= 0x0061) && (c <= 0x007A)) || // latin small
((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
nCJK = 2;
}
nMnemonicIndex = ImplGetMnemonicIndex( c );
if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
{
if ( maMnemonics[nMnemonicIndex] )
{
maMnemonics[nMnemonicIndex] = 0;
rKey.Insert( MNEMONIC_CHAR, nIndex );
bChanged = sal_True;
break;
}
}
// Search for next word
do
{
nIndex++;
c = aKey.GetChar( nIndex );
if ( c == ' ' )
break;
}
while ( nIndex < nLen );
nIndex++;
}
while ( nIndex < nLen );
// 2) search for a unique/uncommon character
if ( !bChanged )
{
sal_uInt16 nBestCount = 0xFFFF;
sal_uInt16 nBestMnemonicIndex = 0;
xub_StrLen nBestIndex = 0;
nIndex = 0;
do
{
c = aKey.GetChar( nIndex );
nMnemonicIndex = ImplGetMnemonicIndex( c );
if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
{
if ( maMnemonics[nMnemonicIndex] )
{
if ( maMnemonics[nMnemonicIndex] < nBestCount )
{
nBestCount = maMnemonics[nMnemonicIndex];
nBestIndex = nIndex;
nBestMnemonicIndex = nMnemonicIndex;
if ( nBestCount == 2 )
break;
}
}
}
nIndex++;
}
while ( nIndex < nLen );
if ( nBestCount != 0xFFFF )
{
maMnemonics[nBestMnemonicIndex] = 0;
rKey.Insert( MNEMONIC_CHAR, nBestIndex );
bChanged = sal_True;
}
}
}
else
nCJK = 1;
// 3) Add English Mnemonic for CJK Text
if ( !bChanged && (nCJK == 1) && rKey.Len() )
{
// Append Ascii Mnemonic
for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
{
nMnemonicIndex = ImplGetMnemonicIndex( c );
if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
{
if ( maMnemonics[nMnemonicIndex] )
{
maMnemonics[nMnemonicIndex] = 0;
UniString aStr( '(' );
aStr += MNEMONIC_CHAR;
aStr += c;
aStr += ')';
nIndex = rKey.Len();
if( nIndex >= 2 )
{
static sal_Unicode cGreaterGreater[] = { 0xFF1E, 0xFF1E };
if ( rKey.EqualsAscii( ">>", nIndex-2, 2 ) ||
rKey.Equals( cGreaterGreater, nIndex-2, 2 ) )
nIndex -= 2;
}
if( nIndex >= 3 )
{
static sal_Unicode cDotDotDot[] = { 0xFF0E, 0xFF0E, 0xFF0E };
if ( rKey.EqualsAscii( "...", nIndex-3, 3 ) ||
rKey.Equals( cDotDotDot, nIndex-3, 3 ) )
nIndex -= 3;
}
if( nIndex >= 1)
{
sal_Unicode cLastChar = rKey.GetChar( nIndex-1 );
if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
(cLastChar == '.') || (cLastChar == 0xFF0E) ||
(cLastChar == '?') || (cLastChar == 0xFF1F) ||
(cLastChar == ' ') )
nIndex--;
}
rKey.Insert( aStr, nIndex );
bChanged = sal_True;
break;
}
}
}
}
// #i87415# Duplicates mnemonics are bad for consistent keyboard accessibility
// It's probably better to not have mnemonics for some widgets, than to have ambiguous ones.
// if( ! bChanged )
// {
// /*
// * #97809# if all else fails use the first character of a word
// * anyway and live with duplicate mnemonics
// */
// nIndex = 0;
// do
// {
// c = aKey.GetChar( nIndex );
//
// nMnemonicIndex = ImplGetMnemonicIndex( c );
// if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
// {
// maMnemonics[nMnemonicIndex] = 0;
// rKey.Insert( MNEMONIC_CHAR, nIndex );
// bChanged = sal_True;
// break;
// }
//
// // Search for next word
// do
// {
// nIndex++;
// c = aKey.GetChar( nIndex );
// if ( c == ' ' )
// break;
// }
// while ( nIndex < nLen );
// nIndex++;
// }
// while ( nIndex < nLen );
// }
return bChanged;
}
// -----------------------------------------------------------------------
uno::Reference< i18n::XCharacterClassification > MnemonicGenerator::GetCharClass()
{
if ( !mxCharClass.is() )
mxCharClass = vcl::unohelper::CreateCharacterClassification();
return mxCharClass;
}
// -----------------------------------------------------------------------
String MnemonicGenerator::EraseAllMnemonicChars( const String& rStr )
{
String aStr = rStr;
xub_StrLen nLen = aStr.Len();
xub_StrLen i = 0;
while ( i < nLen )
{
if ( aStr.GetChar( i ) == '~' )
{
// check for CJK-style mnemonic
if( i > 0 && (i+2) < nLen )
{
sal_Unicode c = aStr.GetChar(i+1);
if( aStr.GetChar( i-1 ) == '(' &&
aStr.GetChar( i+2 ) == ')' &&
c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
{
aStr.Erase( i-1, 4 );
nLen -= 4;
i--;
continue;
}
}
// remove standard mnemonics
aStr.Erase( i, 1 );
nLen--;
}
else
i++;
}
return aStr;
}