LibreOffice Module vcl (master)  1
mnemonic.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <string.h>
21 #include <vcl/svapp.hxx>
22 #include <vcl/settings.hxx>
23 #include <vcl/mnemonic.hxx>
24 
25 #include <vcl/unohelp.hxx>
26 #include <com/sun/star/i18n/XCharacterClassification.hpp>
28 #include <i18nlangtag/mslangid.hxx>
29 #include <rtl/character.hxx>
30 
31 using namespace ::com::sun::star;
32 
34  : m_cMnemonic(cMnemonic)
35 {
36  memset( maMnemonics, 1, sizeof( maMnemonics ) );
37 }
38 
40 {
41  static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
42  {
47  };
48 
49  sal_uInt16 nMnemonicIndex = 0;
50  for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
51  {
52  if ( (c >= aImplMnemonicRangeTab[i*2]) &&
53  (c <= aImplMnemonicRangeTab[i*2+1]) )
54  return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
55 
56  nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
57  }
58 
60 }
61 
63 {
64  sal_Int32 nIndex = 0;
65  while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 )
66  {
67  sal_Unicode cMnemonic = rKey[ nIndex+1 ];
68  if ( cMnemonic != m_cMnemonic )
69  return cMnemonic;
70  nIndex += 2;
71  }
72 
73  return 0;
74 }
75 
76 void MnemonicGenerator::RegisterMnemonic( const OUString& rKey )
77 {
78  uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
79 
80  // Don't crash even when we don't have access to i18n service
81  if ( !xCharClass.is() )
82  return;
83 
84  OUString aKey = xCharClass->toLower(rKey, 0, rKey.getLength(), css::lang::Locale());
85 
86  // If we find a Mnemonic, set the flag. In other case count the
87  // characters, because we need this to set most as possible
88  // Mnemonics
89  sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
90  if ( cMnemonic )
91  {
92  sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
93  if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
94  maMnemonics[nMnemonicIndex] = 0;
95  }
96  else
97  {
98  sal_Int32 nIndex = 0;
99  sal_Int32 nLen = aKey.getLength();
100  while ( nIndex < nLen )
101  {
102  sal_Unicode c = aKey[ nIndex ];
103 
104  sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
105  if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
106  {
107  if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
108  maMnemonics[nMnemonicIndex]++;
109  }
110 
111  nIndex++;
112  }
113  }
114 }
115 
116 OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey )
117 {
118  if ( _rKey.isEmpty() || ImplFindMnemonic( _rKey ) )
119  return _rKey;
120 
121  uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
122 
123  // Don't crash even when we don't have access to i18n service
124  if ( !xCharClass.is() )
125  return _rKey;
126 
127  OUString aKey = xCharClass->toLower(_rKey, 0, _rKey.getLength(), css::lang::Locale());
128 
129  bool bChanged = false;
130  sal_Int32 nLen = aKey.getLength();
131 
132  bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
133 
134  // #107889# in CJK versions ALL strings (even those that contain latin characters)
135  // will get mnemonics in the form: xyz (M)
136  // thus steps 1) and 2) are skipped for CJK locales
137 
138  // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
139  if( bCJK )
140  {
141  bool bLatinOnly = true;
142  bool bMnemonicIndexFound = false;
143  sal_Unicode c;
144  sal_Int32 nIndex;
145 
146  for( nIndex=0; nIndex < nLen; nIndex++ )
147  {
148  c = aKey[ nIndex ];
149  if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
150  ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
151  {
152  bLatinOnly = false;
153  break;
154  }
156  bMnemonicIndexFound = true;
157  }
158  if( bLatinOnly && !bMnemonicIndexFound )
159  return _rKey;
160  }
161 
162  OUString rKey(_rKey);
163  int nCJK = 0;
164  sal_uInt16 nMnemonicIndex;
165  sal_Unicode c;
166  sal_Int32 nIndex = 0;
167  if( !bCJK )
168  {
169  // 1) first try the first character of a word
170  do
171  {
172  c = aKey[ nIndex ];
173 
174  if ( nCJK != 2 )
175  {
176  if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
177  ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
178  nCJK = 1;
179  else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
180  ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
181  ((c >= 0x0061) && (c <= 0x007A)) || // latin small
182  ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
183  ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
184  nCJK = 2;
185  }
186 
187  nMnemonicIndex = ImplGetMnemonicIndex( c );
188  if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
189  {
190  if ( maMnemonics[nMnemonicIndex] )
191  {
192  maMnemonics[nMnemonicIndex] = 0;
193  rKey = rKey.replaceAt( nIndex, 0, OUString(m_cMnemonic) );
194  bChanged = true;
195  break;
196  }
197  }
198 
199  // Search for next word
200  nIndex++;
201  while ( nIndex < nLen )
202  {
203  c = aKey[ nIndex ];
204  if ( c == ' ' )
205  break;
206  nIndex++;
207  }
208  nIndex++;
209  }
210  while ( nIndex < nLen );
211 
212  // 2) search for a unique/uncommon character
213  if ( !bChanged )
214  {
215  sal_uInt16 nBestCount = 0xFFFF;
216  sal_uInt16 nBestMnemonicIndex = 0;
217  sal_Int32 nBestIndex = 0;
218  nIndex = 0;
219  do
220  {
221  c = aKey[ nIndex ];
222  nMnemonicIndex = ImplGetMnemonicIndex( c );
223  if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
224  {
225  if ( maMnemonics[nMnemonicIndex] )
226  {
227  if ( maMnemonics[nMnemonicIndex] < nBestCount )
228  {
229  nBestCount = maMnemonics[nMnemonicIndex];
230  nBestIndex = nIndex;
231  nBestMnemonicIndex = nMnemonicIndex;
232  if ( nBestCount == 2 )
233  break;
234  }
235  }
236  }
237 
238  nIndex++;
239  }
240  while ( nIndex < nLen );
241 
242  if ( nBestCount != 0xFFFF )
243  {
244  maMnemonics[nBestMnemonicIndex] = 0;
245  rKey = rKey.replaceAt( nBestIndex, 0, OUString(m_cMnemonic) );
246  bChanged = true;
247  }
248  }
249  }
250  else
251  nCJK = 1;
252 
253  // 3) Add English Mnemonic for CJK Text
254  if ( !bChanged && (nCJK == 1) && !rKey.isEmpty() )
255  {
256  // Append Ascii Mnemonic
257  for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
258  {
259  nMnemonicIndex = ImplGetMnemonicIndex(c);
260  if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
261  {
262  if ( maMnemonics[nMnemonicIndex] )
263  {
264  maMnemonics[nMnemonicIndex] = 0;
265  OUString aStr = OUStringLiteral("(") + OUStringChar(m_cMnemonic) +
266  OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(c))) +
267  ")";
268  nIndex = rKey.getLength();
269  if( nIndex >= 2 )
270  {
271  if ( ( rKey[nIndex-2] == '>' && rKey[nIndex-1] == '>' ) ||
272  ( rKey[nIndex-2] == 0xFF1E && rKey[nIndex-1] == 0xFF1E ) )
273  nIndex -= 2;
274  }
275  if( nIndex >= 3 )
276  {
277  if ( ( rKey[nIndex-3] == '.' && rKey[nIndex-2] == '.' && rKey[nIndex-1] == '.' ) ||
278  ( rKey[nIndex-3] == 0xFF0E && rKey[nIndex-2] == 0xFF0E && rKey[nIndex-1] == 0xFF0E ) )
279  nIndex -= 3;
280  }
281  if( nIndex >= 1)
282  {
283  sal_Unicode cLastChar = rKey[ nIndex-1 ];
284  if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
285  (cLastChar == '.') || (cLastChar == 0xFF0E) ||
286  (cLastChar == '?') || (cLastChar == 0xFF1F) ||
287  (cLastChar == ' ') )
288  nIndex--;
289  }
290  rKey = rKey.replaceAt( nIndex, 0, aStr );
291  break;
292  }
293  }
294  }
295  }
296 
297  return rKey;
298 }
299 
300 uno::Reference< i18n::XCharacterClassification > const & MnemonicGenerator::GetCharClass()
301 {
302  if ( !mxCharClass.is() )
304  return mxCharClass;
305 }
306 
307 OUString MnemonicGenerator::EraseAllMnemonicChars( const OUString& rStr )
308 {
309  OUString aStr = rStr;
310  sal_Int32 nLen = aStr.getLength();
311  sal_Int32 i = 0;
312 
313  while ( i < nLen )
314  {
315  if ( aStr[ i ] == '~' )
316  {
317  // check for CJK-style mnemonic
318  if( i > 0 && (i+2) < nLen )
319  {
320  sal_Unicode c = sal_Unicode(rtl::toAsciiUpperCase(aStr[i+1]));
321  if( aStr[ i-1 ] == '(' &&
322  aStr[ i+2 ] == ')' &&
324  {
325  aStr = aStr.replaceAt( i-1, 4, "" );
326  nLen -= 4;
327  i--;
328  continue;
329  }
330  }
331 
332  // remove standard mnemonics
333  aStr = aStr.replaceAt( i, 1, "" );
334  nLen--;
335  }
336  else
337  i++;
338  }
339 
340  return aStr;
341 }
342 
343 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
#define MNEMONIC_RANGE_4_START
Definition: mnemonic.hxx:40
MnemonicGenerator(sal_Unicode cMnemonic=MNEMONIC_CHAR)
Definition: mnemonic.cxx:33
#define MNEMONIC_RANGE_3_START
Definition: mnemonic.hxx:37
static const AllSettings & GetSettings()
Gets the application's settings.
Definition: svapp.cxx:704
OUString CreateMnemonic(const OUString &rKey)
Definition: mnemonic.cxx:116
sal_Unicode m_cMnemonic
Definition: mnemonic.hxx:54
sal_uInt16 sal_Unicode
css::uno::Reference< css::i18n::XCharacterClassification > mxCharClass
Definition: mnemonic.hxx:57
static bool isCJK(LanguageType nLang)
css::uno::Reference< css::i18n::XCharacterClassification > const & GetCharClass()
Definition: mnemonic.cxx:300
#define MNEMONIC_RANGE_2_START
Definition: mnemonic.hxx:34
int i
#define MNEMONIC_RANGES
Definition: mnemonic.hxx:42
sal_uInt8 maMnemonics[MAX_MNEMONICS]
Definition: mnemonic.hxx:56
#define MNEMONIC_RANGE_1_END
Definition: mnemonic.hxx:32
static SAL_DLLPRIVATE sal_uInt16 ImplGetMnemonicIndex(sal_Unicode c)
Definition: mnemonic.cxx:39
VCL_DLLPUBLIC css::uno::Reference< css::i18n::XCharacterClassification > CreateCharacterClassification()
Definition: unohelp.cxx:43
SAL_DLLPRIVATE sal_Unicode ImplFindMnemonic(const OUString &rKey)
Definition: mnemonic.cxx:62
#define MNEMONIC_RANGE_1_START
Definition: mnemonic.hxx:31
static OUString EraseAllMnemonicChars(const OUString &rStr)
Definition: mnemonic.cxx:307
#define MNEMONIC_RANGE_2_END
Definition: mnemonic.hxx:35
#define MNEMONIC_INDEX_NOTFOUND
Definition: mnemonic.hxx:49
void RegisterMnemonic(const OUString &rKey)
Definition: mnemonic.cxx:76
#define MNEMONIC_RANGE_4_END
Definition: mnemonic.hxx:41
aStr
#define MNEMONIC_RANGE_3_END
Definition: mnemonic.hxx:38