xref: /aoo41x/main/vcl/source/window/mnemonic.cxx (revision cdf0e10c)
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