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