Line data Source code
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 :
21 : #include <string.h>
22 : #include <vcl/svapp.hxx>
23 : #include <vcl/settings.hxx>
24 : #include <vcl/mnemonic.hxx>
25 :
26 : #include <vcl/unohelp.hxx>
27 : #include <com/sun/star/i18n/XCharacterClassification.hpp>
28 : #include <i18npool/mslangid.hxx>
29 :
30 : using namespace ::com::sun::star;
31 :
32 :
33 : // =======================================================================
34 :
35 0 : MnemonicGenerator::MnemonicGenerator()
36 : {
37 0 : memset( maMnemonics, 1, sizeof( maMnemonics ) );
38 0 : }
39 :
40 : // -----------------------------------------------------------------------
41 :
42 0 : sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c )
43 : {
44 : static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] =
45 : {
46 : MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END,
47 : MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END,
48 : MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END,
49 : MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END
50 : };
51 :
52 0 : sal_uInt16 nMnemonicIndex = 0;
53 0 : for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ )
54 : {
55 0 : if ( (c >= aImplMnemonicRangeTab[i*2]) &&
56 0 : (c <= aImplMnemonicRangeTab[i*2+1]) )
57 0 : return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2];
58 :
59 0 : nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2];
60 : }
61 :
62 0 : return MNEMONIC_INDEX_NOTFOUND;
63 : }
64 :
65 : // -----------------------------------------------------------------------
66 :
67 0 : sal_Unicode MnemonicGenerator::ImplFindMnemonic( const XubString& rKey )
68 : {
69 0 : xub_StrLen nIndex = 0;
70 0 : while ( (nIndex = rKey.Search( MNEMONIC_CHAR, nIndex )) != STRING_NOTFOUND )
71 : {
72 0 : sal_Unicode cMnemonic = rKey.GetChar( nIndex+1 );
73 0 : if ( cMnemonic != MNEMONIC_CHAR )
74 0 : return cMnemonic;
75 0 : nIndex += 2;
76 : }
77 :
78 0 : return 0;
79 : }
80 :
81 : // -----------------------------------------------------------------------
82 :
83 0 : void MnemonicGenerator::RegisterMnemonic( const XubString& rKey )
84 : {
85 0 : const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale();
86 0 : uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
87 :
88 : // Don't crash even when we don't have access to i18n service
89 0 : if ( !xCharClass.is() )
90 0 : return;
91 :
92 0 : XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
93 :
94 : // If we find a Mnemonic, set the flag. In other case count the
95 : // characters, because we need this to set most as possible
96 : // Mnemonics
97 0 : sal_Unicode cMnemonic = ImplFindMnemonic( aKey );
98 0 : if ( cMnemonic )
99 : {
100 0 : sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic );
101 0 : if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
102 0 : maMnemonics[nMnemonicIndex] = 0;
103 : }
104 : else
105 : {
106 0 : xub_StrLen nIndex = 0;
107 0 : xub_StrLen nLen = aKey.Len();
108 0 : while ( nIndex < nLen )
109 : {
110 0 : sal_Unicode c = aKey.GetChar( nIndex );
111 :
112 0 : sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c );
113 0 : if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
114 : {
115 0 : if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) )
116 0 : maMnemonics[nMnemonicIndex]++;
117 : }
118 :
119 0 : nIndex++;
120 : }
121 0 : }
122 : }
123 :
124 : // -----------------------------------------------------------------------
125 :
126 0 : sal_Bool MnemonicGenerator::CreateMnemonic( XubString& rKey )
127 : {
128 0 : if ( !rKey.Len() || ImplFindMnemonic( rKey ) )
129 0 : return sal_False;
130 :
131 0 : const ::com::sun::star::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale();
132 0 : uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass();
133 :
134 : // Don't crash even when we don't have access to i18n service
135 0 : if ( !xCharClass.is() )
136 0 : return sal_False;
137 :
138 0 : XubString aKey = xCharClass->toUpper( rKey, 0, rKey.Len(), rLocale );
139 :
140 0 : sal_Bool bChanged = sal_False;
141 0 : xub_StrLen nLen = aKey.Len();
142 :
143 0 : bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType());
144 :
145 : // #107889# in CJK versions ALL strings (even those that contain latin characters)
146 : // will get mnemonics in the form: xyz (M)
147 : // thus steps 1) and 2) are skipped for CJK locales
148 :
149 : // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars
150 0 : if( bCJK )
151 : {
152 0 : sal_Bool bLatinOnly = sal_True;
153 0 : sal_Bool bMnemonicIndexFound = sal_False;
154 : sal_Unicode c;
155 : xub_StrLen nIndex;
156 :
157 0 : for( nIndex=0; nIndex < nLen; nIndex++ )
158 : {
159 0 : c = aKey.GetChar( nIndex );
160 0 : if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
161 : ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
162 : {
163 0 : bLatinOnly = sal_False;
164 0 : break;
165 : }
166 0 : if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND )
167 0 : bMnemonicIndexFound = sal_True;
168 : }
169 0 : if( bLatinOnly && !bMnemonicIndexFound )
170 0 : return sal_False;
171 : }
172 :
173 :
174 0 : int nCJK = 0;
175 : sal_uInt16 nMnemonicIndex;
176 : sal_Unicode c;
177 0 : xub_StrLen nIndex = 0;
178 0 : if( !bCJK )
179 : {
180 : // 1) first try the first character of a word
181 0 : do
182 : {
183 0 : c = aKey.GetChar( nIndex );
184 :
185 0 : if ( nCJK != 2 )
186 : {
187 0 : if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk
188 : ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms
189 0 : nCJK = 1;
190 0 : else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits
191 : ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals
192 : ((c >= 0x0061) && (c <= 0x007A)) || // latin small
193 : ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs
194 : ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic
195 0 : nCJK = 2;
196 : }
197 :
198 0 : nMnemonicIndex = ImplGetMnemonicIndex( c );
199 0 : if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
200 : {
201 0 : if ( maMnemonics[nMnemonicIndex] )
202 : {
203 0 : maMnemonics[nMnemonicIndex] = 0;
204 0 : rKey.Insert( MNEMONIC_CHAR, nIndex );
205 0 : bChanged = sal_True;
206 0 : break;
207 : }
208 : }
209 :
210 : // Search for next word
211 0 : do
212 : {
213 0 : nIndex++;
214 0 : c = aKey.GetChar( nIndex );
215 0 : if ( c == ' ' )
216 0 : break;
217 : }
218 : while ( nIndex < nLen );
219 0 : nIndex++;
220 : }
221 : while ( nIndex < nLen );
222 :
223 : // 2) search for a unique/uncommon character
224 0 : if ( !bChanged )
225 : {
226 0 : sal_uInt16 nBestCount = 0xFFFF;
227 0 : sal_uInt16 nBestMnemonicIndex = 0;
228 0 : xub_StrLen nBestIndex = 0;
229 0 : nIndex = 0;
230 0 : do
231 : {
232 0 : c = aKey.GetChar( nIndex );
233 0 : nMnemonicIndex = ImplGetMnemonicIndex( c );
234 0 : if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
235 : {
236 0 : if ( maMnemonics[nMnemonicIndex] )
237 : {
238 0 : if ( maMnemonics[nMnemonicIndex] < nBestCount )
239 : {
240 0 : nBestCount = maMnemonics[nMnemonicIndex];
241 0 : nBestIndex = nIndex;
242 0 : nBestMnemonicIndex = nMnemonicIndex;
243 0 : if ( nBestCount == 2 )
244 0 : break;
245 : }
246 : }
247 : }
248 :
249 0 : nIndex++;
250 : }
251 : while ( nIndex < nLen );
252 :
253 0 : if ( nBestCount != 0xFFFF )
254 : {
255 0 : maMnemonics[nBestMnemonicIndex] = 0;
256 0 : rKey.Insert( MNEMONIC_CHAR, nBestIndex );
257 0 : bChanged = sal_True;
258 : }
259 : }
260 : }
261 : else
262 0 : nCJK = 1;
263 :
264 : // 3) Add English Mnemonic for CJK Text
265 0 : if ( !bChanged && (nCJK == 1) && rKey.Len() )
266 : {
267 : // Append Ascii Mnemonic
268 0 : for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ )
269 : {
270 0 : nMnemonicIndex = ImplGetMnemonicIndex( c );
271 0 : if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
272 : {
273 0 : if ( maMnemonics[nMnemonicIndex] )
274 : {
275 0 : maMnemonics[nMnemonicIndex] = 0;
276 : rtl::OUString aStr = rtl::OUStringBuffer().
277 0 : append('(').append(MNEMONIC_CHAR).append(c).
278 0 : append(')').makeStringAndClear();
279 0 : nIndex = rKey.Len();
280 0 : if( nIndex >= 2 )
281 : {
282 : static sal_Unicode cGreaterGreater[] = { 0xFF1E, 0xFF1E };
283 0 : if ( rKey.EqualsAscii( ">>", nIndex-2, 2 ) ||
284 0 : rKey.Equals( cGreaterGreater, nIndex-2, 2 ) )
285 0 : nIndex -= 2;
286 : }
287 0 : if( nIndex >= 3 )
288 : {
289 : static sal_Unicode cDotDotDot[] = { 0xFF0E, 0xFF0E, 0xFF0E };
290 0 : if ( rKey.EqualsAscii( "...", nIndex-3, 3 ) ||
291 0 : rKey.Equals( cDotDotDot, nIndex-3, 3 ) )
292 0 : nIndex -= 3;
293 : }
294 0 : if( nIndex >= 1)
295 : {
296 0 : sal_Unicode cLastChar = rKey.GetChar( nIndex-1 );
297 0 : if ( (cLastChar == ':') || (cLastChar == 0xFF1A) ||
298 : (cLastChar == '.') || (cLastChar == 0xFF0E) ||
299 : (cLastChar == '?') || (cLastChar == 0xFF1F) ||
300 : (cLastChar == ' ') )
301 0 : nIndex--;
302 : }
303 0 : rKey.Insert( aStr, nIndex );
304 0 : bChanged = sal_True;
305 0 : break;
306 : }
307 : }
308 : }
309 : }
310 :
311 : // #i87415# Duplicates mnemonics are bad for consistent keyboard accessibility
312 : // It's probably better to not have mnemonics for some widgets, than to have ambiguous ones.
313 : // if( ! bChanged )
314 : // {
315 : // /*
316 : // * #97809# if all else fails use the first character of a word
317 : // * anyway and live with duplicate mnemonics
318 : // */
319 : // nIndex = 0;
320 : // do
321 : // {
322 : // c = aKey.GetChar( nIndex );
323 : //
324 : // nMnemonicIndex = ImplGetMnemonicIndex( c );
325 : // if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND )
326 : // {
327 : // maMnemonics[nMnemonicIndex] = 0;
328 : // rKey.Insert( MNEMONIC_CHAR, nIndex );
329 : // bChanged = sal_True;
330 : // break;
331 : // }
332 : //
333 : // // Search for next word
334 : // do
335 : // {
336 : // nIndex++;
337 : // c = aKey.GetChar( nIndex );
338 : // if ( c == ' ' )
339 : // break;
340 : // }
341 : // while ( nIndex < nLen );
342 : // nIndex++;
343 : // }
344 : // while ( nIndex < nLen );
345 : // }
346 :
347 0 : return bChanged;
348 : }
349 :
350 : // -----------------------------------------------------------------------
351 :
352 0 : uno::Reference< i18n::XCharacterClassification > MnemonicGenerator::GetCharClass()
353 : {
354 0 : if ( !mxCharClass.is() )
355 0 : mxCharClass = vcl::unohelper::CreateCharacterClassification();
356 0 : return mxCharClass;
357 : }
358 :
359 : // -----------------------------------------------------------------------
360 :
361 2404 : String MnemonicGenerator::EraseAllMnemonicChars( const String& rStr )
362 : {
363 2404 : String aStr = rStr;
364 2404 : xub_StrLen nLen = aStr.Len();
365 2404 : xub_StrLen i = 0;
366 :
367 51341 : while ( i < nLen )
368 : {
369 46533 : if ( aStr.GetChar( i ) == '~' )
370 : {
371 : // check for CJK-style mnemonic
372 0 : if( i > 0 && (i+2) < nLen )
373 : {
374 0 : sal_Unicode c = aStr.GetChar(i+1);
375 0 : if( aStr.GetChar( i-1 ) == '(' &&
376 0 : aStr.GetChar( i+2 ) == ')' &&
377 : c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END )
378 : {
379 0 : aStr.Erase( i-1, 4 );
380 0 : nLen -= 4;
381 0 : i--;
382 0 : continue;
383 : }
384 : }
385 :
386 : // remove standard mnemonics
387 0 : aStr.Erase( i, 1 );
388 0 : nLen--;
389 : }
390 : else
391 46533 : i++;
392 : }
393 :
394 2404 : return aStr;
395 : }
396 :
397 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|