• 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     Q_ASSERT(actionId.size()==4);
00365 
00366     if (actionId.size() < 4) {
00367         return;
00368     }
00369 
00370     // Stop grabbing the key
00371     setInactive(actionId);
00372     actionData *ad = d->takeAction(actionId);
00373     // Don't let dangling pointers behind
00374     Q_FOREACH(int key, d->keyToAction.keys(ad)) {
00375         d->keyToAction.remove(key);
00376     }
00377     delete ad;
00378 
00379     scheduleWriteSettings();
00380 }
00381 
00382 
00383 //TODO: make sure and document that we don't want trailing zero shortcuts in the list
00384 QList<int> KdedGlobalAccel::setShortcut(const QStringList &actionId,
00385                                         const QList<int> &keys, uint flags)
00386 {
00387     //spare the DBus framework some work
00388     const bool setPresent = (flags & SetPresent);
00389     const bool isAutoloading = !(flags & NoAutoloading);
00390     const bool isDefault = (flags & IsDefault);
00391 
00392     actionData *ad = d->findAction(actionId);
00393     if (!ad) {
00394         return QList<int>();
00395     }
00396 
00397     //default shortcuts cannot clash because they don't do anything
00398     if (isDefault) {
00399         if (ad->defaultKeys != keys) {
00400             ad->defaultKeys = keys;
00401             scheduleWriteSettings();
00402         }
00403         return keys;    //doesn't matter
00404     }
00405 
00406     //the trivial and common case - synchronize the action from our data and exit
00407     if (isAutoloading && !ad->isFresh) {
00408         if (!ad->isPresent && setPresent) {
00409             ad->isPresent = true;
00410             foreach (int key, ad->keys) {
00411                 if (key != 0) {
00412                     Q_ASSERT(d->keyToAction.value(key) == ad);
00413                     d->impl->grabKey(key, true);
00414                 }
00415             }
00416         }
00417         return ad->keys;
00418     }
00419 
00420     //now we are actually changing the shortcut of the action
00421 
00422     QList<int> added = d->nonemptyOnly(keys);
00423 
00424     //take care of stale keys and remove from added these that remain.
00425     foreach(int oldKey, ad->keys) {
00426         if (oldKey != 0) {
00427             bool remains = false;
00428             for (int i = 0; i < added.count(); i++) {
00429                 if (oldKey == added[i]) {
00430                     added.removeAt(i);
00431                     i--;
00432                     remains = true;
00433                     //no break; - remove possible duplicates
00434                 }
00435             }
00436             if (!remains) {
00437                 d->keyToAction.remove(oldKey);
00438                 if (ad->isPresent) {
00439                     d->impl->grabKey(oldKey, false);
00440                 }
00441             }
00442         }
00443     }
00444 
00445     //update ad
00446     //note that ad->keys may still get changed later if conflicts are found
00447     if (setPresent) {
00448         ad->isPresent = true;
00449     }
00450     ad->keys = keys;
00451     //maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
00452     //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
00453     //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
00454     //  which can never be fresh if created the usual way
00455     ad->isFresh = false;
00456 
00457     //update keyToAction and find conflicts with other actions
00458     //this code inherently does the right thing for duplicates in added
00459     for (int i = 0; i < added.count(); i++) {
00460         if (!d->keyToAction.contains(added[i])) {
00461             d->keyToAction.insert(added[i], ad);
00462         } else {
00463             //clash
00464             for (int j = 0; j < ad->keys.count(); j++) {
00465                 if (ad->keys[j] == added[i]) {
00466                     if (ad->keys.last() == added[i]) {
00467                         ad->keys.removeLast();
00468                         j--;
00469                     } else
00470                         ad->keys[j] = 0;
00471                 }
00472             }
00473             added.removeAt(i);
00474             i--;
00475         }
00476     }
00477 
00478     if (ad->isPresent) {
00479         foreach (int key, added) {
00480             Q_ASSERT(d->keyToAction.value(key) == ad);
00481             d->impl->grabKey(key, true);
00482         }
00483     }
00484 
00485     scheduleWriteSettings();
00486 
00487     return ad->keys;
00488 }
00489 
00490 
00491 void KdedGlobalAccel::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
00492 {
00493     actionData *ad = d->findAction(actionId);
00494     if (!ad)
00495         return;
00496 
00497     uint setterFlags = NoAutoloading;
00498 
00499     QList<int> oldKeys = ad->keys;
00500     QList<int> newKeys = setShortcut(actionId, keys, setterFlags);
00501 
00502     // if (oldKeys == newKeys)
00503     //     return;
00504     // We cannot make that comparison or we break KGlobalAccel which first
00505     // calls setShortcut() and *then* setForeignShortcut. 
00506     emit yourShortcutGotChanged(actionId, newKeys);
00507 }
00508 
00509 
00510 void KdedGlobalAccel::setInactive(const QStringList &actionId)
00511 {
00512     actionData *ad = d->findAction(actionId);
00513     if (!ad)
00514         return;
00515     ad->isPresent = false;
00516 
00517     const int len = ad->keys.count();
00518     for (int i = 0; i < len; i++)
00519         if (ad->keys[i] != 0)
00520             d->impl->grabKey(ad->keys[i], false);
00521 }
00522 
00523 
00524 void KdedGlobalAccel::scheduleWriteSettings()
00525 {
00526     if (!d->writeoutTimer.isActive())
00527         d->writeoutTimer.start(500);
00528 }
00529 
00530 
00531 //slot
00532 void KdedGlobalAccel::writeSettings()
00533 {
00534     foreach (const componentData *const cd, d->mainComponents) {
00535         KConfigGroup configGroup(&d->config, cd->uniqueName);
00536 
00537         KConfigGroup friendlyGroup(&configGroup, "Friendly Name");  // :)
00538         friendlyGroup.writeEntry("Friendly Name", cd->friendlyName);
00539 
00540         foreach (const actionData *const ad, cd->actions) {
00541             if (ad->isFresh) {
00542                 //no shortcut assignement took place, the action was only registered
00543                 //(we could still write it out to document its existence, but the "fresh"
00544                 //state should be regarded as transitional *only*.)
00545                 continue;
00546             }
00547             QStringList entry(stringFromKeys(ad->keys));
00548             entry.append(stringFromKeys(ad->defaultKeys));
00549             entry.append(ad->friendlyName);
00550 
00551             configGroup.writeEntry(ad->uniqueName, entry);
00552         }
00553     }
00554 
00555     d->config.sync();
00556 }
00557 
00558 
00559 void KdedGlobalAccel::loadSettings()
00560 {
00561     QStringList lActionId;
00562     for (int i = 0; i < 4; i++) {
00563         lActionId.append(QString());
00564     }
00565 
00566     foreach (const QString &groupName, d->config.groupList()) {
00567         KConfigGroup configGroup(&d->config, groupName);
00568         lActionId[ComponentUnique] = groupName;
00569 
00570         KConfigGroup friendlyGroup(&configGroup, "Friendly Name");
00571         lActionId[ComponentFriendly] = friendlyGroup.readEntry("Friendly Name");
00572 
00573         foreach (const QString &confKey, configGroup.keyList()) {
00574             const QStringList entry = configGroup.readEntry(confKey, QStringList());
00575             if (entry.size() != 3) {
00576                 continue;
00577             }
00578             lActionId[ActionUnique] = confKey;
00579             lActionId[ActionFriendly] = entry[2];
00580 
00581             actionData *ad = d->addAction(lActionId);
00582             ad->keys = keysFromString(entry[0]);
00583             ad->defaultKeys = keysFromString(entry[1]);
00584             ad->isPresent = false;
00585             ad->isFresh = false;
00586     
00587             foreach (int key, ad->keys) {
00588                 if (key != 0) {
00589                     d->keyToAction.insert(key, ad);
00590                 }
00591             }
00592         }
00593     }
00594 }
00595 
00596 
00597 QList<int> KdedGlobalAccel::keysFromString(const QString &str)
00598 {
00599     QList<int> ret;
00600     if (str == "none") {
00601         return ret;
00602     }
00603     QStringList strList = str.split('\t');
00604     foreach (const QString &s, strList) {
00605         int key = QKeySequence(s)[0];
00606         if (key != -1) {     //sanity check just in case
00607             ret.append(key);
00608         }
00609     }
00610     return ret;
00611 }
00612 
00613 
00614 QString KdedGlobalAccel::stringFromKeys(const QList<int> &keys)
00615 {
00616     if (keys.isEmpty()) {
00617         return "none";
00618     }
00619     QString ret;
00620     foreach (int key, keys) {
00621         ret.append(QKeySequence(key).toString());
00622         ret.append('\t');
00623     }
00624     ret.chop(1);
00625     return ret;
00626 }
00627 
00628 
00629 bool KdedGlobalAccel::keyPressed(int keyQt)
00630 {
00631     actionData *ad = d->keyToAction.value(keyQt);
00632     if (!ad || !ad->isPresent)
00633         return false;
00634 
00635     QStringList data(ad->parent->uniqueName);
00636     data.append(ad->uniqueName);
00637     data.append(ad->parent->friendlyName);
00638     data.append(ad->friendlyName);
00639 #ifdef Q_WS_X11
00640     // pass X11 timestamp
00641     long timestamp = QX11Info::appTime();
00642     // Make sure kded has ungrabbed the keyboard after receiving the keypress,
00643     // otherwise actions in application that try to grab the keyboard (e.g. in kwin)
00644     // may fail to do so. There is still a small race condition with this being out-of-process.
00645     qApp->syncX();
00646 #else
00647     long timestamp = 0;
00648 #endif
00649     emit invokeAction(data, timestamp);
00650     return true;
00651 }
00652 
00653 #include "kdedglobalaccel.moc"
00654 #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.4
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