1 /* This file is part of the KDE project
2 Copyright (C) 2000 Keunwoo Lee <klee@cs.washington.edu>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
18 // -*- mode: c++; c-basic-offset: 2 -*-
25 #include <qstringlist.h>
27 #include <kdelibs_export.h>
30 * Provides functions that, given a collection of QStrings, will
31 * automatically and intelligently assign menu accelerators to the
32 * QStrings in the collection.
34 * NOTE: When this file speaks of "accelerators", we really mean
35 * accelerators as defined by the KDE User Interface Guidelines. We
36 * do NOT mean "shortcuts", which are what's handled by most other KDE
37 * libraries with "accel" in the name.
39 * In the Qt library, the mechanism for adding a keyboard accelerator
40 * to a menu item is to insert an '&' before the letter. Since we
41 * usually don't want to disturb the original collection, the idiom in
42 * these functions is to populate a "target" QStringList parameter
43 * with the input collectin's QStrings, plus possibly some added '&'
46 * That is the mechanism. Here is the policy, in order of decreasing
47 * importance (it may seem like these are implementation details, but
48 * IMHO the policy is an important part of the interface):
50 * 1. If the string already contains an '&' character, skip this
51 * string, because we consider such strings to be "user-specified"
54 * 2. No accelerator may clash with a previously defined accelerator,
55 * including any legal (alphanumeric) user-specified accelerator
56 * anywhere in the collection
58 * 3. Prefer alphanumerics at the start of the string.
60 * 4. Otherwise, prefer alphanumerics at the start of a word.
62 * 5. Otherwise, choose any alphanumeric character not already
63 * taken. If no such character is available, give up & skip this
66 * A typical use of these functions would be to automatically assign
67 * accelerators to a dynamically populated popup menu. For example,
68 * the core code was written to automatically set accelerators for the
69 * "Load View Profile" popup menu for Konqueror. We quickly realized
70 * that it would be useful to make this facility more generally
71 * available, so I abstracted it out into a set of templates.
75 * + Add sugar functions for more collections.
77 * + Add more Deref classes so that we can access a wider variety of
86 * Static dereference class, for use as a template parameter.
92 static QString
deref(Iter i
) { return *i
; }
96 * Static dereference class that calls the key() method on its
97 * target; for use as a template parameter.
103 static QString
deref(Iter i
) { return i
.key(); }
107 * Helper to determine if the given offset in the string could be a
108 * legal alphanumeric accelerator.
110 * @param str base string
111 * @param index offset to check
114 isLegalAccelerator(const QString
& str
, uint index
)
116 return index
< str
.length()
117 && str
[index
].isLetterOrNumber();
121 * Loads all legal predefined accelerators in the (implicitly
122 * specified) collection into the given QMap.
124 * @param begin start iterator
125 * @param end (last+1) iterator
126 * @param keys map to store output
128 template <class Iter
, class Deref
>
130 loadPredefined(Iter begin
, Iter end
, QMap
<QChar
,bool>& keys
)
132 for (Iter i
= begin
; i
!= end
; ++i
) {
133 QString item
= Deref::deref(i
);
134 int user_ampersand
= item
.find(QChar('&'));
135 if( user_ampersand
>= 0 ) {
136 // Sanity check. Note that we don't try to find an
137 // accelerator if the user shoots him/herself in the foot
138 // by adding a bad '&'.
139 if( isLegalAccelerator(item
, user_ampersand
+1) ) {
140 keys
.insert(item
[user_ampersand
+1], true);
147 // ///////////////////////////////////////////////////////////////////
148 // MAIN USER FUNCTIONS
152 * Main, maximally flexible template function that assigns
153 * accelerators to the elements of a collection of QStrings. Clients
154 * will seldom use this directly, as it's usually easier to use one of
155 * the wrapper functions that simply takes a collection (see below).
157 * The Deref template parameter is a class containing a static
158 * dereferencing function, modeled after the comparison class C in
161 * @param begin (you know)
162 * @param end (you know)
163 * @param target collection to store generated strings
165 template <class Iter
, class Iter_Deref
>
167 generate(Iter begin
, Iter end
, QStringList
& target
)
169 // Will keep track of used accelerator chars
170 QMap
<QChar
,bool> used_accels
;
172 // Prepass to detect manually user-coded accelerators
173 loadPredefined
<Iter
,Iter_Deref
>(begin
, end
, used_accels
);
176 for (Iter i
= begin
; i
!= end
; ++i
) {
177 QString item
= Iter_Deref::deref(i
);
179 // Attempt to find a good accelerator, but only if the user
180 // has not manually hardcoded one.
181 int user_ampersand
= item
.find(QChar('&'));
182 if( user_ampersand
< 0 || item
[user_ampersand
+1] == '&') {
187 // Check word-starting letters first.
188 for( j
=0; j
< item
.length(); ++j
) {
189 if( isLegalAccelerator(item
, j
)
190 && !used_accels
.contains(item
[j
])
191 && (0 == j
|| j
> 0 && item
[j
-1].isSpace()) ) {
199 // No word-starting letter; search for any letter.
200 for( j
=0; j
< item
.length(); ++j
) {
201 if( isLegalAccelerator(item
, j
)
202 && !used_accels
.contains(item
[j
]) ) {
211 // Both upper and lower case marked as used
212 used_accels
.insert(item
[j
].upper(),true);
213 used_accels
.insert(item
[j
].lower(),true);
214 item
.insert(j
,QChar('&'));
218 target
.append( item
);
223 * Another convenience function; looks up the key instead of
224 * dereferencing directly for the given iterator.
230 template <class Iter
>
232 generateFromKeys(Iter begin
, Iter end
, QStringList
& target
)
234 generate
< Iter
, Deref_Key
<Iter
> >(begin
, end
, target
);
239 * Convenience function; generates accelerators for all the items in
242 * @param source Strings for which to generate accelerators
243 * @param target Output for accelerator-added strings */
245 generate(const QStringList
& source
, QStringList
& target
)
247 generate
<QStringList::ConstIterator
, Deref
<QStringList::ConstIterator
> >(source
.begin(), source
.end(), target
);
251 * Convenience function; generates accelerators for all the values in
254 * @param source Map with input strings as VALUES.
255 * @param target Output for accelerator-added strings */
258 generateFromValues(const QMap
<Key
,QString
>& source
, QStringList
& target
)
260 generate
<QMapConstIterator
<Key
,QString
>, Deref_Key
<QMapConstIterator
<Key
,QString
> > >(source
.begin(), source
.end(), target
);
264 * Convenience function; generates an accelerator mapping from all the
265 * keys in a QMap<QString,T>
267 * @param source Map with input strings as KEYS.
268 * @param target Output for accelerator-added strings */
269 template <class Data
>
271 generateFromKeys(const QMap
<QString
,Data
>& source
, QStringList
& target
)
273 generateFromKeys(source
.begin(), source
.end(), target
);
277 } // end namespace KAccelGen