1 | /* This file is part of the KDE project |
2 | Copyright (C) 2000 Keunwoo Lee <klee@cs.washington.edu> |
3 | |
4 | This program is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Library General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2 of the License, or (at your option) any later version. |
8 | |
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 Library General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to |
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #ifndef KACCELGEN_H |
21 | #define KACCELGEN_H |
22 | |
23 | #include <kdeui_export.h> |
24 | |
25 | #include <QtCore/QMap> |
26 | #include <QtCore/QString> |
27 | #include <QtCore/QStringList> |
28 | |
29 | /** |
30 | * Provides functions that, given a collection of QStrings, will |
31 | * automatically and intelligently assign menu accelerators to the |
32 | * QStrings in the collection. |
33 | * |
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. |
38 | * |
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 '&' |
44 | * characters. |
45 | * |
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): |
49 | * |
50 | * 1. If the string already contains an '&' character, skip this |
51 | * string, because we consider such strings to be "user-specified" |
52 | * accelerators. |
53 | * |
54 | * 2. No accelerator may clash with a previously defined accelerator, |
55 | * including any legal (alphanumeric) user-specified accelerator |
56 | * anywhere in the collection |
57 | * |
58 | * 3. Prefer alphanumerics at the start of the string. |
59 | * |
60 | * 4. Otherwise, prefer alphanumerics at the start of a word. |
61 | * |
62 | * 5. Otherwise, choose any alphanumeric character not already |
63 | * taken. If no such character is available, give up & skip this |
64 | * string. |
65 | * |
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. |
72 | * |
73 | * TODO: |
74 | * |
75 | * + Add sugar functions for more collections. |
76 | * |
77 | * + Add more Deref classes so that we can access a wider variety of |
78 | * collections. |
79 | * */ |
80 | namespace KAccelGen |
81 | { |
82 | |
83 | // HELPERS |
84 | |
85 | /** |
86 | * Static dereference class, for use as a template parameter. |
87 | */ |
88 | template <class Iter> |
89 | class Deref |
90 | { |
91 | public: |
92 | static QString deref(Iter i) { return *i; } |
93 | }; |
94 | |
95 | /** |
96 | * Static dereference class that calls the key() method on its |
97 | * target; for use as a template parameter. |
98 | */ |
99 | template <class Iter> |
100 | class Deref_Key |
101 | { |
102 | public: |
103 | static QString deref(Iter i) { return i.key(); } |
104 | }; |
105 | |
106 | /** |
107 | * Helper to determine if the given offset in the string could be a |
108 | * legal alphanumeric accelerator. |
109 | * |
110 | * @param str base string |
111 | * @param index offset to check |
112 | */ |
113 | inline bool |
114 | isLegalAccelerator(const QString& str, int index) |
115 | { |
116 | return index >= 0 && index < str.length() |
117 | && str[index].isLetterOrNumber(); |
118 | } |
119 | |
120 | /** |
121 | * Loads all legal predefined accelerators in the (implicitly |
122 | * specified) collection into the given QMap. |
123 | * |
124 | * @param begin start iterator |
125 | * @param end (last+1) iterator |
126 | * @param keys map to store output |
127 | */ |
128 | template <class Iter, class Deref> |
129 | inline void |
130 | loadPredefined(Iter begin, Iter end, QMap<QChar,bool>& keys) |
131 | { |
132 | for (Iter i = begin; i != end; ++i) { |
133 | QString item = Deref::deref(i); |
134 | int user_ampersand = item.indexOf(QLatin1Char('&')); |
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); |
141 | } |
142 | } |
143 | } |
144 | } |
145 | |
146 | |
147 | // /////////////////////////////////////////////////////////////////// |
148 | // MAIN USER FUNCTIONS |
149 | |
150 | |
151 | /** |
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). |
156 | * |
157 | * The Deref template parameter is a class containing a static |
158 | * dereferencing function, modeled after the comparison class C in |
159 | * Stroustrup 13.4. |
160 | * |
161 | * @param begin (you know) |
162 | * @param end (you know) |
163 | * @param target collection to store generated strings |
164 | */ |
165 | template <class Iter, class Iter_Deref > |
166 | void |
167 | generate(Iter begin, Iter end, QStringList& target) |
168 | { |
169 | // Will keep track of used accelerator chars |
170 | QMap<QChar,bool> used_accels; |
171 | |
172 | // Prepass to detect manually user-coded accelerators |
173 | loadPredefined<Iter,Iter_Deref>(begin, end, used_accels); |
174 | |
175 | // Main pass |
176 | for (Iter i = begin; i != end; ++i) { |
177 | QString item = Iter_Deref::deref(i); |
178 | |
179 | // Attempt to find a good accelerator, but only if the user |
180 | // has not manually hardcoded one. |
181 | int user_ampersand = item.indexOf(QLatin1Char('&')); |
182 | if( user_ampersand < 0 || item[user_ampersand+1] == QLatin1Char('&')) { |
183 | bool found = false; |
184 | int j; |
185 | |
186 | // Check word-starting letters first. |
187 | for( j=0; j < item.length(); ++j ) { |
188 | if( isLegalAccelerator(item, j) |
189 | && !used_accels.contains(item[j]) |
190 | && (0 == j || (j > 0 && item[j-1].isSpace())) ) { |
191 | found = true; |
192 | break; |
193 | } |
194 | } |
195 | |
196 | if( !found ) { |
197 | // No word-starting letter; search for any letter. |
198 | for( j=0; j < item.length(); ++j ) { |
199 | if( isLegalAccelerator(item, j) |
200 | && !used_accels.contains(item[j]) ) { |
201 | found = true; |
202 | break; |
203 | } |
204 | } |
205 | } |
206 | |
207 | if( found ) { |
208 | // Both upper and lower case marked as used |
209 | used_accels.insert(item[j].toUpper(),true); |
210 | used_accels.insert(item[j].toLower(),true); |
211 | item.insert(j,QLatin1Char('&')); |
212 | } |
213 | } |
214 | |
215 | target.append( item ); |
216 | } |
217 | } |
218 | |
219 | /** |
220 | * Another convenience function; looks up the key instead of |
221 | * dereferencing directly for the given iterator. |
222 | * |
223 | * @param begin |
224 | * @param end |
225 | * @param target |
226 | */ |
227 | template <class Iter> |
228 | inline void |
229 | generateFromKeys(Iter begin, Iter end, QStringList& target) |
230 | { |
231 | generate< Iter, Deref_Key<Iter> >(begin, end, target); |
232 | } |
233 | |
234 | |
235 | /** |
236 | * Convenience function; generates accelerators for all the items in |
237 | * a QStringList. |
238 | * |
239 | * @param source Strings for which to generate accelerators |
240 | * @param target Output for accelerator-added strings */ |
241 | inline void |
242 | generate(const QStringList& source, QStringList& target) |
243 | { |
244 | generate<QStringList::ConstIterator, Deref<QStringList::ConstIterator> >(source.begin(), source.end(), target); |
245 | } |
246 | |
247 | /** |
248 | * Convenience function; generates accelerators for all the values in |
249 | * a QMap<T,QString>. |
250 | * |
251 | * @param source Map with input strings as VALUES. |
252 | * @param target Output for accelerator-added strings */ |
253 | template <class Key> |
254 | inline void |
255 | generateFromValues(const QMap<Key,QString>& source, QStringList& target) |
256 | { |
257 | generate<typename QMap<Key,QString>::ConstIterator, Deref_Key<typename QMap<Key,QString>::ConstIterator> >(source.begin(), source.end(), target); |
258 | } |
259 | |
260 | /** |
261 | * Convenience function; generates an accelerator mapping from all the |
262 | * keys in a QMap<QString,T> |
263 | * |
264 | * @param source Map with input strings as KEYS. |
265 | * @param target Output for accelerator-added strings */ |
266 | template <class Data> |
267 | inline void |
268 | generateFromKeys(const QMap<QString,Data>& source, QStringList& target) |
269 | { |
270 | generateFromKeys(source.begin(), source.end(), target); |
271 | } |
272 | |
273 | |
274 | } // end namespace KAccelGen |
275 | |
276 | #endif |
277 | |
278 | |