• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDEUI

kdedglobalaccel.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of the KDE libraries
00003 
00004     Copyright (c) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 
00023 #include "kdedglobalaccel.h"
00024 #include <kdebug.h>
00025 #include "kdedglobalaccel_adaptor.h"
00026 
00027 // For KGlobalAccelImpl
00028 #ifdef Q_WS_X11
00029 #include "kglobalaccel_x11.h"
00030 #elif defined(Q_WS_MACX)
00031 #include "kglobalaccel_mac.h"
00032 #elif defined(Q_WS_WIN)
00033 #include "kglobalaccel_win.h"
00034 #else
00035 #include "kglobalaccel_qws.h"
00036 #endif
00037 
00038 #include <QtCore/QHash>
00039 #include <QtCore/QTimer>
00040 
00041 #ifdef Q_WS_X11
00042 #include <QtGui/QX11Info>
00043 #include <QtGui/QApplication>
00044 #endif
00045 
00046 #include <kconfiggroup.h>
00047 #include <kglobal.h>
00048 #include <ksharedconfig.h>
00049 #include <kpluginfactory.h>
00050 #include <kpluginloader.h>
00051 
00052 K_PLUGIN_FACTORY(KdedGlobalAccelFactory,
00053                  registerPlugin<KdedGlobalAccel>();
00054     )
00055 K_EXPORT_PLUGIN(KdedGlobalAccelFactory("globalaccel"))
00056 
00057 struct componentData
00058 {
00059     QString uniqueName;
00060     //the name as it would be found in a magazine article about the application,
00061     //possibly localized if a localized name exists.
00062     QString friendlyName;
00063     QHash<QString, actionData *> actions;
00064 };
00065 
00066 struct actionData
00067 {
00068 //TODO: clear isPresent when an action's application/mainComponent disappears
00069     bool isPresent:1;
00070     bool isFresh:1;
00071     componentData *parent;
00072     QString uniqueName;
00073     QString friendlyName; //usually localized
00074     QList<int> keys;
00075     QList<int> defaultKeys;
00076 };
00077 
00078 enum actionIdFields
00079 {
00080     ComponentUnique = 0,
00081     ActionUnique = 1,
00082     ComponentFriendly = 2,
00083     ActionFriendly = 3
00084 };
00085 
00086 
00087 //Consider to emit warnings if an actionId does not contain enough elements of that turns out
00088 //to be a source of bugs.
00089 
00090 
00091 class KdedGlobalAccelPrivate
00092 {
00093 public:
00094     KdedGlobalAccelPrivate();
00095     ~KdedGlobalAccelPrivate();
00096     actionData *findAction(int) const;
00097     actionData *findAction(const QStringList &actionId) const;
00098     actionData *addAction(const QStringList &actionId);
00099     actionData *takeAction(const QStringList &actionId);
00100 
00101     //helpers
00102     static bool isEmpty(const QList<int>&);
00103     static QList<int> nonemptyOnly(const QList<int> &);
00104 
00105     KGlobalAccelImpl *impl;
00106 
00107     QHash<int, actionData *> keyToAction;
00108     QHash<QString, componentData *> mainComponents;
00109 
00110     KConfig config;
00111     QTimer writeoutTimer;
00112 };
00113 
00114 
00115 KdedGlobalAccelPrivate::KdedGlobalAccelPrivate()
00116  : config("kglobalshortcutsrc", KConfig::SimpleConfig)
00117 {
00118 }
00119 
00120 
00121 KdedGlobalAccelPrivate::~KdedGlobalAccelPrivate()
00122 {
00123 }
00124 
00125 
00126 actionData *KdedGlobalAccelPrivate::findAction(int key) const
00127 {
00128     return keyToAction.value(key);
00129 }
00130 
00131 
00132 actionData *KdedGlobalAccelPrivate::findAction(const QStringList &actionId) const
00133 {
00134     if (actionId.count() < 2)
00135         return 0;
00136     componentData *cd = mainComponents.value(actionId.at(ComponentUnique));
00137     if (!cd)
00138         return 0;
00139     return cd->actions.value(actionId.at(ActionUnique));
00140 }
00141 
00142 
00143 actionData *KdedGlobalAccelPrivate::addAction(const QStringList &actionId)
00144 {
00145     Q_ASSERT(actionId.size() >= 4);
00146     componentData *cd = mainComponents.value(actionId.at(ComponentUnique));
00147     if (!cd) {
00148         cd = new componentData;
00149         cd->uniqueName = actionId.at(ComponentUnique);
00150         cd->friendlyName = actionId.at(ComponentFriendly);
00151         mainComponents.insert(actionId.at(ComponentUnique), cd);
00152     }
00153     Q_ASSERT(!cd->actions.value(actionId.at(ActionUnique)));
00154     actionData *ad = new actionData;
00155     ad->parent = cd;
00156     ad->uniqueName = actionId.at(ActionUnique);
00157     ad->friendlyName = actionId.at(ActionFriendly);
00158     cd->actions.insert(actionId.at(ActionUnique), ad);
00159     return ad;
00160 }
00161 
00162 
00163 actionData *KdedGlobalAccelPrivate::takeAction(const QStringList &actionId)
00164 {
00165     componentData *cd = mainComponents.value(actionId.at(ComponentUnique));
00166     if (!cd)
00167         return 0;
00168     actionData *ret = cd->actions.take(actionId.at(ActionUnique));
00169     if (cd->actions.isEmpty())
00170         delete mainComponents.take(actionId.at(ComponentUnique));
00171     return ret;
00172 }
00173 
00174 
00175 //return if a list of keys is *logically* empty
00176 //static
00177 bool KdedGlobalAccelPrivate::isEmpty(const QList<int>& keys)
00178 {
00179     const int count = keys.count();
00180     for (int i = 0; i < count; i++)
00181         if (keys[i] != 0)
00182             return false;
00183 
00184     return true;
00185 }
00186 
00187 
00188 //static
00189 QList<int> KdedGlobalAccelPrivate::nonemptyOnly(const QList<int> &keys)
00190 {
00191     QList<int> ret;
00192     const int count = keys.count();
00193     for (int i = 0; i < count; i++)
00194         if (keys[i] != 0)
00195             ret.append(keys[i]);
00196 
00197     return ret;
00198 }
00199 
00200 
00201 KdedGlobalAccel::KdedGlobalAccel(QObject* parent, const QList<QVariant>&)
00202  : KDEDModule(parent),
00203    d(new KdedGlobalAccelPrivate)
00204 {
00205     qDBusRegisterMetaType<QList<int> >();
00206 
00207     d->impl = new KGlobalAccelImpl(this);
00208     //TODO: Make this controllable from applications, for example to prevent
00209     //shortcuts from triggering when the user is entering a shortcut
00210     d->impl->setEnabled(true);
00211     connect(&d->writeoutTimer, SIGNAL(timeout()), SLOT(writeSettings()));
00212     d->writeoutTimer.setSingleShot(true);
00213     connect(this, SIGNAL(moduleDeleted(KDEDModule *)), SLOT(writeSettings()));
00214 
00215     loadSettings();
00216     new KdedGlobalAccelAdaptor(this);
00217     QDBusConnection::sessionBus().registerObject("/KdedGlobalAccel", this);
00218 }
00219 
00220 
00221 KdedGlobalAccel::~KdedGlobalAccel()
00222 {
00223     // TODO: Rip out all that StringLists and Lists
00224     // ... by providing classes for ActionId and Component and whatever is
00225     // implemented by a QStringList.
00226 
00227     // Unregister "/KdedGlobalAccel" explicit
00228     QDBusConnection::sessionBus().unregisterObject("/KdedGlobalAccel" );
00229 
00230     // Unregister all currently registered actions. Enables the module to be
00231     // loaded / unloaded by kded.
00232     Q_FOREACH (const QStringList &component, allComponents()) {
00233         Q_FOREACH (const QStringList &actionId, allActionsForComponent(component)) {
00234             setInactive(actionId);
00235         }
00236     }
00237 
00238     //TODO: is this safe?
00239     delete d->impl;
00240     delete d;
00241 }
00242 
00243 QList<QStringList> KdedGlobalAccel::allComponents()
00244 {
00245     //### Would it be advantageous to sort the components by unique name?
00246     QList<QStringList> ret;
00247     QStringList emptyList;
00248     for (int i = 0; i < 4; i++) {
00249         emptyList.append(QString());
00250     }
00251 
00252     foreach (const componentData *const cd, d->mainComponents) {
00253         QStringList actionId(emptyList);
00254         actionId[ComponentUnique] = cd->uniqueName;
00255         actionId[ComponentFriendly] = cd->friendlyName;
00256         ret.append(actionId);
00257     }
00258     return ret;
00259 }
00260 
00261 QList<QStringList> KdedGlobalAccel::allActionsForComponent(const QStringList &actionId)
00262 {
00263     //### Would it be advantageous to sort the actions by unique name?
00264     QList<QStringList> ret;
00265 
00266     componentData *const cd = d->mainComponents.value(actionId[ComponentUnique]);
00267     if (!cd) {
00268         return ret;
00269     }
00270 
00271     QStringList partialId(actionId[ComponentUnique]);   //ComponentUnique
00272     partialId.append(QString());                        //ActionUnique
00273     //Use our internal friendlyName, not the one passed in. We should have the latest data.
00274     partialId.append(cd->friendlyName);                 //ComponentFriendly
00275     partialId.append(QString());                        //ActionFriendly
00276 
00277     foreach (const actionData *const ad, cd->actions) {
00278         if (ad->isFresh) {
00279             // isFresh is only an intermediate state, not to be reported outside.
00280             continue;
00281         }
00282         QStringList actionId(partialId);
00283         actionId[ActionUnique] = ad->uniqueName;
00284         actionId[ActionFriendly] = ad->friendlyName;
00285         ret.append(actionId);
00286     }
00287     return ret;
00288 }
00289 
00290 QList<int> KdedGlobalAccel::allKeys()
00291 {
00292     QList<int> ret = d->keyToAction.keys();
00293     kDebug() << ret;
00294     return ret;
00295 }
00296 
00297 QStringList KdedGlobalAccel::allKeysAsString()
00298 {
00299     QStringList ret;
00300     foreach(int keyQt, d->keyToAction.keys())
00301         ret << QKeySequence(keyQt).toString();
00302     return ret;
00303 }
00304 
00305 QStringList KdedGlobalAccel::actionId(int key)
00306 {
00307     QStringList ret;
00308     if (actionData *ad = d->findAction(key)) {
00309         ret.append(ad->parent->uniqueName);
00310         ret.append(ad->uniqueName);
00311         ret.append(ad->parent->friendlyName);
00312         ret.append(ad->friendlyName);
00313     }
00314     return ret;
00315 }
00316 
00317 
00318 QList<int> KdedGlobalAccel::shortcut(const QStringList &action)
00319 {
00320     actionData *ad = d->findAction(action);
00321     if (ad)
00322         return ad->keys;
00323     return QList<int>();
00324 }
00325 
00326 
00327 QList<int> KdedGlobalAccel::defaultShortcut(const QStringList &action)
00328 {
00329     actionData *ad = d->findAction(action);
00330     if (ad)
00331         return ad->defaultKeys;
00332     return QList<int>();
00333 }
00334 
00335 
00336 void KdedGlobalAccel::doRegister(const QStringList &actionId)
00337 {
00338     if (actionId.size() < 4) {
00339         return;
00340     }
00341     actionData *ad = d->findAction(actionId);
00342     if (!ad) {
00343         ad = d->addAction(actionId);
00344         //addAction only fills in the names
00345         ad->isPresent = false;
00346         ad->isFresh = true;
00347         //scheduleWriteSettings();  //we don't write out isFresh actions, cf. writeSettings()
00348     } else {
00349         //a switch of locales is one common reason for a changing friendlyName
00350         if ((!actionId[ActionFriendly].isEmpty()) && ad->friendlyName != actionId[ActionFriendly]) {
00351             ad->friendlyName = actionId[ActionFriendly];
00352             scheduleWriteSettings();
00353         }
00354         if ((!actionId[ComponentFriendly].isEmpty()) && ad->parent->friendlyName != actionId[ComponentFriendly]) {
00355             ad->parent->friendlyName = actionId[ComponentFriendly];
00356             scheduleWriteSettings();
00357         }
00358     }
00359 }
00360 
00361 
00362 void KdedGlobalAccel::unRegister(const QStringList &actionId)
00363 {
00364     kDebug(125) << actionId;
00365 
00366     Q_ASSERT(actionId.size()==4);
00367     if (actionId.size() < 4) {
00368         return;
00369     }
00370 
00371     // Stop grabbing the key
00372     setInactive(actionId);
00373     actionData *ad = d->takeAction(actionId);
00374     // Don't let dangling pointers behind
00375     Q_FOREACH(int key, d->keyToAction.keys(ad)) {
00376         d->keyToAction.remove(key);
00377     }
00378     delete ad;
00379 
00380     scheduleWriteSettings();
00381 }
00382 
00383 
00384 //TODO: make sure and document that we don't want trailing zero shortcuts in the list
00385 QList<int> KdedGlobalAccel::setShortcut(const QStringList &actionId,
00386                                         const QList<int> &keys, uint flags)
00387 {
00388     //spare the DBus framework some work
00389     const bool setPresent = (flags & SetPresent);
00390     const bool isAutoloading = !(flags & NoAutoloading);
00391     const bool isDefault = (flags & IsDefault);
00392 
00393     actionData *ad = d->findAction(actionId);
00394     if (!ad) {
00395         return QList<int>();
00396     }
00397 
00398     //default shortcuts cannot clash because they don't do anything
00399     if (isDefault) {
00400         if (ad->defaultKeys != keys) {
00401             ad->defaultKeys = keys;
00402             scheduleWriteSettings();
00403         }
00404         return keys;    //doesn't matter
00405     }
00406 
00407     //the trivial and common case - synchronize the action from our data and exit
00408     if (isAutoloading && !ad->isFresh) {
00409         if (!ad->isPresent && setPresent) {
00410             ad->isPresent = true;
00411             foreach (int key, ad->keys) {
00412                 if (key != 0) {
00413                     Q_ASSERT(d->keyToAction.value(key) == ad);
00414                     d->impl->grabKey(key, true);
00415                 }
00416             }
00417         }
00418         return ad->keys;
00419     }
00420 
00421     //now we are actually changing the shortcut of the action
00422 
00423     QList<int> added = d->nonemptyOnly(keys);
00424 
00425     //take care of stale keys and remove from added these that remain.
00426     foreach(int oldKey, ad->keys) {
00427         if (oldKey != 0) {
00428             bool remains = false;
00429             for (int i = 0; i < added.count(); i++) {
00430                 if (oldKey == added[i]) {
00431                     added.removeAt(i);
00432                     i--;
00433                     remains = true;
00434                     //no break; - remove possible duplicates
00435                 }
00436             }
00437             if (!remains) {
00438                 d->keyToAction.remove(oldKey);
00439                 if (ad->isPresent) {
00440                     d->impl->grabKey(oldKey, false);
00441                 }
00442             }
00443         }
00444     }
00445 
00446     //update ad
00447     //note that ad->keys may still get changed later if conflicts are found
00448     if (setPresent) {
00449         ad->isPresent = true;
00450     }
00451     ad->keys = keys;
00452     //maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
00453     //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
00454     //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
00455     //  which can never be fresh if created the usual way
00456     ad->isFresh = false;
00457 
00458     //update keyToAction and find conflicts with other actions
00459     //this code inherently does the right thing for duplicates in added
00460     for (int i = 0; i < added.count(); i++) {
00461         if (!d->keyToAction.contains(added[i])) {
00462             d->keyToAction.insert(added[i], ad);
00463         } else {
00464             //clash
00465             for (int j = 0; j < ad->keys.count(); j++) {
00466                 if (ad->keys[j] == added[i]) {
00467                     if (ad->keys.last() == added[i]) {
00468                         ad->keys.removeLast();
00469                         j--;
00470                     } else
00471                         ad->keys[j] = 0;
00472                 }
00473             }
00474             added.removeAt(i);
00475             i--;
00476         }
00477     }
00478 
00479     if (ad->isPresent) {
00480         foreach (int key, added) {
00481             Q_ASSERT(d->keyToAction.value(key) == ad);
00482             d->impl->grabKey(key, true);
00483         }
00484     }
00485 
00486     scheduleWriteSettings();
00487 
00488     return ad->keys;
00489 }
00490 
00491 
00492 void KdedGlobalAccel::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
00493 {
00494     actionData *ad = d->findAction(actionId);
00495     if (!ad)
00496         return;
00497 
00498     uint setterFlags = NoAutoloading;
00499 
00500     QList<int> oldKeys = ad->keys;
00501     QList<int> newKeys = setShortcut(actionId, keys, setterFlags);
00502 
00503     // if (oldKeys == newKeys)
00504     //     return;
00505     // We cannot make that comparison or we break KGlobalAccel which first
00506     // calls setShortcut() and *then* setForeignShortcut. 
00507     emit yourShortcutGotChanged(actionId, newKeys);
00508 }
00509 
00510 
00511 void KdedGlobalAccel::setInactive(const QStringList &actionId)
00512 {
00513     actionData *ad = d->findAction(actionId);
00514     if (!ad)
00515         return;
00516     ad->isPresent = false;
00517 
00518     const int len = ad->keys.count();
00519     for (int i = 0; i < len; i++)
00520         if (ad->keys[i] != 0)
00521             d->impl->grabKey(ad->keys[i], false);
00522 }
00523 
00524 
00525 void KdedGlobalAccel::scheduleWriteSettings()
00526 {
00527     if (!d->writeoutTimer.isActive())
00528         d->writeoutTimer.start(500);
00529 }
00530 
00531 
00532 //slot
00533 void KdedGlobalAccel::writeSettings()
00534 {
00535     foreach (const componentData *const cd, d->mainComponents) {
00536         KConfigGroup configGroup(&d->config, cd->uniqueName);
00537 
00538         KConfigGroup friendlyGroup(&configGroup, "Friendly Name");  // :)
00539         friendlyGroup.writeEntry("Friendly Name", cd->friendlyName);
00540 
00541         foreach (const actionData *const ad, cd->actions) {
00542             if (ad->isFresh) {
00543                 //no shortcut assignement took place, the action was only registered
00544                 //(we could still write it out to document its existence, but the "fresh"
00545                 //state should be regarded as transitional *only*.)
00546                 continue;
00547             }
00548             QStringList entry(stringFromKeys(ad->keys));
00549             entry.append(stringFromKeys(ad->defaultKeys));
00550             entry.append(ad->friendlyName);
00551 
00552             configGroup.writeEntry(ad->uniqueName, entry);
00553         }
00554     }
00555 
00556     d->config.sync();
00557 }
00558 
00559 
00560 void KdedGlobalAccel::loadSettings()
00561 {
00562     QStringList lActionId;
00563     for (int i = 0; i < 4; i++) {
00564         lActionId.append(QString());
00565     }
00566 
00567     foreach (const QString &groupName, d->config.groupList()) {
00568         KConfigGroup configGroup(&d->config, groupName);
00569         lActionId[ComponentUnique] = groupName;
00570 
00571         KConfigGroup friendlyGroup(&configGroup, "Friendly Name");
00572         lActionId[ComponentFriendly] = friendlyGroup.readEntry("Friendly Name");
00573 
00574         foreach (const QString &confKey, configGroup.keyList()) {
00575             const QStringList entry = configGroup.readEntry(confKey, QStringList());
00576             if (entry.size() != 3) {
00577                 continue;
00578             }
00579             lActionId[ActionUnique] = confKey;
00580             lActionId[ActionFriendly] = entry[2];
00581 
00582             actionData *ad = d->addAction(lActionId);
00583             ad->keys = keysFromString(entry[0]);
00584             ad->defaultKeys = keysFromString(entry[1]);
00585             ad->isPresent = false;
00586             ad->isFresh = false;
00587     
00588             foreach (int key, ad->keys) {
00589                 if (key != 0) {
00590                     if (d->keyToAction.contains(key)) {
00591                         // The shortcut is already used. The config file is
00592                         // broken. Ignore the request.
00593                         ad->keys.removeAll(key);
00594                         kWarning() << "Shortcut found twice in kglobalshortcutsrc.";
00595                     } else {
00596                         d->keyToAction.insert(key, ad);
00597                     }
00598                 }
00599             }
00600         }
00601     }
00602 }
00603 
00604 
00605 QList<int> KdedGlobalAccel::keysFromString(const QString &str)
00606 {
00607     QList<int> ret;
00608     if (str == "none") {
00609         return ret;
00610     }
00611     QStringList strList = str.split('\t');
00612     foreach (const QString &s, strList) {
00613         int key = QKeySequence(s)[0];
00614         if (key != -1) {     //sanity check just in case
00615             ret.append(key);
00616         }
00617     }
00618     return ret;
00619 }
00620 
00621 
00622 QString KdedGlobalAccel::stringFromKeys(const QList<int> &keys)
00623 {
00624     if (keys.isEmpty()) {
00625         return "none";
00626     }
00627     QString ret;
00628     foreach (int key, keys) {
00629         ret.append(QKeySequence(key).toString());
00630         ret.append('\t');
00631     }
00632     ret.chop(1);
00633     return ret;
00634 }
00635 
00636 
00637 bool KdedGlobalAccel::keyPressed(int keyQt)
00638 {
00639     actionData *ad = d->keyToAction.value(keyQt);
00640     if (!ad || !ad->isPresent)
00641         return false;
00642 
00643     QStringList data(ad->parent->uniqueName);
00644     data.append(ad->uniqueName);
00645     data.append(ad->parent->friendlyName);
00646     data.append(ad->friendlyName);
00647 #ifdef Q_WS_X11
00648     // pass X11 timestamp
00649     long timestamp = QX11Info::appTime();
00650     // Make sure kded has ungrabbed the keyboard after receiving the keypress,
00651     // otherwise actions in application that try to grab the keyboard (e.g. in kwin)
00652     // may fail to do so. There is still a small race condition with this being out-of-process.
00653     qApp->syncX();
00654 #else
00655     long timestamp = 0;
00656 #endif
00657     emit invokeAction(data, timestamp);
00658     return true;
00659 }
00660 
00661 #include "kdedglobalaccel.moc"
00662 #include "kdedglobalaccel_adaptor.moc"

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal