File indexing completed on 2024-04-14 14:20:15

0001 /*  This file is part of the KDE project
0002     Copyright (C) 2000 Keunwoo Lee <klee@cs.washington.edu>
0003 
0004     This program is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU Library General Public
0006     License as published by the Free Software Foundation; either
0007     version 2 of the License, or (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU Library General Public License for more details.
0013 
0014     You should have received a copy of the GNU Library General Public License
0015     along with this library; see the file COPYING.LIB. If not, write to
0016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017     Boston, MA  02110-1301, USA.
0018 */
0019 
0020 #ifndef KACCELGEN_H
0021 #define KACCELGEN_H
0022 
0023 #include <QMap>
0024 #include <QString>
0025 #include <QStringList>
0026 
0027 /**
0028  * Provides functions that, given a collection of QStrings, will
0029  * automatically and intelligently assign menu accelerators to the
0030  * QStrings in the collection.
0031  *
0032  * NOTE: When this file speaks of "accelerators", we really mean
0033  * accelerators as defined by the KDE User Interface Guidelines.  We
0034  * do NOT mean "shortcuts", which are what's handled by most other KDE
0035  * libraries with "accel" in the name.
0036  *
0037  * In the Qt library, the mechanism for adding a keyboard accelerator
0038  * to a menu item is to insert an '&' before the letter. Since we
0039  * usually don't want to disturb the original collection, the idiom in
0040  * these functions is to populate a "target" QStringList parameter
0041  * with the input collectin's QStrings, plus possibly some added '&'
0042  * characters.
0043  *
0044  * That is the mechanism. Here is the policy, in order of decreasing
0045  * importance (it may seem like these are implementation details, but
0046  * IMHO the policy is an important part of the interface):
0047  *
0048  * 1. If the string already contains an '&' character, skip this
0049  * string, because we consider such strings to be "user-specified"
0050  * accelerators.
0051  *
0052  * 2. No accelerator may clash with a previously defined accelerator,
0053  * including any legal (alphanumeric) user-specified accelerator
0054  * anywhere in the collection
0055  *
0056  * 3. Prefer alphanumerics at the start of the string.
0057  *
0058  * 4. Otherwise, prefer alphanumerics at the start of a word.
0059  *
0060  * 5. Otherwise, choose any alphanumeric character not already
0061  * taken. If no such character is available, give up & skip this
0062  * string.
0063  *
0064  * A typical use of these functions would be to automatically assign
0065  * accelerators to a dynamically populated popup menu.  For example,
0066  * the core code was written to automatically set accelerators for the
0067  * "Load View Profile" popup menu for Konqueror.  We quickly realized
0068  * that it would be useful to make this facility more generally
0069  * available, so I abstracted it out into a set of templates.
0070  *
0071  * TODO:
0072  *
0073  * + Add sugar functions for more collections.
0074  *
0075  * + Add more Deref classes so that we can access a wider variety of
0076  * collections.
0077  *
0078  * @deprecated
0079  * */
0080 namespace KAccelGen
0081 {
0082 
0083 // HELPERS
0084 
0085 /**
0086  * Static dereference class, for use as a template parameter.
0087  */
0088 template <class Iter>
0089 class Deref
0090 {
0091 public:
0092     static QString deref(Iter i)
0093     {
0094         return *i;
0095     }
0096 };
0097 
0098 /**
0099  * Static dereference class that calls the key() method on its
0100  * target; for use as a template parameter.
0101  */
0102 template <class Iter>
0103 class Deref_Key
0104 {
0105 public:
0106     static QString deref(Iter i)
0107     {
0108         return i.key();
0109     }
0110 };
0111 
0112 /**
0113  * Helper to determine if the given offset in the string could be a
0114  * legal alphanumeric accelerator.
0115  *
0116  * @param str   base string
0117  * @param index offset to check
0118  */
0119 inline bool
0120 isLegalAccelerator(const QString &str, int index)
0121 {
0122     return index >= 0 && index < str.length()
0123            && str[index].isLetterOrNumber();
0124 }
0125 
0126 /**
0127  * Loads all legal predefined accelerators in the (implicitly
0128  * specified) collection into the given QMap.
0129  *
0130  * @param begin start iterator
0131  * @param end   (last+1) iterator
0132  * @param keys map to store output
0133  */
0134 template <class Iter, class Deref>
0135 inline void
0136 loadPredefined(Iter begin, Iter end, QMap<QChar, bool> &keys)
0137 {
0138     for (Iter i = begin; i != end; ++i) {
0139         QString item = Deref::deref(i);
0140         int user_ampersand = item.indexOf(QLatin1Char('&'));
0141         if (user_ampersand >= 0) {
0142             // Sanity check.  Note that we don't try to find an
0143             // accelerator if the user shoots him/herself in the foot
0144             // by adding a bad '&'.
0145             if (isLegalAccelerator(item, user_ampersand + 1)) {
0146                 keys.insert(item[user_ampersand + 1], true);
0147             }
0148         }
0149     }
0150 }
0151 
0152 // ///////////////////////////////////////////////////////////////////
0153 // MAIN USER FUNCTIONS
0154 
0155 /**
0156  * Main, maximally flexible template function that assigns
0157  * accelerators to the elements of a collection of QStrings. Clients
0158  * will seldom use this directly, as it's usually easier to use one of
0159  * the wrapper functions that simply takes a collection (see below).
0160  *
0161  * The Deref template parameter is a class containing a static
0162  * dereferencing function, modeled after the comparison class C in
0163  * Stroustrup 13.4.
0164  *
0165  * @param begin  (you know)
0166  * @param end    (you know)
0167  * @param target collection to store generated strings
0168  */
0169 template <class Iter, class Iter_Deref >
0170 void
0171 generate(Iter begin, Iter end, QStringList &target)
0172 {
0173     // Will keep track of used accelerator chars
0174     QMap<QChar, bool> used_accels;
0175 
0176     // Prepass to detect manually user-coded accelerators
0177     loadPredefined<Iter, Iter_Deref>(begin, end, used_accels);
0178 
0179     // Main pass
0180     for (Iter i = begin; i != end; ++i) {
0181         QString item = Iter_Deref::deref(i);
0182 
0183         // Attempt to find a good accelerator, but only if the user
0184         // has not manually hardcoded one.
0185         int user_ampersand = item.indexOf(QLatin1Char('&'));
0186         if (user_ampersand < 0 || item[user_ampersand + 1] == QLatin1Char('&')) {
0187             bool found = false;
0188             int j;
0189 
0190             // Check word-starting letters first.
0191             for (j = 0; j < item.length(); ++j) {
0192                 if (isLegalAccelerator(item, j)
0193                         && !used_accels.contains(item[j])
0194                         && (0 == j || (j > 0 && item[j - 1].isSpace()))) {
0195                     found = true;
0196                     break;
0197                 }
0198             }
0199 
0200             if (!found) {
0201                 // No word-starting letter; search for any letter.
0202                 for (j = 0; j < item.length(); ++j) {
0203                     if (isLegalAccelerator(item, j)
0204                             && !used_accels.contains(item[j])) {
0205                         found = true;
0206                         break;
0207                     }
0208                 }
0209             }
0210 
0211             if (found) {
0212                 // Both upper and lower case marked as used
0213                 used_accels.insert(item[j].toUpper(), true);
0214                 used_accels.insert(item[j].toLower(), true);
0215                 item.insert(j, QLatin1Char('&'));
0216             }
0217         }
0218 
0219         target.append(item);
0220     }
0221 }
0222 
0223 /**
0224  * Another convenience function; looks up the key instead of
0225  * dereferencing directly for the given iterator.
0226  *
0227  * @param begin
0228  * @param end
0229  * @param target
0230  */
0231 template <class Iter>
0232 inline void
0233 generateFromKeys(Iter begin, Iter end, QStringList &target)
0234 {
0235     generate< Iter, Deref_Key<Iter> >(begin, end, target);
0236 }
0237 
0238 /**
0239  * Convenience function; generates accelerators for all the items in
0240  * a QStringList.
0241  *
0242  * @param source Strings for which to generate accelerators
0243  * @param target Output for accelerator-added strings */
0244 inline void
0245 generate(const QStringList &source, QStringList &target)
0246 {
0247     generate<QStringList::ConstIterator, Deref<QStringList::ConstIterator> >(source.begin(), source.end(), target);
0248 }
0249 
0250 /**
0251  * Convenience function; generates accelerators for all the values in
0252  * a QMap<T,QString>.
0253  *
0254  * @param source Map with input strings as VALUES.
0255  * @param target Output for accelerator-added strings */
0256 template <class Key>
0257 inline void
0258 generateFromValues(const QMap<Key, QString> &source, QStringList &target)
0259 {
0260     generate<typename QMap<Key, QString>::ConstIterator, Deref_Key<typename QMap<Key, QString>::ConstIterator> >(source.begin(), source.end(), target);
0261 }
0262 
0263 /**
0264  * Convenience function; generates an accelerator mapping from all the
0265  * keys in a QMap<QString,T>
0266  *
0267  * @param source Map with input strings as KEYS.
0268  * @param target Output for accelerator-added strings */
0269 template <class Data>
0270 inline void
0271 generateFromKeys(const QMap<QString, Data> &source, QStringList &target)
0272 {
0273     generateFromKeys(source.begin(), source.end(), target);
0274 }
0275 
0276 } // end namespace KAccelGen
0277 
0278 #endif
0279